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/// Compile-time relation between a column's nullability and an assigned value.
98///
99/// Non-null expressions can be assigned to every column. Nullable expressions
100/// can only be assigned to nullable columns.
101#[doc(hidden)]
102#[diagnostic::on_unimplemented(
103 message = "a nullable expression cannot be assigned to a non-null column",
104 label = "this assignment could produce NULL",
105 note = "handle the NULL case in the expression or make the target column nullable"
106)]
107pub trait AcceptsNullability<Source: Nullability>: Nullability {}
108
109impl AcceptsNullability<NonNull> for NonNull {}
110impl AcceptsNullability<NonNull> for Null {}
111impl AcceptsNullability<Null> for Null {}
112
113// =============================================================================
114// Aggregate Kind Markers
115// =============================================================================
116
117/// Marker trait for expression aggregation state.
118#[diagnostic::on_unimplemented(
119 message = "`{Self}` is not a valid aggregate marker",
120 label = "expected `Scalar` or `Agg`"
121)]
122pub trait AggregateKind: private::Sealed + Copy + Default + 'static {}
123
124/// Marker indicating a scalar (non-aggregate) expression.
125#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
126pub struct Scalar;
127
128/// Marker indicating an aggregate expression (COUNT, SUM, etc.).
129#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
130pub struct Agg;
131
132impl private::Sealed for Scalar {}
133impl private::Sealed for Agg {}
134impl AggregateKind for Scalar {}
135impl AggregateKind for Agg {}
136
137/// Combine aggregate kinds: if either input is Agg, output is Agg.
138///
139/// This follows SQL's aggregate propagation semantics: an expression
140/// derived from any aggregate sub-expression is itself aggregate
141/// (e.g. `SUM(x) + 5` is aggregate, not scalar).
142///
143/// # Truth Table
144///
145/// | Left | Right | Output |
146/// |------|-------|--------|
147/// | Scalar | Scalar | Scalar |
148/// | Scalar | Agg | Agg |
149/// | Agg | Scalar | Agg |
150/// | Agg | Agg | Agg |
151pub trait AggOr<Rhs: AggregateKind>: AggregateKind {
152 /// The resulting aggregate kind.
153 type Output: AggregateKind;
154}
155
156impl AggOr<Self> for Scalar {
157 type Output = Self;
158}
159impl AggOr<Agg> for Scalar {
160 type Output = Agg;
161}
162impl AggOr<Scalar> for Agg {
163 type Output = Self;
164}
165impl AggOr<Self> for Agg {
166 type Output = Self;
167}
168
169// =============================================================================
170// Aggregate Status (for SELECT list validation)
171// =============================================================================
172
173/// Status indicating all selected expressions are scalar (non-aggregate).
174#[derive(Debug, Clone, Copy, Default)]
175pub struct AllScalar;
176
177/// Status indicating all selected expressions are aggregate.
178#[derive(Debug, Clone, Copy, Default)]
179pub struct AllAgg;
180
181/// Status indicating a mix of scalar and aggregate expressions.
182#[derive(Debug, Clone, Copy, Default)]
183pub struct MixedAgg;
184
185/// Convert an `AggregateKind` to an initial `AggStatus`.
186pub trait AggToStatus: AggregateKind {
187 type Status;
188}
189
190impl AggToStatus for Scalar {
191 type Status = AllScalar;
192}
193
194impl AggToStatus for Agg {
195 type Status = AllAgg;
196}
197
198/// Combine two aggregate statuses.
199///
200/// | Left | Right | Output |
201/// |------|-------|--------|
202/// | AllScalar | AllScalar | AllScalar |
203/// | AllAgg | AllAgg | AllAgg |
204/// | anything else | _ | MixedAgg |
205pub trait CombineAggStatus<Rhs> {
206 type Output;
207}
208
209impl CombineAggStatus<Self> for AllScalar {
210 type Output = Self;
211}
212impl CombineAggStatus<AllAgg> for AllScalar {
213 type Output = MixedAgg;
214}
215impl CombineAggStatus<MixedAgg> for AllScalar {
216 type Output = MixedAgg;
217}
218impl CombineAggStatus<AllScalar> for AllAgg {
219 type Output = MixedAgg;
220}
221impl CombineAggStatus<Self> for AllAgg {
222 type Output = Self;
223}
224impl CombineAggStatus<MixedAgg> for AllAgg {
225 type Output = MixedAgg;
226}
227impl CombineAggStatus<AllScalar> for MixedAgg {
228 type Output = Self;
229}
230impl CombineAggStatus<AllAgg> for MixedAgg {
231 type Output = Self;
232}
233impl CombineAggStatus<Self> for MixedAgg {
234 type Output = Self;
235}
236
237/// Extract the aggregate status of a type that appears in a SELECT list.
238///
239/// Implemented for column ZSTs (always Scalar), `SQLExpr`, and expression wrappers.
240pub trait HasAggStatus {
241 type Status;
242}
243
244impl<T: HasAggStatus + ?Sized> HasAggStatus for &T {
245 type Status = T::Status;
246}
247
248// =============================================================================
249// Core Expression Trait
250// =============================================================================
251
252/// An expression in SQL with an associated data type.
253///
254/// This is the core trait for type-safe SQL expressions. Every SQL expression
255/// (column, literal, function result) implements this with its SQL type.
256///
257/// # Type Parameters
258///
259/// - `'a`: Lifetime of borrowed data in the expression
260/// - `V`: The dialect's value type (`SQLiteValue`, `PostgresValue`)
261///
262/// # Associated Types
263///
264/// - `SQLType`: The SQL data type this expression evaluates to
265/// - `Nullable`: Whether this expression can be NULL
266/// - `Aggregate`: Whether this is an aggregate or scalar expression
267///
268/// # Example
269///
270/// ```rust
271/// # let _ = r####"
272/// use drizzle_core::expr::{Expr, NonNull, Scalar};
273/// use drizzle_core::types::Int;
274///
275/// // i32 literals are Int, NonNull, Scalar
276/// fn check_expr<'a, V, E: Expr<'a, V>>() {}
277/// check_expr::<_, i32>(); // SQLType=Int, Nullable=NonNull, Aggregate=Scalar
278/// # "####;
279/// ```
280#[diagnostic::on_unimplemented(
281 message = "`{Self}` is not a valid SQL expression",
282 label = "expected a column, literal, or expression — does this type implement Expr?",
283 note = "SQL expressions must have an associated SQLType, Nullable, and Aggregate kind"
284)]
285pub trait Expr<'a, V: SQLParam>: ToSQL<'a, V> {
286 /// The SQL data type this expression evaluates to.
287 type SQLType: DataType;
288
289 /// Whether this expression can be NULL.
290 type Nullable: Nullability;
291
292 /// Whether this is an aggregate (COUNT, SUM) or scalar expression.
293 type Aggregate: AggregateKind;
294
295 /// Render this value as a scalar expression by reference.
296 ///
297 /// Most expressions use their `ToSQL` implementation. A few Rust container
298 /// types, notably byte buffers, need expression-specific rendering because
299 /// their generic `ToSQL` form is a comma-separated list.
300 fn to_expr_sql(&self) -> crate::SQL<'a, V> {
301 self.to_sql().parens_if_subquery()
302 }
303
304 /// Render this value as a scalar expression, consuming it when useful.
305 fn into_expr_sql(self) -> crate::SQL<'a, V>
306 where
307 Self: Sized,
308 {
309 self.into_sql().parens_if_subquery()
310 }
311
312 /// Render this value as one element of a [`ConditionList`].
313 ///
314 /// `None` means the element contributes no condition and is dropped from
315 /// the combined SQL. Only `Option::None` does this; every other expression
316 /// renders through [`Expr::to_expr_sql`].
317 fn to_condition_sql(&self) -> Option<crate::SQL<'a, V>> {
318 Some(self.to_expr_sql())
319 }
320
321 /// Consuming counterpart of [`Expr::to_condition_sql`].
322 fn into_condition_sql(self) -> Option<crate::SQL<'a, V>>
323 where
324 Self: Sized,
325 {
326 Some(self.into_expr_sql())
327 }
328}
329
330// Note: Columns implement Expr via explicit impls generated by macros,
331// not via a blanket impl, to avoid conflicts with `impl Expr for &T`.