Skip to main content

icydb_core/db/query/expr/
order.rs

1//! Module: db::query::expr::order
2//! Responsibility: typed fluent ORDER BY expression DTOs and lowering.
3//! Does not own: planner validation or execution-time order evaluation.
4//! Boundary: converts fluent order inputs into planner-owned order terms.
5
6use crate::db::{
7    QueryError,
8    query::{
9        builder::{
10            AggregateExpr, FieldRef, NumericProjectionExpr, RoundProjectionExpr, TextProjectionExpr,
11        },
12        plan::{
13            OrderDirection, OrderTerm as PlannedOrderTerm,
14            expr::{Expr, FieldId},
15        },
16        preparation::PreparationWork,
17    },
18};
19
20///
21/// OrderExpr
22///
23/// Typed fluent ORDER BY expression wrapper.
24/// This exists so fluent code can construct planner-owned ORDER BY
25/// semantics directly at the query boundary.
26///
27
28#[derive(Clone, Debug, Eq, PartialEq)]
29pub struct OrderExpr {
30    expr: Expr,
31}
32
33impl OrderExpr {
34    /// Build one direct field ORDER BY expression.
35    #[must_use]
36    pub fn field(field: impl Into<String>) -> Self {
37        let field = field.into();
38
39        Self {
40            expr: Expr::Field(FieldId::new(field)),
41        }
42    }
43
44    /// Freeze one typed fluent order expression onto the planner-owned
45    /// semantic expression now that labels are derived only at explain/hash
46    /// edges instead of being stored in fluent order shells.
47    const fn new(expr: Expr) -> Self {
48        Self { expr }
49    }
50}
51
52impl From<&str> for OrderExpr {
53    fn from(value: &str) -> Self {
54        Self::field(value)
55    }
56}
57
58impl From<String> for OrderExpr {
59    fn from(value: String) -> Self {
60        Self::field(value)
61    }
62}
63
64impl From<FieldRef> for OrderExpr {
65    fn from(value: FieldRef) -> Self {
66        Self::field(value.as_str())
67    }
68}
69
70impl From<TextProjectionExpr> for OrderExpr {
71    fn from(value: TextProjectionExpr) -> Self {
72        Self::new(value.expr().clone())
73    }
74}
75
76impl From<NumericProjectionExpr> for OrderExpr {
77    fn from(value: NumericProjectionExpr) -> Self {
78        Self::new(value.expr().clone())
79    }
80}
81
82impl From<RoundProjectionExpr> for OrderExpr {
83    fn from(value: RoundProjectionExpr) -> Self {
84        Self::new(value.expr().clone())
85    }
86}
87
88impl From<AggregateExpr> for OrderExpr {
89    fn from(value: AggregateExpr) -> Self {
90        Self::new(Expr::Aggregate(value))
91    }
92}
93
94///
95/// OrderTerm
96///
97/// Typed fluent ORDER BY term.
98/// Carries one typed ORDER BY expression plus direction so fluent builders can
99/// express deterministic ordering directly at the query boundary.
100///
101
102#[derive(Clone, Debug, Eq, PartialEq)]
103pub struct OrderTerm {
104    expr: OrderExpr,
105    direction: OrderDirection,
106}
107
108impl OrderTerm {
109    /// Copy admitted ordering syntax without changing direction or expression shape.
110    pub(in crate::db) fn copy_for_preparation(
111        &self,
112        work: &PreparationWork<'_>,
113    ) -> Result<Self, QueryError> {
114        Ok(Self {
115            expr: OrderExpr::new(work.copy_expr(self.expression())?),
116            direction: self.direction,
117        })
118    }
119
120    /// Borrow authored syntax for admission before request preparation copies it.
121    pub(in crate::db) const fn expression(&self) -> &Expr {
122        &self.expr.expr
123    }
124
125    /// Build one ascending ORDER BY term from one typed expression.
126    #[must_use]
127    pub fn asc(expr: impl Into<OrderExpr>) -> Self {
128        Self {
129            expr: expr.into(),
130            direction: OrderDirection::Asc,
131        }
132    }
133
134    /// Build one descending ORDER BY term from one typed expression.
135    #[must_use]
136    pub fn desc(expr: impl Into<OrderExpr>) -> Self {
137        Self {
138            expr: expr.into(),
139            direction: OrderDirection::Desc,
140        }
141    }
142
143    /// Lower one typed fluent order term directly into the planner-owned
144    /// `OrderTerm` contract.
145    pub(in crate::db) fn lower(self) -> PlannedOrderTerm {
146        PlannedOrderTerm::new(self.expr.expr, self.direction)
147    }
148}
149
150/// Build one typed direct-field ORDER BY expression.
151#[must_use]
152pub fn field(field: impl Into<String>) -> OrderExpr {
153    OrderExpr::field(field)
154}
155
156/// Build one ascending typed ORDER BY term.
157#[must_use]
158pub fn asc(expr: impl Into<OrderExpr>) -> OrderTerm {
159    OrderTerm::asc(expr)
160}
161
162/// Build one descending typed ORDER BY term.
163#[must_use]
164pub fn desc(expr: impl Into<OrderExpr>) -> OrderTerm {
165    OrderTerm::desc(expr)
166}
167
168#[cfg(test)]
169mod tests {
170    use super::{OrderExpr, OrderTerm};
171    use crate::{
172        db::query::{
173            builder::sum,
174            plan::{OrderDirection, expr::Expr},
175        },
176        value::Value,
177    };
178
179    #[test]
180    fn owned_order_lowering_preserves_operands_and_direction() {
181        for direction in [OrderDirection::Asc, OrderDirection::Desc] {
182            let aggregate = sum("amount")
183                .with_filter_expr(Expr::Literal(Value::Text("x".repeat(4096))))
184                .distinct();
185            let input_address = std::ptr::from_ref(aggregate.input_expr().expect("input"));
186            let filter_address = std::ptr::from_ref(aggregate.filter_expr().expect("filter"));
187            let expected = Expr::Aggregate(aggregate.clone());
188            let term = OrderTerm {
189                expr: OrderExpr::from(aggregate),
190                direction,
191            };
192
193            let lowered = term.lower();
194            assert_eq!(lowered.direction(), direction);
195            assert_eq!(lowered.expr(), &expected);
196            let Expr::Aggregate(aggregate) = lowered.expr() else {
197                panic!("aggregate order expression");
198            };
199            assert_eq!(
200                std::ptr::from_ref(aggregate.input_expr().expect("input")),
201                input_address
202            );
203            assert_eq!(
204                std::ptr::from_ref(aggregate.filter_expr().expect("filter")),
205                filter_address
206            );
207        }
208    }
209}