drizzle-core 0.1.12

A type-safe SQL query builder for Rust
Documentation
//! Type-safe SQL expression system.
//!
//! This module provides a type-safe wrapper around SQL expressions that tracks:
//! - The SQL data type of the expression
//! - Whether the expression can be NULL
//! - Whether the expression is an aggregate or scalar
//!
//! # Example
//!
//! ```rust
//! # let _ = r####"
//! use drizzle_core::expr::*;
//!
//! // Type-safe comparisons
//! let condition = eq(users.id, 10);  // OK: Int == Int
//! // let bad = eq(users.id, "hello"); // ERROR: Int != Text
//!
//! // Type-safe arithmetic
//! let total = users.price + users.tax;  // OK: both Numeric
//! // let bad = users.name + users.id;   // ERROR: Text + Int
//! # "####;
//! ```

mod agg;
mod case;
mod cmp;
mod column_ops;
mod datetime;
mod logical;
mod math;
mod null;
mod ops;
mod primitives;
mod seq;
mod set;
mod string;
mod subquery;
mod typed;
mod util;
mod window;

pub use agg::*;
pub use case::*;
pub use cmp::*;
#[doc(hidden)]
pub use column_ops::*;
pub use datetime::*;
pub use logical::*;
pub use math::*;
pub use null::*;
pub use seq::*;
pub use set::*;
pub use string::*;
pub use subquery::*;
// ops has only trait impls - no items to re-export
pub use typed::*;
pub use util::*;
pub use window::*;

use crate::traits::{SQLParam, ToSQL};
use crate::types::DataType;

// =============================================================================
// Sealed Trait Pattern
// =============================================================================

mod private {
    pub trait Sealed {}
}

// =============================================================================
// Nullability Markers
// =============================================================================

/// Marker trait for nullability state.
#[diagnostic::on_unimplemented(
    message = "`{Self}` is not a valid nullability marker",
    label = "expected `NonNull` or `Null`"
)]
pub trait Nullability: private::Sealed + Copy + Default + 'static {}

/// Marker indicating an expression cannot be NULL.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct NonNull;

/// Marker indicating an expression can be NULL.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Null;

impl private::Sealed for NonNull {}
impl private::Sealed for Null {}
impl Nullability for NonNull {}
impl Nullability for Null {}

// =============================================================================
// Aggregate Kind Markers
// =============================================================================

/// Marker trait for expression aggregation state.
#[diagnostic::on_unimplemented(
    message = "`{Self}` is not a valid aggregate marker",
    label = "expected `Scalar` or `Agg`"
)]
pub trait AggregateKind: private::Sealed + Copy + Default + 'static {}

/// Marker indicating a scalar (non-aggregate) expression.
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Scalar;

/// Marker indicating an aggregate expression (COUNT, SUM, etc.).
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
pub struct Agg;

impl private::Sealed for Scalar {}
impl private::Sealed for Agg {}
impl AggregateKind for Scalar {}
impl AggregateKind for Agg {}

/// Combine aggregate kinds: if either input is Agg, output is Agg.
///
/// This follows SQL's aggregate propagation semantics: an expression
/// derived from any aggregate sub-expression is itself aggregate
/// (e.g. `SUM(x) + 5` is aggregate, not scalar).
///
/// # Truth Table
///
/// | Left | Right | Output |
/// |------|-------|--------|
/// | Scalar | Scalar | Scalar |
/// | Scalar | Agg | Agg |
/// | Agg | Scalar | Agg |
/// | Agg | Agg | Agg |
pub trait AggOr<Rhs: AggregateKind>: AggregateKind {
    /// The resulting aggregate kind.
    type Output: AggregateKind;
}

impl AggOr<Self> for Scalar {
    type Output = Self;
}
impl AggOr<Agg> for Scalar {
    type Output = Agg;
}
impl AggOr<Scalar> for Agg {
    type Output = Self;
}
impl AggOr<Self> for Agg {
    type Output = Self;
}

// =============================================================================
// Aggregate Status (for SELECT list validation)
// =============================================================================

/// Status indicating all selected expressions are scalar (non-aggregate).
#[derive(Debug, Clone, Copy, Default)]
pub struct AllScalar;

/// Status indicating all selected expressions are aggregate.
#[derive(Debug, Clone, Copy, Default)]
pub struct AllAgg;

