use crate::query::condition::FilterValue;
use std::any::TypeId;
#[derive(Debug, Clone)]
#[non_exhaustive]
pub(crate) enum ExprNode {
Field { column: &'static str },
RawSql(&'static str),
Literal(FilterValue),
Add(Box<ExprNode>, Box<ExprNode>),
Sub(Box<ExprNode>, Box<ExprNode>),
Mul(Box<ExprNode>, Box<ExprNode>),
Div(Box<ExprNode>, Box<ExprNode>),
And(Box<ExprNode>, Box<ExprNode>),
Or(Box<ExprNode>, Box<ExprNode>),
Not(Box<ExprNode>),
IsNull(Box<ExprNode>),
IsNotNull(Box<ExprNode>),
Coalesce(Vec<ExprNode>),
Cmp {
op: CmpOp,
lhs: Box<ExprNode>,
rhs: Box<ExprNode>,
},
GroupingVariadic {
args: Vec<ExprNode>,
},
Aggregate {
op: AggOp,
arg: Box<ExprNode>,
arg2: Option<Box<ExprNode>>,
filter: Option<Box<ExprNode>>,
cast_to: Option<&'static str>,
distinct: bool,
window: Option<crate::expr::window::WindowSpec>,
order_by: Vec<crate::query::order::OrderExpr>,
within_group_order_by: Vec<crate::query::order::OrderExpr>,
},
Case {
arms: Vec<(ExprNode, ExprNode)>,
otherwise: Box<ExprNode>,
},
Exists(Box<SubqueryNode>),
Subquery(Box<SubqueryNode>),
InSubquery {
lhs: Box<ExprNode>,
negated: bool,
subquery: Box<SubqueryNode>,
},
QuantifiedSubquery {
lhs: Box<ExprNode>,
op: QuantifiedCmpOp,
quantifier: SubqueryQuantifier,
subquery: Box<SubqueryNode>,
},
ArrayLength { column: &'static str },
CurrentYear,
OuterRef {
column: &'static str,
},
OuterRefColumn {
table: &'static str,
column: &'static str,
},
Excluded {
column: &'static str,
},
OuterRefAlias {
alias: &'static str,
column: &'static str,
model_type: TypeId,
model_name: &'static str,
},
TsMatch {
column: &'static str,
dictionary: &'static str,
query_text: String,
},
TsRank {
column: &'static str,
dictionary: &'static str,
query_text: String,
},
TsRankCd {
column: &'static str,
dictionary: &'static str,
query_text: String,
},
#[cfg(feature = "trgm")]
TrgmSimilarTo {
column: &'static str,
pattern: String,
},
#[cfg(feature = "trgm")]
TrgmSimilarityScore {
column: &'static str,
pattern: String,
},
IntervalLiteral {
microseconds: i64,
},
#[cfg(feature = "spatial")]
Spatial(crate::expr::spatial::SpatialExpr),
#[cfg(feature = "spatial")]
RowAggregate {
op: RowAggOp,
#[allow(dead_code)]
columns: Vec<&'static str>,
},
}
#[derive(Debug, Clone)]
pub(crate) struct SubqueryNode {
pub(crate) table: &'static str,
pub(crate) select_column: Option<&'static str>,
pub(crate) where_clause: Option<ErasedSubqueryPredicate>,
}
#[derive(Clone)]
pub(crate) struct ErasedSubqueryPredicate(std::sync::Arc<dyn SubqueryPredicateEmitter>);
pub(crate) trait SubqueryPredicateEmitter: Send + Sync + 'static {
fn emit(
&self,
acc: &mut crate::pg::accumulator::SqlAccumulator,
ctx: crate::query::portable::SqlEmitContext,
) -> Result<(), crate::query::portable::PortablePredicateError>;
fn fmt_debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result;
}
struct TypedSubqueryPredicate<T: crate::model::Model> {
q: crate::query::q::Q<T>,
}
impl<T: crate::model::Model> SubqueryPredicateEmitter for TypedSubqueryPredicate<T> {
fn emit(
&self,
acc: &mut crate::pg::accumulator::SqlAccumulator,
ctx: crate::query::portable::SqlEmitContext,
) -> Result<(), crate::query::portable::PortablePredicateError> {
crate::query::sql::emit_q::<T>(acc, &self.q, ctx)
}
fn fmt_debug(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_tuple("TypedSubqueryPredicate")
.field(&self.q)
.finish()
}
}
impl ErasedSubqueryPredicate {
pub(crate) fn from_q<T: crate::model::Model>(q: crate::query::q::Q<T>) -> Self {
Self(std::sync::Arc::new(TypedSubqueryPredicate::<T> { q }))
}
pub(crate) fn emit(
&self,
acc: &mut crate::pg::accumulator::SqlAccumulator,
ctx: crate::query::portable::SqlEmitContext,
) -> Result<(), crate::query::portable::PortablePredicateError> {
self.0.emit(acc, ctx)
}
}
impl std::fmt::Debug for ErasedSubqueryPredicate {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt_debug(f)
}
}
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum AggOp {
Count,
CountStar,
Sum,
Avg,
Min,
Max,
ArrayAgg,
JsonAgg,
StringAgg(String),
BoolAnd,
BoolOr,
Every,
BitAnd,
BitOr,
BitXor,
StddevPop,
StddevSamp,
Stddev,
VarPop,
VarSamp,
Variance,
CovarPop,
CovarSamp,
Corr,
RegrAvgx,
RegrAvgy,
RegrCount,
RegrIntercept,
RegrR2,
RegrSlope,
RegrSxx,
RegrSxy,
RegrSyy,
JsonObjectAgg,
JsonbObjectAgg,
Grouping,
PercentileCont,
PercentileDisc,
Mode,
HypotheticalRank,
HypotheticalDenseRank,
HypotheticalPercentRank,
HypotheticalCumeDist,
#[cfg(feature = "spatial")]
SpatialCentroid,
#[cfg(feature = "spatial")]
SpatialConvexHull,
#[cfg(feature = "spatial")]
SpatialCollect,
#[cfg(feature = "spatial")]
SpatialUnion,
#[cfg(feature = "spatial")]
SpatialExtent,
#[cfg(feature = "spatial")]
SpatialExtent3D,
#[cfg(feature = "spatial")]
SpatialMakeLine,
#[cfg(feature = "spatial")]
SpatialLineAgg,
#[cfg(feature = "spatial")]
SpatialPolygonAgg,
#[cfg(feature = "spatial")]
SpatialClusterIntersecting,
#[cfg(feature = "spatial")]
SpatialClusterWithin(f64),
#[cfg(feature = "spatial")]
SpatialMemUnion,
#[cfg(feature = "spatial")]
SpatialPolygonize,
}
#[cfg(feature = "spatial")]
#[derive(Debug, Clone, PartialEq)]
pub(crate) enum RowAggOp {
AsMvt {
layer_name: String,
extent: i32,
geom_name: String,
feature_id_name: Option<String>,
},
AsGeobuf {
geom_name: String,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum CmpOp {
Eq,
Neq,
Gt,
Gte,
Lt,
Lte,
IsDistinctFrom,
IsNotDistinctFrom,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum SubqueryQuantifier {
Any,
All,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) enum QuantifiedCmpOp {
Eq,
Neq,
Gt,
Gte,
Lt,
Lte,
}
impl TryFrom<CmpOp> for QuantifiedCmpOp {
type Error = crate::DjogiError;
fn try_from(op: CmpOp) -> Result<Self, Self::Error> {
match op {
CmpOp::Eq => Ok(Self::Eq),
CmpOp::Neq => Ok(Self::Neq),
CmpOp::Gt => Ok(Self::Gt),
CmpOp::Gte => Ok(Self::Gte),
CmpOp::Lt => Ok(Self::Lt),
CmpOp::Lte => Ok(Self::Lte),
CmpOp::IsDistinctFrom => Err(crate::DjogiError::invalid_subquery_modifier(
"ANY/ALL quantifier",
"IS DISTINCT FROM has no quantified subquery form",
)),
CmpOp::IsNotDistinctFrom => Err(crate::DjogiError::invalid_subquery_modifier(
"ANY/ALL quantifier",
"IS NOT DISTINCT FROM has no quantified subquery form",
)),
}
}
}
#[cfg(test)]
mod tests {
use super::{CmpOp, ExprNode, QuantifiedCmpOp, SubqueryNode, SubqueryQuantifier};
#[test]
fn in_subquery_node_round_trips_through_clone_and_debug() {
let inner = SubqueryNode {
table: "authors",
select_column: Some("id"),
where_clause: None,
};
let node = ExprNode::InSubquery {
lhs: Box::new(ExprNode::Field {
column: "author_id",
}),
negated: false,
subquery: Box::new(inner),
};
let cloned = node.clone();
let dbg = format!("{cloned:?}");
assert!(dbg.contains("InSubquery"), "got: {dbg}");
}
#[test]
fn quantified_subquery_node_round_trips() {
let inner = SubqueryNode {
table: "authors",
select_column: Some("follower_count"),
where_clause: None,
};
let node = ExprNode::QuantifiedSubquery {
lhs: Box::new(ExprNode::Field { column: "score" }),
op: QuantifiedCmpOp::Gt,
quantifier: SubqueryQuantifier::Any,
subquery: Box::new(inner),
};
let dbg = format!("{node:?}");
assert!(dbg.contains("QuantifiedSubquery"), "got: {dbg}");
}
#[test]
fn quantified_cmp_op_try_from_maps_six_and_rejects_distinctness() {
assert_eq!(
QuantifiedCmpOp::try_from(CmpOp::Eq).unwrap(),
QuantifiedCmpOp::Eq
);
assert_eq!(
QuantifiedCmpOp::try_from(CmpOp::Neq).unwrap(),
QuantifiedCmpOp::Neq
);
assert_eq!(
QuantifiedCmpOp::try_from(CmpOp::Lt).unwrap(),
QuantifiedCmpOp::Lt
);
assert_eq!(
QuantifiedCmpOp::try_from(CmpOp::Lte).unwrap(),
QuantifiedCmpOp::Lte
);
assert_eq!(
QuantifiedCmpOp::try_from(CmpOp::Gt).unwrap(),
QuantifiedCmpOp::Gt
);
assert_eq!(
QuantifiedCmpOp::try_from(CmpOp::Gte).unwrap(),
QuantifiedCmpOp::Gte
);
assert!(QuantifiedCmpOp::try_from(CmpOp::IsDistinctFrom).is_err());
assert!(QuantifiedCmpOp::try_from(CmpOp::IsNotDistinctFrom).is_err());
let msg = QuantifiedCmpOp::try_from(CmpOp::IsDistinctFrom)
.unwrap_err()
.to_string();
assert!(msg.contains("DISTINCT FROM"), "got: {msg}");
}
}