use crate::db::{
query::{
builder::AggregateExpr,
plan::expr::{Expr, FieldPath},
},
schema::SchemaInfo,
};
#[derive(Clone, Debug)]
pub(in crate::db::sql::lowering) struct AnalyzedLoweredExpr {
expr: Expr,
analysis: LoweredExprAnalysis,
}
impl AnalyzedLoweredExpr {
#[must_use]
pub(in crate::db::sql::lowering) fn new(expr: Expr) -> Self {
let analysis = analyze_lowered_expr(&expr);
Self { expr, analysis }
}
#[must_use]
pub(in crate::db::sql::lowering) const fn expr(&self) -> &Expr {
&self.expr
}
#[must_use]
pub(in crate::db::sql::lowering) const fn analysis(&self) -> &LoweredExprAnalysis {
&self.analysis
}
#[must_use]
pub(in crate::db::sql::lowering) fn into_expr(self) -> Expr {
self.expr
}
#[must_use]
pub(in crate::db::sql::lowering) fn into_parts(self) -> (Expr, LoweredExprAnalysis) {
(self.expr, self.analysis)
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub(in crate::db::sql::lowering) enum LoweredExprSourceRef {
Direct(String),
Path(FieldPath),
}
impl LoweredExprSourceRef {
const fn root(&self) -> &str {
match self {
Self::Direct(field) => field.as_str(),
Self::Path(path) => path.root().as_str(),
}
}
}
#[derive(Clone, Debug, Default)]
pub(in crate::db::sql::lowering) struct LoweredExprAnalysis {
aggregate_refs: Vec<AggregateExpr>,
source_refs: Vec<LoweredExprSourceRef>,
first_unknown_field: Option<String>,
}
impl LoweredExprAnalysis {
#[must_use]
pub(in crate::db::sql::lowering) const fn contains_aggregate(&self) -> bool {
!self.aggregate_refs.is_empty()
}
#[must_use]
pub(in crate::db::sql::lowering) const fn aggregate_refs(&self) -> &[AggregateExpr] {
self.aggregate_refs.as_slice()
}
#[must_use]
pub(in crate::db::sql::lowering) const fn references_direct_fields(&self) -> bool {
!self.source_refs.is_empty()
}
#[must_use]
pub(in crate::db::sql::lowering) fn references_only_direct_fields(
&self,
allowed: &[&str],
) -> bool {
self.source_refs.iter().all(|source_ref| match source_ref {
LoweredExprSourceRef::Direct(field) => allowed.contains(&field.as_str()),
LoweredExprSourceRef::Path(_) => false,
})
}
#[must_use]
pub(in crate::db::sql::lowering) fn first_unknown_field(&self) -> Option<&str> {
self.first_unknown_field.as_deref()
}
#[must_use]
pub(in crate::db::sql::lowering) fn first_unknown_field_for_schema(
&self,
schema: &SchemaInfo,
) -> Option<&str> {
self.first_unknown_field().or_else(|| {
self.source_refs.iter().find_map(|source_ref| {
let root = source_ref.root();
schema.field_slot_index(root).is_none().then_some(root)
})
})
}
#[must_use]
pub(in crate::db::sql::lowering) const fn source_refs(&self) -> &[LoweredExprSourceRef] {
self.source_refs.as_slice()
}
fn visit_field(&mut self, field: &str) {
self.source_refs
.push(LoweredExprSourceRef::Direct(field.to_string()));
}
fn visit_field_path(&mut self, path: &FieldPath) {
self.source_refs
.push(LoweredExprSourceRef::Path(path.clone()));
}
fn visit_aggregate(&mut self, aggregate: &AggregateExpr) {
self.aggregate_refs.push(aggregate.clone());
}
}
#[must_use]
pub(in crate::db::sql::lowering) fn analyze_lowered_expr(expr: &Expr) -> LoweredExprAnalysis {
let mut analysis = LoweredExprAnalysis {
aggregate_refs: Vec::new(),
source_refs: Vec::new(),
first_unknown_field: None,
};
expr.for_each_tree_expr(&mut |node| match node {
Expr::Field(field) => {
analysis.visit_field(field.as_str());
}
Expr::FieldPath(path) => {
analysis.visit_field_path(path);
}
Expr::Aggregate(aggregate) => {
analysis.visit_aggregate(aggregate);
}
_ => {}
});
analysis
}