/// Status indicating a mix of scalar and aggregate expressions.
#[derive(Debug, Clone, Copy, Default)]
pub struct MixedAgg;

/// Convert an `AggregateKind` to an initial `AggStatus`.
pub trait AggToStatus: AggregateKind {
    type Status;
}

impl AggToStatus for Scalar {
    type Status = AllScalar;
}

impl AggToStatus for Agg {
    type Status = AllAgg;
}

/// Combine two aggregate statuses.
///
/// | Left | Right | Output |
/// |------|-------|--------|
/// | AllScalar | AllScalar | AllScalar |
/// | AllAgg | AllAgg | AllAgg |
/// | anything else | _ | MixedAgg |
pub trait CombineAggStatus<Rhs> {
    type Output;
}

impl CombineAggStatus<Self> for AllScalar {
    type Output = Self;
}
impl CombineAggStatus<AllAgg> for AllScalar {
    type Output = MixedAgg;
}
impl CombineAggStatus<MixedAgg> for AllScalar {
    type Output = MixedAgg;
}
impl CombineAggStatus<AllScalar> for AllAgg {
    type Output = MixedAgg;
}
impl CombineAggStatus<Self> for AllAgg {
    type Output = Self;
}
impl CombineAggStatus<MixedAgg> for AllAgg {
    type Output = MixedAgg;
}
impl CombineAggStatus<AllScalar> for MixedAgg {
    type Output = Self;
}
impl CombineAggStatus<AllAgg> for MixedAgg {
    type Output = Self;
}
impl CombineAggStatus<Self> for MixedAgg {
    type Output = Self;
}

/// Extract the aggregate status of a type that appears in a SELECT list.
///
/// Implemented for column ZSTs (always Scalar), `SQLExpr`, and expression wrappers.
pub trait HasAggStatus {
    type Status;
}

// =============================================================================
// Core Expression Trait
// =============================================================================

/// An expression in SQL with an associated data type.
///
/// This is the core trait for type-safe SQL expressions. Every SQL expression
/// (column, literal, function result) implements this with its SQL type.
///
/// # Type Parameters
///
/// - `'a`: Lifetime of borrowed data in the expression
/// - `V`: The dialect's value type (`SQLiteValue`, `PostgresValue`)
///
/// # Associated Types
///
/// - `SQLType`: The SQL data type this expression evaluates to
/// - `Nullable`: Whether this expression can be NULL
/// - `Aggregate`: Whether this is an aggregate or scalar expression
///
/// # Example
///
/// ```rust
/// # let _ = r####"
/// use drizzle_core::expr::{Expr, NonNull, Scalar};
/// use drizzle_core::types::Int;
///
/// // i32 literals are Int, NonNull, Scalar
/// fn check_expr<'a, V, E: Expr<'a, V>>() {}
/// check_expr::<_, i32>(); // SQLType=Int, Nullable=NonNull, Aggregate=Scalar
/// # "####;
/// ```
#[diagnostic::on_unimplemented(
    message = "`{Self}` is not a valid SQL expression",
    label = "expected a column, literal, or expression — does this type implement Expr?",
    note = "SQL expressions must have an associated SQLType, Nullable, and Aggregate kind"
)]
pub trait Expr<'a, V: SQLParam>: ToSQL<'a, V> {
    /// The SQL data type this expression evaluates to.
    type SQLType: DataType;

    /// Whether this expression can be NULL.
    type Nullable: Nullability;

    /// Whether this is an aggregate (COUNT, SUM) or scalar expression.
    type Aggregate: AggregateKind;

    /// Render this value as a scalar expression by reference.
    ///
    /// Most expressions use their `ToSQL` implementation. A few Rust container
    /// types, notably byte buffers, need expression-specific rendering because
    /// their generic `ToSQL` form is a comma-separated list.
    fn to_expr_sql(&self) -> crate::SQL<'a, V> {
        self.to_sql().parens_if_subquery()
    }

    /// Render this value as a scalar expression, consuming it when useful.
    fn into_expr_sql(self) -> crate::SQL<'a, V>
    where
        Self: Sized,
    {
        self.into_sql().parens_if_subquery()
    }
}

// Note: Columns implement Expr via explicit impls generated by macros,
// not via a blanket impl, to avoid conflicts with `impl Expr for &T`.