Skip to main content

icydb_core/db/query/
expr.rs

1//! Module: query::expr
2//! Responsibility: schema-agnostic filter/order expression wrappers and lowering.
3//! Does not own: planner route selection or executor evaluation.
4//! Boundary: intent boundary lowers these to validated predicate/order forms.
5
6use crate::db::{
7    predicate::{Predicate, normalize, normalize_enum_literals},
8    query::{
9        builder::FieldRef,
10        builder::{
11            AggregateExpr, NumericProjectionExpr, RoundProjectionExpr, TextProjectionExpr,
12            ValueProjectionExpr, scalar_projection::render_scalar_projection_expr_sql_label,
13        },
14        plan::{
15            OrderDirection, OrderTerm as PlannedOrderTerm,
16            expr::{Expr, FieldId},
17        },
18    },
19    schema::{SchemaInfo, ValidateError, reject_unsupported_query_features, validate},
20};
21
22///
23/// FilterExpr
24/// Schema-agnostic filter expression for dynamic query input.
25/// Lowered into a validated predicate at the intent boundary.
26///
27
28#[derive(Clone, Debug)]
29pub struct FilterExpr(pub Predicate);
30
31impl FilterExpr {
32    /// Lower the filter expression into a validated predicate for the provided schema.
33    pub(crate) fn lower_with(&self, schema: &SchemaInfo) -> Result<Predicate, ValidateError> {
34        // Phase 1: normalize enum literals using schema enum metadata.
35        let normalized_enum_literals = normalize_enum_literals(schema, &self.0)?;
36
37        // Phase 2: reject unsupported query features and validate against schema.
38        reject_unsupported_query_features(&normalized_enum_literals)?;
39        validate(schema, &normalized_enum_literals)?;
40
41        // Phase 3: normalize structural predicate shape for deterministic planning.
42        Ok(normalize(&normalized_enum_literals))
43    }
44}
45
46///
47/// OrderExpr
48///
49/// Typed fluent ORDER BY expression wrapper.
50/// This exists so fluent code can construct planner-owned ORDER BY
51/// semantics directly at the query boundary.
52///
53
54#[derive(Clone, Debug, Eq, PartialEq)]
55pub struct OrderExpr {
56    label: String,
57    expr: Expr,
58}
59
60impl OrderExpr {
61    /// Build one direct field ORDER BY expression.
62    #[must_use]
63    pub fn field(field: impl Into<String>) -> Self {
64        let field = field.into();
65
66        Self {
67            label: field.clone(),
68            expr: Expr::Field(FieldId::new(field)),
69        }
70    }
71
72    // Freeze one typed fluent order expression into its stable planner-facing
73    // label plus semantic expression so callers do not rediscover either shape.
74    const fn new(label: String, expr: Expr) -> Self {
75        Self { label, expr }
76    }
77
78    // Lower one typed fluent order expression into the planner-owned order
79    // contract now that ordering is expression-based end to end.
80    pub(in crate::db) fn lower(&self, direction: OrderDirection) -> PlannedOrderTerm {
81        PlannedOrderTerm::new(self.label.clone(), self.expr.clone(), direction)
82    }
83}
84
85impl From<&str> for OrderExpr {
86    fn from(value: &str) -> Self {
87        Self::field(value)
88    }
89}
90
91impl From<String> for OrderExpr {
92    fn from(value: String) -> Self {
93        Self::field(value)
94    }
95}
96
97impl From<FieldRef> for OrderExpr {
98    fn from(value: FieldRef) -> Self {
99        Self::field(value.as_str())
100    }
101}
102
103impl From<TextProjectionExpr> for OrderExpr {
104    fn from(value: TextProjectionExpr) -> Self {
105        Self::new(value.sql_label(), value.expr().clone())
106    }
107}
108
109impl From<NumericProjectionExpr> for OrderExpr {
110    fn from(value: NumericProjectionExpr) -> Self {
111        Self::new(value.sql_label(), value.expr().clone())
112    }
113}
114
115impl From<RoundProjectionExpr> for OrderExpr {
116    fn from(value: RoundProjectionExpr) -> Self {
117        Self::new(value.sql_label(), value.expr().clone())
118    }
119}
120
121impl From<AggregateExpr> for OrderExpr {
122    fn from(value: AggregateExpr) -> Self {
123        let expr = Expr::Aggregate(value);
124
125        Self::new(render_scalar_projection_expr_sql_label(&expr), expr)
126    }
127}
128
129///
130/// OrderTerm
131///
132/// Typed fluent ORDER BY term.
133/// Carries one typed ORDER BY expression plus direction so fluent builders can
134/// express deterministic ordering directly at the query boundary.
135///
136
137#[derive(Clone, Debug, Eq, PartialEq)]
138pub struct OrderTerm {
139    expr: OrderExpr,
140    direction: OrderDirection,
141}
142
143impl OrderTerm {
144    /// Build one ascending ORDER BY term from one typed expression.
145    #[must_use]
146    pub fn asc(expr: impl Into<OrderExpr>) -> Self {
147        Self {
148            expr: expr.into(),
149            direction: OrderDirection::Asc,
150        }
151    }
152
153    /// Build one descending ORDER BY term from one typed expression.
154    #[must_use]
155    pub fn desc(expr: impl Into<OrderExpr>) -> Self {
156        Self {
157            expr: expr.into(),
158            direction: OrderDirection::Desc,
159        }
160    }
161
162    // Lower one typed fluent order term directly into the planner-owned
163    // `OrderTerm` contract.
164    pub(in crate::db) fn lower(&self) -> PlannedOrderTerm {
165        self.expr.lower(self.direction)
166    }
167}
168
169/// Build one typed direct-field ORDER BY expression.
170#[must_use]
171pub fn field(field: impl Into<String>) -> OrderExpr {
172    OrderExpr::field(field)
173}
174
175/// Build one ascending typed ORDER BY term.
176#[must_use]
177pub fn asc(expr: impl Into<OrderExpr>) -> OrderTerm {
178    OrderTerm::asc(expr)
179}
180
181/// Build one descending typed ORDER BY term.
182#[must_use]
183pub fn desc(expr: impl Into<OrderExpr>) -> OrderTerm {
184    OrderTerm::desc(expr)
185}