use std::marker::PhantomData;
use quex::Encode;
use crate::{
BigInt, Blob, Bool, Comparable, Double, Float, Int, Likeable, LowerCompatible, Nullable,
Qrafting, Required, Text, UBigInt, UInt, Untyped,
alias::Aliased,
expression::{
As, Between, Binary, EqExt, Expression, IsNull, IsPredicate, Like, LikeExt, Postfix,
op::{Eq, Gt, Gte, Lt, Lte, Neq},
},
lower::LowerCtx,
param::encode_param,
query::LowerProject,
ty::{Orderable, TypeMeta},
};
#[derive(Debug)]
pub struct Column<M, T: TypeMeta> {
pub name: &'static str,
marker: PhantomData<(M, T)>,
}
impl<M, Ty: TypeMeta> Copy for Column<M, Ty> {}
impl<M, Ty: TypeMeta> Clone for Column<M, Ty> {
fn clone(&self) -> Self {
*self
}
}
impl<M, T: TypeMeta> Column<M, T> {
pub const fn new(value: &'static str) -> Self {
Column {
name: value,
marker: PhantomData,
}
}
pub const fn name(self) -> &'static str {
self.name
}
}
pub trait Col {
fn typed<T: TypeMeta>(&self) -> Column<(), T>;
fn untyped(&self) -> Column<(), Untyped> {
self.typed::<Untyped>()
}
fn text(&self) -> Column<(), Text> {
self.typed::<Text>()
}
fn blob(&self) -> Column<(), Blob> {
self.typed::<Blob>()
}
fn bool(&self) -> Column<(), Bool> {
self.typed::<Bool>()
}
fn bigint(&self) -> Column<(), BigInt> {
self.typed::<BigInt>()
}
fn int(&self) -> Column<(), Int> {
self.typed::<Int>()
}
fn float(&self) -> Column<(), Float> {
self.typed::<Float>()
}
fn double(&self) -> Column<(), Double> {
self.typed::<Double>()
}
fn uint(&self) -> Column<(), UInt> {
self.typed::<UInt>()
}
fn ubigint(&self) -> Column<(), UBigInt> {
self.typed::<UBigInt>()
}
}
impl Col for &'static str {
fn typed<T: TypeMeta>(&self) -> Column<(), T> {
Column {
name: self,
marker: PhantomData,
}
}
}
impl<M, T: TypeMeta + Required> Column<M, T> {
pub fn nullable(self) -> Column<M, Nullable<T>> {
Column {
name: self.name,
marker: PhantomData,
}
}
}
impl<M: Qrafting, T: Comparable> Column<M, T> {
pub fn eq<E>(self, e: E) -> Binary<Eq, Self, E>
where
E: LowerCompatible<T>,
{
EqExt::eq(self, e)
}
pub fn neq<E>(self, e: E) -> Binary<Neq, Self, E>
where
E: LowerCompatible<T>,
{
EqExt::neq(self, e)
}
}
impl<T: Comparable> Column<(), T> {
pub fn eq<E>(self, e: E) -> Binary<Eq, Self, E>
where
E: LowerCompatible<T>,
{
EqExt::eq(self, e)
}
pub fn neq<E>(self, e: E) -> Binary<Neq, Self, E>
where
E: LowerCompatible<T>,
{
EqExt::neq(self, e)
}
}
pub trait CompareUntyped {
fn equal<E>(self, other: E) -> Binary<Eq, Column<(), Untyped>, UntypedValue<E>>
where
E: Encode;
fn not_equal<E>(self, other: E) -> Binary<Neq, Column<(), Untyped>, UntypedValue<E>>
where
E: Encode;
fn less_than<E>(self, other: E) -> Binary<Lt, Column<(), Untyped>, UntypedValue<E>>
where
E: Encode;
fn less_or_equal<E>(self, other: E) -> Binary<Lte, Column<(), Untyped>, UntypedValue<E>>
where
E: Encode;
fn greater_than<E>(self, other: E) -> Binary<Gt, Column<(), Untyped>, UntypedValue<E>>
where
E: Encode;
fn greater_or_equal<E>(self, other: E) -> Binary<Gte, Column<(), Untyped>, UntypedValue<E>>
where
E: Encode;
}
impl CompareUntyped for &'static str {
fn equal<E>(self, other: E) -> Binary<Eq, Column<(), Untyped>, UntypedValue<E>>
where
E: Encode,
{
Binary {
left: self.untyped(),
right: UntypedValue(other),
op: PhantomData,
}
}
fn not_equal<E>(self, other: E) -> Binary<Neq, Column<(), Untyped>, UntypedValue<E>>
where
E: Encode,
{
Binary {
left: self.untyped(),
right: UntypedValue(other),
op: PhantomData,
}
}
fn less_than<E>(self, other: E) -> Binary<Lt, Column<(), Untyped>, UntypedValue<E>>
where
E: Encode,
{
Binary {
left: self.untyped(),
right: UntypedValue(other),
op: PhantomData,
}
}
fn less_or_equal<E>(self, other: E) -> Binary<Lte, Column<(), Untyped>, UntypedValue<E>>
where
E: Encode,
{
Binary {
left: self.untyped(),
right: UntypedValue(other),
op: PhantomData,
}
}
fn greater_than<E>(self, other: E) -> Binary<Gt, Column<(), Untyped>, UntypedValue<E>>
where
E: Encode,
{
Binary {
left: self.untyped(),
right: UntypedValue(other),
op: PhantomData,
}
}
fn greater_or_equal<E>(self, other: E) -> Binary<Gte, Column<(), Untyped>, UntypedValue<E>>
where
E: Encode,
{
Binary {
left: self.untyped(),
right: UntypedValue(other),
op: PhantomData,
}
}
}
pub struct UntypedValue<T>(T);
#[qraft_expression_macro::as_expression]
impl<T: Encode> Expression for UntypedValue<T> {
type Type = Untyped;
fn lower(&self, ctx: &mut LowerCtx) -> usize {
let param = encode_param(&self.0, ctx.data);
ctx.lower_param(param)
}
}
impl<M: Qrafting, T: Comparable + Required> Column<M, Nullable<T>> {
#[allow(clippy::wrong_self_convention)]
pub fn is_null(self) -> Postfix<Self> {
IsNull::is_null(self)
}
#[allow(clippy::wrong_self_convention)]
pub fn is_not_null(self) -> Postfix<Self> {
IsNull::is_not_null(self)
}
}
impl<M: Qrafting, T: crate::Boolean> Column<M, T> {
#[allow(clippy::wrong_self_convention)]
pub fn is_true(self) -> Postfix<Self> {
IsPredicate::is_true(self)
}
#[allow(clippy::wrong_self_convention)]
pub fn is_false(self) -> Postfix<Self> {
IsPredicate::is_false(self)
}
}
impl<M: Qrafting, T: Likeable> Column<M, T> {
pub fn like<E>(self, other: E) -> Like<Self, E>
where
E: LowerCompatible<T>,
{
LikeExt::like(self, other)
}
pub fn not_like<E>(self, other: E) -> Like<Self, E>
where
E: LowerCompatible<T>,
{
LikeExt::not_like(self, other)
}
}
#[qraft_expression_macro::as_expression]
impl<M: Qrafting, T: TypeMeta> Expression for Column<M, T> {
type Type = T;
fn lower(&self, ctx: &mut LowerCtx) -> usize {
ctx.lower_column(Some(M::TABLE), self.name)
}
}
#[qraft_expression_macro::as_expression]
impl<T: TypeMeta> Expression for Column<(), T> {
type Type = T;
fn lower(&self, ctx: &mut LowerCtx) -> usize {
ctx.lower_column(None, self.name)
}
}
pub struct ColumnStar<M> {
pub marker: PhantomData<M>,
}
impl<M> Copy for ColumnStar<M> {}
impl<M> Clone for ColumnStar<M> {
fn clone(&self) -> Self {
*self
}
}
impl<M: Qrafting> LowerProject for ColumnStar<M> {
fn lower_project(self, ctx: &mut LowerCtx) {
let table = M::TABLE;
ctx.lower_column(Some(table), "*");
}
}
impl<M: Qrafting> LowerProject for As<ColumnStar<M>> {
fn lower_project(self, ctx: &mut LowerCtx) {
ctx.lower_column(Some(self.table), "*");
}
}
impl<M: Qrafting> LowerProject for Aliased<As<ColumnStar<M>>> {
fn lower_project(self, ctx: &mut LowerCtx) {
let inner = ctx.lower_column(Some(self.inner.table), "*");
let _ = ctx.lower_alias(self.alias, inner);
}
}
impl<M> ColumnStar<M> {
pub const fn new() -> Self {
ColumnStar {
marker: PhantomData,
}
}
}
impl<M> Default for ColumnStar<M> {
fn default() -> Self {
Self::new()
}
}
pub const fn col<T: TypeMeta>(name: &'static str) -> Column<(), T> {
Column {
name,
marker: PhantomData,
}
}
impl<M: Qrafting, T: Orderable> Column<M, T> {
pub fn between<L, H>(self, low: L, high: H) -> Between<Self, L, H>
where
L: LowerCompatible<T>,
H: LowerCompatible<T>,
{
BetweenExt::between(self, low, high)
}
pub fn not_between<L, H>(self, low: L, high: H) -> Between<Self, L, H>
where
L: LowerCompatible<T>,
H: LowerCompatible<T>,
{
BetweenExt::not_between(self, low, high)
}
}
pub trait BetweenExt<T: Orderable>: Sized + Expression {
fn between<L, H>(self, low: L, high: H) -> Between<Self, L, H>
where
L: LowerCompatible<T>,
H: LowerCompatible<T>,
{
Between {
negated: false,
lhs: self,
low,
high,
}
}
fn not_between<L, H>(self, low: L, high: H) -> Between<Self, L, H>
where
L: LowerCompatible<T>,
H: LowerCompatible<T>,
{
Between {
negated: true,
lhs: self,
low,
high,
}
}
}
impl<T: Orderable, V> BetweenExt<T> for V where V: Expression<Type = T> {}
#[cfg(test)]
mod tests {
use super::{CompareUntyped, col};
use crate::{Sqlite, query::select};
#[test]
fn untyped_column_helpers_emit_expected_sql() {
let sql = select(col::<crate::Untyped>("priority"))
.from("tasks")
.filter("priority".greater_than(10))
.to_debug_sql::<Sqlite>();
assert_eq!(
sql,
r#"select "priority" from "tasks" where "priority" > ?; params=[10]"#
);
}
#[test]
fn untyped_column_equal_uses_untyped_builder() {
let sql = select("status")
.from("tasks")
.filter("status".equal("queued"))
.to_debug_sql::<Sqlite>();
assert_eq!(
sql,
r#"select "status" from "tasks" where "status" = ?; params=["queued"]"#
);
}
}