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