drizzle_core/expr/mod.rs
1//! Type-safe SQL expression system.
2//!
3//! This module provides a type-safe wrapper around SQL expressions that tracks:
4//! - The SQL data type of the expression
5//! - Whether the expression can be NULL
6//! - Whether the expression is an aggregate or scalar
7//!
8//! # Example
9//!
10//! ```rust
11//! # let _ = r####"
12//! use drizzle_core::expr::*;
13//!
14//! // Type-safe comparisons
15//! let condition = eq(users.id, 10); // OK: Int == Int
16//! // let bad = eq(users.id, "hello"); // ERROR: Int != Text
17//!
18//! // Type-safe arithmetic
19//! let total = users.price + users.tax; // OK: both Numeric
20//! // let bad = users.name + users.id; // ERROR: Text + Int
21//! # "####;
22//! ```
23
24mod agg;
25mod case;
26mod cmp;
27mod column_ops;
28mod datetime;
29mod logical;
30mod math;
31mod null;
32mod ops;
33mod primitives;
34mod seq;
35mod set;
36mod string;
37mod subquery;
38mod typed;
39mod util;
40mod window;
41
42pub use agg::*;
43pub use case::*;
44pub use cmp::*;
45#[doc(hidden)]
46pub use column_ops::*;
47pub use datetime::*;
48pub use logical::*;
49pub use math::*;
50pub use null::*;
51pub use seq::*;
52pub use set::*;
53pub use string::*;
54pub use subquery::*;
55// ops has only trait impls - no items to re-export
56pub use typed::*;
57pub use util::*;
58pub use window::*;
59
60use crate::traits::{SQLParam, ToSQL};
61use crate::types::DataType;
62
63// =============================================================================
64// Sealed Trait Pattern
65// =============================================================================
66
67mod private {
68 pub trait Sealed {}
69}
70
71// =============================================================================
72// Nullability Markers
73// =============================================================================
74
75/// Marker trait for nullability state.
76#[diagnostic::on_unimplemented(
77 message = "`{Self}` is not a valid nullability marker",
78 label = "expected `NonNull` or `Null`"
79)]
80pub trait Nullability: private::Sealed + Copy + Default + 'static {}
81
82/// Marker indicating an expression cannot be NULL.
83#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
84pub struct NonNull;
85
86/// Marker indicating an expression can be NULL.
87#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
88pub struct Null;
89
90impl private::Sealed for NonNull {}
91impl private::Sealed for Null {}
92impl Nullability for NonNull {}
93impl Nullability for Null {}
94
95// =============================================================================
96// Aggregate Kind Markers
97// =============================================================================
98
99/// Marker trait for expression aggregation state.
100#[diagnostic::on_unimplemented(
101 message = "`{Self}` is not a valid aggregate marker",
102 label = "expected `Scalar` or `Agg`"
103)]
104pub trait AggregateKind: private::Sealed + Copy + Default + 'static {}
105
106/// Marker indicating a scalar (non-aggregate) expression.
107#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
108pub struct Scalar;
109
110/// Marker indicating an aggregate expression (COUNT, SUM, etc.).
111#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
112pub struct Agg;
113
114impl private::Sealed for Scalar {}
115impl private::Sealed for Agg {}
116impl AggregateKind for Scalar {}
117impl AggregateKind for Agg {}
118
119/// Combine aggregate kinds: if either input is Agg, output is Agg.
120///
121/// This follows SQL's aggregate propagation semantics: an expression
122/// derived from any aggregate sub-expression is itself aggregate
123/// (e.g. `SUM(x) + 5` is aggregate, not scalar).
124///
125/// # Truth Table
126///
127/// | Left | Right | Output |
128/// |------|-------|--------|
129/// | Scalar | Scalar | Scalar |
130/// | Scalar | Agg | Agg |
131/// | Agg | Scalar | Agg |
132/// | Agg | Agg | Agg |
133pub trait AggOr<Rhs: AggregateKind>: AggregateKind {
134 /// The resulting aggregate kind.
135 type Output: AggregateKind;
136}
137
138impl AggOr<Self> for Scalar {
139 type Output = Self;
140}
141impl AggOr<Agg> for Scalar {
142 type Output = Agg;
143}
144impl AggOr<Scalar> for Agg {
145 type Output = Self;
146}
147impl AggOr<Self> for Agg {
148 type Output = Self;
149}
150
151// =============================================================================
152// Aggregate Status (for SELECT list validation)
153// =============================================================================
154
155/// Status indicating all selected expressions are scalar (non-aggregate).
156#[derive(Debug, Clone, Copy, Default)]
157pub struct AllScalar;
158
159/// Status indicating all selected expressions are aggregate.
160#[derive(Debug, Clone, Copy, Default)]
161pub struct AllAgg;
162
163/// Status indicating a mix of scalar and aggregate expressions.
164#[derive(Debug, Clone, Copy, Default)]
165pub struct MixedAgg;
166
167/// Convert an `AggregateKind` to an initial `AggStatus`.
168pub trait AggToStatus: AggregateKind {
169 type Status;
170}
171
172impl AggToStatus for Scalar {
173 type Status = AllScalar;
174}
175
176impl AggToStatus for Agg {
177 type Status = AllAgg;
178}
179
180/// Combine two aggregate statuses.
181///
182/// | Left | Right | Output |
183/// |------|-------|--------|
184/// | AllScalar | AllScalar | AllScalar |
185/// | AllAgg | AllAgg | AllAgg |
186/// | anything else | _ | MixedAgg |
187pub trait CombineAggStatus<Rhs> {
188 type Output;
189}
190
191impl CombineAggStatus<Self> for AllScalar {
192 type Output = Self;
193}
194impl CombineAggStatus<AllAgg> for AllScalar {
195 type Output = MixedAgg;
196}
197impl CombineAggStatus<MixedAgg> for AllScalar {
198 type Output = MixedAgg;
199}
200impl CombineAggStatus<AllScalar> for AllAgg {
201 type Output = MixedAgg;
202}
203impl CombineAggStatus<Self> for AllAgg {
204 type Output = Self;
205}
206impl CombineAggStatus<MixedAgg> for AllAgg {
207 type Output = MixedAgg;
208}
209impl CombineAggStatus<AllScalar> for MixedAgg {
210 type Output = Self;
211}
212impl CombineAggStatus<AllAgg> for MixedAgg {
213 type Output = Self;
214}
215impl CombineAggStatus<Self> for MixedAgg {
216 type Output = Self;
217}
218
219/// Extract the aggregate status of a type that appears in a SELECT list.
220///
221/// Implemented for column ZSTs (always Scalar), `SQLExpr`, and expression wrappers.
222pub trait HasAggStatus {
223 type Status;
224}
225
226// =============================================================================
227// Core Expression Trait
228// =============================================================================
229
230/// An expression in SQL with an associated data type.
231///
232/// This is the core trait for type-safe SQL expressions. Every SQL expression
233/// (column, literal, function result) implements this with its SQL type.
234///
235/// # Type Parameters
236///
237/// - `'a`: Lifetime of borrowed data in the expression
238/// - `V`: The dialect's value type (`SQLiteValue`, `PostgresValue`)
239///
240/// # Associated Types
241///
242/// - `SQLType`: The SQL data type this expression evaluates to
243/// - `Nullable`: Whether this expression can be NULL
244/// - `Aggregate`: Whether this is an aggregate or scalar expression
245///
246/// # Example
247///
248/// ```rust
249/// # let _ = r####"
250/// use drizzle_core::expr::{Expr, NonNull, Scalar};
251/// use drizzle_core::types::Int;
252///
253/// // i32 literals are Int, NonNull, Scalar
254/// fn check_expr<'a, V, E: Expr<'a, V>>() {}
255/// check_expr::<_, i32>(); // SQLType=Int, Nullable=NonNull, Aggregate=Scalar
256/// # "####;
257/// ```
258#[diagnostic::on_unimplemented(
259 message = "`{Self}` is not a valid SQL expression",
260 label = "expected a column, literal, or expression — does this type implement Expr?",
261 note = "SQL expressions must have an associated SQLType, Nullable, and Aggregate kind"
262)]
263pub trait Expr<'a, V: SQLParam>: ToSQL<'a, V> {
264 /// The SQL data type this expression evaluates to.
265 type SQLType: DataType;
266
267 /// Whether this expression can be NULL.
268 type Nullable: Nullability;
269
270 /// Whether this is an aggregate (COUNT, SUM) or scalar expression.
271 type Aggregate: AggregateKind;
272
273 /// Render this value as a scalar expression by reference.
274 ///
275 /// Most expressions use their `ToSQL` implementation. A few Rust container
276 /// types, notably byte buffers, need expression-specific rendering because
277 /// their generic `ToSQL` form is a comma-separated list.
278 fn to_expr_sql(&self) -> crate::SQL<'a, V> {
279 self.to_sql().parens_if_subquery()
280 }
281
282 /// Render this value as a scalar expression, consuming it when useful.
283 fn into_expr_sql(self) -> crate::SQL<'a, V>
284 where
285 Self: Sized,
286 {
287 self.into_sql().parens_if_subquery()
288 }
289}
290
291// Note: Columns implement Expr via explicit impls generated by macros,
292// not via a blanket impl, to avoid conflicts with `impl Expr for &T`.