use std::marker::PhantomData;
use crate::{
BinaryType, LowerCompatible, NullOf, Nullable, Numeric, Orderable, PredicateType,
expression::{
Column, Expression, PostfixOperator, Scalar,
op::{And, Or},
},
instr::RpnInstr,
lower::{Instructions, LowerCtx},
ty::{Bool, Boolean, Comparable, Nullability, TypeMeta},
};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Operator {
Eq,
Neq,
Lt,
Lte,
Gt,
Gte,
Like { sensitive: bool, negated: bool },
And,
Or,
Add,
Mul,
Rem,
Sub,
Div,
BitAnd,
BitOr,
Shl,
Shr,
}
pub struct Binary<T, L, R> {
pub(crate) left: L,
pub(crate) op: PhantomData<T>,
pub(crate) right: R,
}
impl<T, L, R> Binary<T, L, R> {
pub fn into_parts(self) -> (L, R) {
(self.left, self.right)
}
}
pub trait BinaryOp<L: TypeMeta, R: TypeMeta> {
type Output: TypeMeta;
const INSTRUCTION: RpnInstr;
}
pub mod op {
pub struct Eq;
pub struct And;
pub struct Or;
pub struct Neq;
pub struct Gt;
pub struct Gte;
pub struct Lt;
pub struct Lte;
pub struct Add;
pub struct Rem;
pub struct Mul;
pub struct Sub;
pub struct Div;
pub struct BitAnd;
pub struct BitOr;
pub struct Shl;
pub struct Shr;
}
macro_rules! impl_predicate_binary {
($op:ty, $variant:expr, $bound:ident) => {
impl<T> BinaryOp<T, T> for $op
where
T: $bound + Nullability,
BinaryType<NullOf<T>, NullOf<T>>: PredicateType,
{
type Output = <BinaryType<NullOf<T>, NullOf<T>> as PredicateType>::Output;
const INSTRUCTION: RpnInstr = RpnInstr::Binary {
op: $variant,
lhs: 0,
rhs: 0,
};
}
};
}
impl_predicate_binary!(op::Eq, Operator::Eq, Comparable);
impl_predicate_binary!(op::Neq, Operator::Neq, Comparable);
impl_predicate_binary!(op::Gt, Operator::Gt, Orderable);
impl_predicate_binary!(op::Lt, Operator::Lt, Orderable);
impl_predicate_binary!(op::Gte, Operator::Gte, Orderable);
impl_predicate_binary!(op::Lte, Operator::Lte, Orderable);
impl_predicate_binary!(op::And, Operator::And, Boolean);
impl_predicate_binary!(op::Or, Operator::Or, Boolean);
macro_rules! impl_numeric_binary {
($op:ty, $variant:expr) => {
impl<T> BinaryOp<T, T> for $op
where
T: Numeric,
{
type Output = T;
const INSTRUCTION: RpnInstr = RpnInstr::Binary {
op: $variant,
lhs: 0,
rhs: 0,
};
}
};
}
impl_numeric_binary!(op::Add, Operator::Add);
impl_numeric_binary!(op::Rem, Operator::Rem);
impl_numeric_binary!(op::Mul, Operator::Mul);
impl_numeric_binary!(op::Sub, Operator::Sub);
impl_numeric_binary!(op::Div, Operator::Div);
impl_numeric_binary!(op::BitAnd, Operator::BitAnd);
impl_numeric_binary!(op::BitOr, Operator::BitOr);
impl_numeric_binary!(op::Shl, Operator::Shl);
impl_numeric_binary!(op::Shr, Operator::Shr);
#[qraft_expression_macro::as_expression]
impl<T, L, R> Expression for Binary<T, L, R>
where
L: Expression,
R: LowerCompatible<L::Type>,
T: BinaryOp<L::Type, L::Type>,
{
type Type = T::Output;
fn lower(&self, ctx: &mut LowerCtx) -> usize {
let lhs = self.left.lower(ctx);
let rhs = self.right.lower_compatible(ctx);
let RpnInstr::Binary { op, .. } = T::INSTRUCTION else {
unreachable!("binary instruction");
};
ctx.instrs.push_binary(op, lhs, rhs);
lhs + rhs + 1
}
}
pub trait EqExt<T: Comparable>: Sized + Expression {
fn eq<E>(self, other: E) -> Binary<op::Eq, Self, E>
where
E: LowerCompatible<T>,
{
Binary {
left: self,
right: other,
op: PhantomData,
}
}
fn neq<E>(self, other: E) -> Binary<op::Neq, Self, E>
where
E: LowerCompatible<T>,
{
Binary {
left: self,
right: other,
op: PhantomData,
}
}
}
#[derive(Debug)]
pub struct Postfix<L> {
pub lhs: L,
pub operator: PostfixOperator,
}
#[qraft_expression_macro::as_expression]
impl<E: Expression> Expression for Postfix<E> {
type Type = Bool;
fn lower(&self, ctx: &mut LowerCtx) -> usize {
let lhs = self.lhs.lower(ctx);
ctx.lower_postfix(self.operator, lhs)
}
}
pub trait IsNull<T>: Sized {
#[allow(clippy::wrong_self_convention)]
fn is_null(self) -> Postfix<Self> {
Postfix {
lhs: self,
operator: PostfixOperator::Null { negated: false },
}
}
#[allow(clippy::wrong_self_convention)]
fn is_not_null(self) -> Postfix<Self> {
Postfix {
lhs: self,
operator: PostfixOperator::Null { negated: true },
}
}
}
pub trait IsPredicate<T>: Sized {
#[allow(clippy::wrong_self_convention)]
fn is_true(self) -> Postfix<Self> {
Postfix {
lhs: self,
operator: PostfixOperator::True,
}
}
#[allow(clippy::wrong_self_convention)]
fn is_false(self) -> Postfix<Self> {
Postfix {
lhs: self,
operator: PostfixOperator::False,
}
}
}
impl<T: Comparable, V> IsNull<T> for V where V: Expression<Type = Nullable<T>> {}
impl<T: Boolean, V> IsPredicate<T> for V where V: Expression<Type = T> {}
impl<T: Comparable, V> EqExt<T> for V where V: Expression<Type = T> {}
pub trait OrderExt<T: Orderable>: Sized + Expression {
fn lt<E>(self, other: E) -> Binary<op::Lt, Self, E>
where
E: LowerCompatible<T>,
{
Binary {
left: self,
right: other,
op: PhantomData,
}
}
fn lte<E>(self, other: E) -> Binary<op::Lte, Self, E>
where
E: LowerCompatible<T>,
{
Binary {
left: self,
right: other,
op: PhantomData,
}
}
fn gt<E>(self, other: E) -> Binary<op::Gt, Self, E>
where
E: LowerCompatible<T>,
{
Binary {
left: self,
right: other,
op: PhantomData,
}
}
fn gte<E>(self, other: E) -> Binary<op::Gte, Self, E>
where
E: LowerCompatible<T>,
{
Binary {
left: self,
right: other,
op: PhantomData,
}
}
}
impl<T: Orderable, V> OrderExt<T> for V where V: Expression<Type = T> {}
pub trait PredicateExt<T: Boolean>: Sized + Expression {
fn and<E>(self, other: E) -> Binary<And, Self, E>
where
E: LowerCompatible<T>,
{
Binary {
left: self,
right: other,
op: PhantomData,
}
}
fn or<E>(self, other: E) -> Binary<Or, Self, E>
where
E: LowerCompatible<T>,
{
Binary {
left: self,
right: other,
op: PhantomData,
}
}
}
impl<T: Boolean, V> PredicateExt<T> for V where V: Expression<Type = T> {}
macro_rules! impl_binop_family {
(trait = $trait:ident,method = $method:ident,op = $op:path) => {
impl<T: Numeric, Op, L, R, E> core::ops::$trait<E> for Binary<Op, L, R>
where
E: Expression<Type = T>,
{
type Output = Binary<$op, Self, E>;
fn $method(self, rhs: E) -> Self::Output {
Binary {
left: self,
op: PhantomData,
right: rhs,
}
}
}
impl<T: Numeric, E> core::ops::$trait<E> for Scalar<T>
where
E: Expression<Type = T>,
{
type Output = Binary<$op, Self, E>;
fn $method(self, rhs: E) -> Self::Output {
Binary {
left: self,
op: PhantomData,
right: rhs,
}
}
}
impl<M, T: Numeric, E> core::ops::$trait<E> for Column<M, T>
where
E: Expression<Type = T>,
{
type Output = Binary<$op, Self, E>;
fn $method(self, rhs: E) -> Self::Output {
Binary {
left: self,
op: PhantomData,
right: rhs,
}
}
}
};
}
impl_binop_family!(trait = BitAnd, method = bitand, op = op::BitAnd);
impl_binop_family!(trait = BitOr, method = bitor, op = op::BitOr);
impl_binop_family!(trait = Shl, method = shl, op = op::Shl);
impl_binop_family!(trait = Shr, method = shr, op = op::Shr);