Skip to main content

drizzle_core/expr/
column_ops.rs

1//! Internal wrapper types for column arithmetic operations.
2//!
3//! These types are implementation details that allow `column + 5` syntax
4//! to work seamlessly. Users don't interact with these directly.
5
6use core::marker::PhantomData;
7
8use crate::ValueTypeForDialect;
9use crate::sql::{SQL, Token};
10use crate::traits::{SQLParam, ToSQL};
11use crate::types::{AlwaysNullable, ArithmeticOutput, NegOutput, Numeric, PropagateNullability};
12
13use super::{AggOr, AggregateKind, Expr, NonNull, NullOr, Nullability};
14
15/// Binary operation result for column arithmetic.
16///
17/// This is an implementation detail - users see `column + 5` and it "just works".
18#[derive(Debug, Clone, Copy)]
19pub struct ColumnBinOp<Lhs, Rhs, Op, D, SQLType, Nullable> {
20    lhs: Lhs,
21    rhs: Rhs,
22    _type: PhantomData<(Op, D, SQLType, Nullable)>,
23}
24
25impl<Lhs, Rhs, Op, D, SQLType, Nullable> ColumnBinOp<Lhs, Rhs, Op, D, SQLType, Nullable> {
26    #[inline]
27    pub const fn new(lhs: Lhs, rhs: Rhs) -> Self {
28        Self {
29            lhs,
30            rhs,
31            _type: PhantomData,
32        }
33    }
34}
35
36#[doc(hidden)]
37pub use crate::types::{
38    AddOp as OpAdd, DivOp as OpDiv, MulOp as OpMul, RemOp as OpRem, SubOp as OpSub,
39};
40
41/// Trait to get the token for an operation
42pub trait BinOpToken {
43    const TOKEN: Token;
44}
45
46#[doc(hidden)]
47pub trait ResolveArithmeticNullability<Lhs, Rhs> {
48    type Output: Nullability;
49}
50
51/// Lifetime-independent type metadata for the right-hand side of generated
52/// column arithmetic operators.
53#[doc(hidden)]
54pub trait ArithmeticRhs<D> {
55    type SQLType: Numeric;
56    type Nullable: Nullability;
57}
58
59/// Computes and constructs the result of a generated column arithmetic
60/// operator without tying right-hand-side metadata to an expression lifetime.
61#[doc(hidden)]
62pub trait BuildColumnArithmetic<Lhs, Rhs, Op, D, LhsSQLType, LhsNullable> {
63    type Output;
64
65    fn build(lhs: Lhs, rhs: Rhs) -> Self::Output;
66}
67
68impl<Lhs, Rhs, Op, D, LhsSQLType, LhsNullable>
69    BuildColumnArithmetic<Lhs, Rhs, Op, D, LhsSQLType, LhsNullable> for ()
70where
71    Rhs: ArithmeticRhs<D>,
72    LhsSQLType: ArithmeticOutput<Rhs::SQLType, Op>,
73    <LhsSQLType as ArithmeticOutput<Rhs::SQLType, Op>>::Nullability:
74        ResolveArithmeticNullability<LhsNullable, Rhs::Nullable>,
75{
76    type Output = ColumnBinOp<
77        Lhs,
78        Rhs,
79        Op,
80        D,
81        <LhsSQLType as ArithmeticOutput<Rhs::SQLType, Op>>::Output,
82        <<LhsSQLType as ArithmeticOutput<Rhs::SQLType, Op>>::Nullability as ResolveArithmeticNullability<
83            LhsNullable,
84            Rhs::Nullable,
85        >>::Output,
86    >;
87
88    fn build(lhs: Lhs, rhs: Rhs) -> Self::Output {
89        ColumnBinOp::new(lhs, rhs)
90    }
91}
92
93macro_rules! arithmetic_value_rhs {
94    ($dialect:ty; $($value:ty),+ $(,)?) => {
95        $(
96            impl ArithmeticRhs<$dialect> for $value
97            where
98                <$value as ValueTypeForDialect<$dialect>>::SQLType: Numeric,
99            {
100                type SQLType = <$value as ValueTypeForDialect<$dialect>>::SQLType;
101                type Nullable = NonNull;
102            }
103        )+
104    };
105}
106
107arithmetic_value_rhs!(
108    crate::SQLiteDialect;
109    i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, bool, f32, f64
110);
111arithmetic_value_rhs!(
112    crate::PostgresDialect;
113    i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64
114);
115arithmetic_value_rhs!(
116    crate::MySQLDialect;
117    i8, i16, i32, i64, isize, u8, u16, u32, u64, usize, f32, f64
118);
119
120#[cfg(feature = "rust-decimal")]
121arithmetic_value_rhs!(crate::PostgresDialect; rust_decimal::Decimal);
122#[cfg(feature = "rust-decimal")]
123arithmetic_value_rhs!(crate::MySQLDialect; rust_decimal::Decimal);
124
125impl<D, T> ArithmeticRhs<D> for Option<T>
126where
127    T: ValueTypeForDialect<D>,
128    T::SQLType: Numeric,
129{
130    type SQLType = T::SQLType;
131    type Nullable = super::Null;
132}
133
134impl<D, T> ArithmeticRhs<D> for &T
135where
136    T: ArithmeticRhs<D> + ?Sized,
137{
138    type SQLType = T::SQLType;
139    type Nullable = T::Nullable;
140}
141
142impl<D, V, T, N, A> ArithmeticRhs<D> for super::SQLExpr<'_, V, T, N, A>
143where
144    V: SQLParam<DialectMarker = D>,
145    T: Numeric,
146    N: Nullability,
147    A: AggregateKind,
148{
149    type SQLType = T;
150    type Nullable = N;
151}
152
153impl<Lhs, Rhs> ResolveArithmeticNullability<Lhs, Rhs> for PropagateNullability
154where
155    Lhs: Nullability + NullOr<Rhs>,
156    Rhs: Nullability,
157{
158    type Output = <Lhs as NullOr<Rhs>>::Output;
159}
160
161impl<Lhs: Nullability, Rhs: Nullability> ResolveArithmeticNullability<Lhs, Rhs> for AlwaysNullable {
162    type Output = super::Null;
163}
164
165impl BinOpToken for OpAdd {
166    const TOKEN: Token = Token::PLUS;
167}
168
169impl BinOpToken for OpSub {
170    const TOKEN: Token = Token::MINUS;
171}
172
173impl BinOpToken for OpMul {
174    const TOKEN: Token = Token::STAR;
175}
176
177impl BinOpToken for OpDiv {
178    const TOKEN: Token = Token::SLASH;
179}
180
181impl BinOpToken for OpRem {
182    const TOKEN: Token = Token::REM;
183}
184
185impl<'a, V, Lhs, Rhs, Op, D, SQLType, Nullable> ToSQL<'a, V>
186    for ColumnBinOp<Lhs, Rhs, Op, D, SQLType, Nullable>
187where
188    V: SQLParam,
189    Lhs: ToSQL<'a, V>,
190    Rhs: ToSQL<'a, V>,
191    Op: BinOpToken,
192{
193    fn to_sql(&self) -> SQL<'a, V> {
194        super::ops::binary_operator_sql(self.lhs.to_sql(), Op::TOKEN, self.rhs.to_sql())
195    }
196
197    fn into_sql(self) -> SQL<'a, V> {
198        super::ops::binary_operator_sql(self.lhs.into_sql(), Op::TOKEN, self.rhs.into_sql())
199    }
200}
201
202impl<'a, V, Lhs, Rhs, Op, D, SQLType, Nullable> Expr<'a, V>
203    for ColumnBinOp<Lhs, Rhs, Op, D, SQLType, Nullable>
204where
205    V: SQLParam<DialectMarker = D>,
206    Lhs: Expr<'a, V>,
207    Rhs: Expr<'a, V>,
208    Lhs::SQLType: Numeric + ArithmeticOutput<Rhs::SQLType, Op, Output = SQLType>,
209    Rhs::SQLType: Numeric,
210    Rhs::Nullable: Nullability,
211    Lhs::Aggregate: AggOr<Rhs::Aggregate>,
212    Rhs::Aggregate: AggregateKind,
213    Op: BinOpToken,
214    SQLType: crate::types::DataType,
215    Nullable: Nullability,
216    <Lhs::SQLType as ArithmeticOutput<Rhs::SQLType, Op>>::Nullability:
217        ResolveArithmeticNullability<Lhs::Nullable, Rhs::Nullable, Output = Nullable>,
218{
219    type SQLType = SQLType;
220    type Nullable = Nullable;
221    type Aggregate = <Lhs::Aggregate as AggOr<Rhs::Aggregate>>::Output;
222}
223
224impl<Lhs, Rhs, Op, D, SQLType, Nullable> super::HasAggStatus
225    for ColumnBinOp<Lhs, Rhs, Op, D, SQLType, Nullable>
226where
227    Lhs: super::HasAggStatus,
228    Rhs: super::HasAggStatus,
229    Lhs::Status: super::CombineAggStatus<Rhs::Status>,
230{
231    type Status = <Lhs::Status as super::CombineAggStatus<Rhs::Status>>::Output;
232}
233
234impl<Lhs, Rhs, Op, D, SQLType, Nullable> ArithmeticRhs<D>
235    for ColumnBinOp<Lhs, Rhs, Op, D, SQLType, Nullable>
236where
237    SQLType: Numeric,
238    Nullable: Nullability,
239{
240    type SQLType = SQLType;
241    type Nullable = Nullable;
242}
243
244impl<Lhs, Rhs, Op, D, SQLType, Nullable> crate::row::ExprValueType
245    for ColumnBinOp<Lhs, Rhs, Op, D, SQLType, Nullable>
246where
247    SQLType: crate::types::DataType + crate::row::SQLTypeToRust<D>,
248    Nullable:
249        Nullability + crate::row::WrapNullable<<SQLType as crate::row::SQLTypeToRust<D>>::RustType>,
250{
251    type ValueType = <Nullable as crate::row::WrapNullable<
252        <SQLType as crate::row::SQLTypeToRust<D>>::RustType,
253    >>::Output;
254}
255
256impl<Lhs, Rhs, Op, D, SQLType, Nullable> crate::row::IntoSelectTarget
257    for ColumnBinOp<Lhs, Rhs, Op, D, SQLType, Nullable>
258{
259    type Marker = crate::row::SelectCols<(Self,)>;
260}
261
262/// Negation result for column arithmetic.
263#[derive(Debug, Clone, Copy)]
264pub struct ColumnNeg<T, D, SQLType, Nullable> {
265    inner: T,
266    _type: PhantomData<(D, SQLType, Nullable)>,
267}
268
269impl<T, D, SQLType, Nullable> ColumnNeg<T, D, SQLType, Nullable> {
270    #[inline]
271    pub const fn new(inner: T) -> Self {
272        Self {
273            inner,
274            _type: PhantomData,
275        }
276    }
277}
278
279impl<'a, V, T, D, SQLType, Nullable> ToSQL<'a, V> for ColumnNeg<T, D, SQLType, Nullable>
280where
281    V: SQLParam,
282    T: ToSQL<'a, V>,
283{
284    fn to_sql(&self) -> SQL<'a, V> {
285        SQL::raw("-").append(self.inner.to_sql())
286    }
287
288    fn into_sql(self) -> SQL<'a, V> {
289        SQL::raw("-").append(self.inner.into_sql())
290    }
291}
292
293impl<'a, V, T, D, SQLType, Nullable> Expr<'a, V> for ColumnNeg<T, D, SQLType, Nullable>
294where
295    V: SQLParam<DialectMarker = D>,
296    T: Expr<'a, V, Nullable = Nullable>,
297    T::SQLType: Numeric + NegOutput<Output = SQLType>,
298    SQLType: crate::types::DataType,
299    Nullable: Nullability,
300{
301    type SQLType = SQLType;
302    type Nullable = Nullable;
303    type Aggregate = T::Aggregate;
304}
305
306impl<T: super::HasAggStatus, D, SQLType, Nullable> super::HasAggStatus
307    for ColumnNeg<T, D, SQLType, Nullable>
308{
309    type Status = T::Status;
310}
311
312impl<T, D, SQLType, Nullable> crate::row::ExprValueType for ColumnNeg<T, D, SQLType, Nullable>
313where
314    SQLType: crate::types::DataType + crate::row::SQLTypeToRust<D>,
315    Nullable:
316        Nullability + crate::row::WrapNullable<<SQLType as crate::row::SQLTypeToRust<D>>::RustType>,
317{
318    type ValueType = <Nullable as crate::row::WrapNullable<
319        <SQLType as crate::row::SQLTypeToRust<D>>::RustType,
320    >>::Output;
321}
322
323impl<T, D, SQLType, Nullable> crate::row::IntoSelectTarget for ColumnNeg<T, D, SQLType, Nullable> {
324    type Marker = crate::row::SelectCols<(Self,)>;
325}
326
327impl<T, D, SQLType, Nullable> ArithmeticRhs<D> for ColumnNeg<T, D, SQLType, Nullable>
328where
329    SQLType: Numeric,
330    Nullable: Nullability,
331{
332    type SQLType = SQLType;
333    type Nullable = Nullable;
334}