Skip to main content

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`.