Skip to main content

qraft_core/expression/
binary.rs

1//! Binary predicates, comparisons, and numeric operators.
2
3use std::marker::PhantomData;
4
5use crate::{
6    BinaryType, LowerCompatible, NullOf, Nullable, Numeric, Orderable, PredicateType,
7    expression::{
8        Column, Expression, PostfixOperator, Scalar,
9        op::{And, Or},
10    },
11    instr::RpnInstr,
12    lower::{Instructions, LowerCtx},
13    ty::{Bool, Boolean, Comparable, Nullability, TypeMeta},
14};
15
16/// Operators emitted as binary instructions.
17#[derive(Debug, Clone, Copy, PartialEq, Eq)]
18pub enum Operator {
19    Eq,
20    Neq,
21    Lt,
22    Lte,
23    Gt,
24    Gte,
25    Like { sensitive: bool, negated: bool },
26    And,
27    Or,
28    Add,
29    Mul,
30    Rem,
31    Sub,
32    Div,
33    BitAnd,
34    BitOr,
35    Shl,
36    Shr,
37}
38
39/// A typed binary expression with a left and right operand.
40pub struct Binary<T, L, R> {
41    pub(crate) left: L,
42    pub(crate) op: PhantomData<T>,
43    pub(crate) right: R,
44}
45
46impl<T, L, R> Binary<T, L, R> {
47    /// Returns the stored operands without changing their order.
48    pub fn into_parts(self) -> (L, R) {
49        (self.left, self.right)
50    }
51}
52
53/// Maps operand types to the output type and emitted instruction.
54pub trait BinaryOp<L: TypeMeta, R: TypeMeta> {
55    /// Resulting SQL type for this operator and operand pair.
56    type Output: TypeMeta;
57    /// Lowered instruction template for this operator.
58    const INSTRUCTION: RpnInstr;
59}
60
61/// Marker types for supported binary operators.
62pub mod op {
63    pub struct Eq;
64    pub struct And;
65    pub struct Or;
66    pub struct Neq;
67    pub struct Gt;
68    pub struct Gte;
69    pub struct Lt;
70    pub struct Lte;
71    pub struct Add;
72    pub struct Rem;
73    pub struct Mul;
74    pub struct Sub;
75    pub struct Div;
76    pub struct BitAnd;
77    pub struct BitOr;
78    pub struct Shl;
79    pub struct Shr;
80}
81
82macro_rules! impl_predicate_binary {
83    ($op:ty, $variant:expr, $bound:ident) => {
84        impl<T> BinaryOp<T, T> for $op
85        where
86            T: $bound + Nullability,
87            BinaryType<NullOf<T>, NullOf<T>>: PredicateType,
88        {
89            type Output = <BinaryType<NullOf<T>, NullOf<T>> as PredicateType>::Output;
90            const INSTRUCTION: RpnInstr = RpnInstr::Binary {
91                op: $variant,
92                lhs: 0,
93                rhs: 0,
94            };
95        }
96    };
97}
98
99impl_predicate_binary!(op::Eq, Operator::Eq, Comparable);
100impl_predicate_binary!(op::Neq, Operator::Neq, Comparable);
101impl_predicate_binary!(op::Gt, Operator::Gt, Orderable);
102impl_predicate_binary!(op::Lt, Operator::Lt, Orderable);
103impl_predicate_binary!(op::Gte, Operator::Gte, Orderable);
104impl_predicate_binary!(op::Lte, Operator::Lte, Orderable);
105impl_predicate_binary!(op::And, Operator::And, Boolean);
106impl_predicate_binary!(op::Or, Operator::Or, Boolean);
107
108macro_rules! impl_numeric_binary {
109    ($op:ty, $variant:expr) => {
110        impl<T> BinaryOp<T, T> for $op
111        where
112            T: Numeric,
113        {
114            type Output = T;
115            const INSTRUCTION: RpnInstr = RpnInstr::Binary {
116                op: $variant,
117                lhs: 0,
118                rhs: 0,
119            };
120        }
121    };
122}
123
124impl_numeric_binary!(op::Add, Operator::Add);
125impl_numeric_binary!(op::Rem, Operator::Rem);
126impl_numeric_binary!(op::Mul, Operator::Mul);
127impl_numeric_binary!(op::Sub, Operator::Sub);
128impl_numeric_binary!(op::Div, Operator::Div);
129impl_numeric_binary!(op::BitAnd, Operator::BitAnd);
130impl_numeric_binary!(op::BitOr, Operator::BitOr);
131impl_numeric_binary!(op::Shl, Operator::Shl);
132impl_numeric_binary!(op::Shr, Operator::Shr);
133
134#[qraft_expression_macro::as_expression]
135impl<T, L, R> Expression for Binary<T, L, R>
136where
137    L: Expression,
138    R: LowerCompatible<L::Type>,
139    T: BinaryOp<L::Type, L::Type>,
140{
141    type Type = T::Output;
142
143    fn lower(&self, ctx: &mut LowerCtx) -> usize {
144        let lhs = self.left.lower(ctx);
145        let rhs = self.right.lower_compatible(ctx);
146        let RpnInstr::Binary { op, .. } = T::INSTRUCTION else {
147            unreachable!("binary instruction");
148        };
149        ctx.instrs.push_binary(op, lhs, rhs);
150        lhs + rhs + 1
151    }
152}
153
154/// Equality and inequality helpers for comparable expressions.
155pub trait EqExt<T: Comparable>: Sized + Expression {
156    fn eq<E>(self, other: E) -> Binary<op::Eq, Self, E>
157    where
158        E: LowerCompatible<T>,
159    {
160        Binary {
161            left: self,
162            right: other,
163            op: PhantomData,
164        }
165    }
166
167    fn neq<E>(self, other: E) -> Binary<op::Neq, Self, E>
168    where
169        E: LowerCompatible<T>,
170    {
171        Binary {
172            left: self,
173            right: other,
174            op: PhantomData,
175        }
176    }
177}
178
179/// Postfix predicates such as `is null` and `is true`.
180#[derive(Debug)]
181pub struct Postfix<L> {
182    pub lhs: L,
183    pub operator: PostfixOperator,
184}
185
186#[qraft_expression_macro::as_expression]
187impl<E: Expression> Expression for Postfix<E> {
188    type Type = Bool;
189
190    fn lower(&self, ctx: &mut LowerCtx) -> usize {
191        let lhs = self.lhs.lower(ctx);
192        ctx.lower_postfix(self.operator, lhs)
193    }
194}
195
196/// Adds `is null` and `is not null` to nullable expressions.
197pub trait IsNull<T>: Sized {
198    #[allow(clippy::wrong_self_convention)]
199    fn is_null(self) -> Postfix<Self> {
200        Postfix {
201            lhs: self,
202            operator: PostfixOperator::Null { negated: false },
203        }
204    }
205
206    #[allow(clippy::wrong_self_convention)]
207    fn is_not_null(self) -> Postfix<Self> {
208        Postfix {
209            lhs: self,
210            operator: PostfixOperator::Null { negated: true },
211        }
212    }
213}
214
215/// Adds `is true` and `is false` to boolean expressions.
216pub trait IsPredicate<T>: Sized {
217    #[allow(clippy::wrong_self_convention)]
218    fn is_true(self) -> Postfix<Self> {
219        Postfix {
220            lhs: self,
221            operator: PostfixOperator::True,
222        }
223    }
224
225    #[allow(clippy::wrong_self_convention)]
226    fn is_false(self) -> Postfix<Self> {
227        Postfix {
228            lhs: self,
229            operator: PostfixOperator::False,
230        }
231    }
232}
233
234impl<T: Comparable, V> IsNull<T> for V where V: Expression<Type = Nullable<T>> {}
235impl<T: Boolean, V> IsPredicate<T> for V where V: Expression<Type = T> {}
236
237impl<T: Comparable, V> EqExt<T> for V where V: Expression<Type = T> {}
238
239/// Ordering predicates for comparable expressions.
240pub trait OrderExt<T: Orderable>: Sized + Expression {
241    fn lt<E>(self, other: E) -> Binary<op::Lt, Self, E>
242    where
243        E: LowerCompatible<T>,
244    {
245        Binary {
246            left: self,
247            right: other,
248            op: PhantomData,
249        }
250    }
251
252    fn lte<E>(self, other: E) -> Binary<op::Lte, Self, E>
253    where
254        E: LowerCompatible<T>,
255    {
256        Binary {
257            left: self,
258            right: other,
259            op: PhantomData,
260        }
261    }
262
263    fn gt<E>(self, other: E) -> Binary<op::Gt, Self, E>
264    where
265        E: LowerCompatible<T>,
266    {
267        Binary {
268            left: self,
269            right: other,
270            op: PhantomData,
271        }
272    }
273
274    fn gte<E>(self, other: E) -> Binary<op::Gte, Self, E>
275    where
276        E: LowerCompatible<T>,
277    {
278        Binary {
279            left: self,
280            right: other,
281            op: PhantomData,
282        }
283    }
284}
285
286impl<T: Orderable, V> OrderExt<T> for V where V: Expression<Type = T> {}
287
288/// Boolean combinators for predicate expressions.
289pub trait PredicateExt<T: Boolean>: Sized + Expression {
290    fn and<E>(self, other: E) -> Binary<And, Self, E>
291    where
292        E: LowerCompatible<T>,
293    {
294        Binary {
295            left: self,
296            right: other,
297            op: PhantomData,
298        }
299    }
300
301    fn or<E>(self, other: E) -> Binary<Or, Self, E>
302    where
303        E: LowerCompatible<T>,
304    {
305        Binary {
306            left: self,
307            right: other,
308            op: PhantomData,
309        }
310    }
311}
312
313impl<T: Boolean, V> PredicateExt<T> for V where V: Expression<Type = T> {}
314
315macro_rules! impl_binop_family {
316    (trait = $trait:ident,method = $method:ident,op = $op:path) => {
317        impl<T: Numeric, Op, L, R, E> core::ops::$trait<E> for Binary<Op, L, R>
318        where
319            E: Expression<Type = T>,
320        {
321            type Output = Binary<$op, Self, E>;
322
323            fn $method(self, rhs: E) -> Self::Output {
324                Binary {
325                    left: self,
326                    op: PhantomData,
327                    right: rhs,
328                }
329            }
330        }
331
332        impl<T: Numeric, E> core::ops::$trait<E> for Scalar<T>
333        where
334            E: Expression<Type = T>,
335        {
336            type Output = Binary<$op, Self, E>;
337
338            fn $method(self, rhs: E) -> Self::Output {
339                Binary {
340                    left: self,
341                    op: PhantomData,
342                    right: rhs,
343                }
344            }
345        }
346
347        impl<M, T: Numeric, E> core::ops::$trait<E> for Column<M, T>
348        where
349            E: Expression<Type = T>,
350        {
351            type Output = Binary<$op, Self, E>;
352
353            fn $method(self, rhs: E) -> Self::Output {
354                Binary {
355                    left: self,
356                    op: PhantomData,
357                    right: rhs,
358                }
359            }
360        }
361    };
362}
363
364impl_binop_family!(trait = BitAnd, method = bitand, op = op::BitAnd);
365impl_binop_family!(trait = BitOr, method = bitor, op = op::BitOr);
366impl_binop_family!(trait = Shl, method = shl, op = op::Shl);
367impl_binop_family!(trait = Shr, method = shr, op = op::Shr);