pub mod aggregate;
mod db_typ;
pub mod from_expr;
pub mod into_expr;
#[cfg(feature = "jiff-02")]
mod jiff_operations;
mod operations;
pub mod optional;
use std::{cell::OnceCell, fmt::Debug, marker::PhantomData, ops::Deref, rc::Rc};
use sea_query::{Alias, JoinType, SelectStatement};
use crate::{
IntoExpr, IntoSelect, Select, Table,
alias::{Field, JoinableTable, MyAlias, Scope},
ast::{MySelect, Source},
db::TableRow,
mutable::Mutable,
mymap::MyMap,
private::IntoJoinable,
};
pub use db_typ::{DbTyp, StorableTyp};
#[derive(Default)]
pub struct ValueBuilder {
pub(crate) from: Rc<MySelect>,
pub(super) scope: Scope,
pub(super) extra: MyMap<Source, MyAlias>,
pub(super) forwarded: MyMap<MyTableRef, MyAlias>,
}
impl ValueBuilder {
pub(crate) fn get_aggr(
&mut self,
aggr: Rc<SelectStatement>,
conds: Vec<MyTableRef>,
) -> MyAlias {
let source = Source {
kind: crate::ast::SourceKind::Aggregate(aggr),
conds: conds
.into_iter()
.enumerate()
.map(|(idx, join)| {
let alias = Alias::new(join.table_name.main_column());
(
Field::U64(MyAlias::new(idx)),
sea_query::Expr::col((self.get_table(join), alias)),
)
})
.collect(),
};
let new_alias = || self.scope.new_alias();
*self.extra.get_or_init(source, new_alias)
}
pub(crate) fn get_join<T: Table>(
&mut self,
expr: sea_query::Expr,
possible_null: bool,
new_col: &'static str,
) -> sea_query::Expr {
match &expr {
sea_query::Expr::Column(sea_query::ColumnRef::Column(sea_query::ColumnName(
Some(sea_query::TableName(None, table)),
col,
))) => {
if let Some(alias) = MyAlias::try_from(table)
&& let Some(from) = self.from.tables.get(alias.idx)
&& from.main_column() == col.inner().as_ref()
{
return sea_query::Expr::col((alias, new_col));
}
}
_ => (),
};
let join_type = if possible_null {
JoinType::LeftJoin
} else {
JoinType::Join
};
let source = Source {
kind: crate::ast::SourceKind::Implicit(T::NAME.to_owned(), join_type),
conds: vec![(Field::Str(T::ID), expr)],
};
let new_alias = || self.scope.new_alias();
let alias = *self.extra.get_or_init(source, new_alias);
sea_query::Expr::col((alias, new_col))
}
pub fn get_unique<T: Table>(
&mut self,
conds: Box<[(&'static str, sea_query::Expr)]>,
) -> sea_query::Expr {
let source = Source {
kind: crate::ast::SourceKind::Implicit(T::NAME.to_owned(), JoinType::LeftJoin),
conds: conds.into_iter().map(|x| (Field::Str(x.0), x.1)).collect(),
};
let new_alias = || self.scope.new_alias();
let table = self.extra.get_or_init(source, new_alias);
sea_query::Expr::col((*table, Alias::new(T::ID)))
}
pub fn get_table(&mut self, table: MyTableRef) -> MyAlias {
if Rc::ptr_eq(&self.from.scope_rc, &table.scope_rc) {
MyAlias::new(table.idx)
} else {
*self.forwarded.get_or_init(table, || self.scope.new_alias())
}
}
}
#[derive(Clone)]
pub struct MyTableRef {
pub(crate) scope_rc: Rc<()>,
pub(crate) idx: usize,
pub(crate) table_name: JoinableTable,
}
impl PartialEq for MyTableRef {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.scope_rc, &other.scope_rc) && self.idx == other.idx
}
}
pub trait NumTyp: OrdTyp + Clone + Copy {
const ZERO: sea_query::Value;
}
impl NumTyp for i64 {
const ZERO: sea_query::Value = sea_query::Value::BigInt(Some(0));
}
impl NumTyp for f64 {
const ZERO: sea_query::Value = sea_query::Value::Double(Some(0.));
}
pub trait OrdTyp: EqTyp {}
impl OrdTyp for String {}
impl OrdTyp for Vec<u8> {}
impl OrdTyp for i64 {}
impl OrdTyp for f64 {}
impl OrdTyp for bool {}
#[cfg(feature = "jiff-02")]
impl OrdTyp for jiff::Timestamp {}
#[cfg(feature = "jiff-02")]
impl OrdTyp for jiff::civil::Date {}
pub trait BuffTyp: DbTyp {}
impl BuffTyp for String {}
impl BuffTyp for Vec<u8> {}
#[diagnostic::on_unimplemented(
message = "Columns with type `{Self}` can not be checked for equality",
note = "`EqTyp` is also implemented for all table types"
)]
pub trait EqTyp: DbTyp {}
impl EqTyp for String {}
impl EqTyp for Vec<u8> {}
impl EqTyp for i64 {}
impl EqTyp for f64 {}
impl EqTyp for bool {}
#[cfg(feature = "jiff-02")]
impl EqTyp for jiff::Timestamp {}
#[cfg(feature = "jiff-02")]
impl EqTyp for jiff::civil::Date {}
#[diagnostic::do_not_recommend]
impl<T: Table> EqTyp for TableRow<T> {}
pub trait OptTable: DbTyp {
type Schema;
type Select;
type Mutable<'t>;
fn select_opt_mutable(
val: Expr<'_, Self::Schema, Self>,
) -> Select<'_, Self::Schema, Self::Select>;
fn into_mutable<'t>(val: Self::Select) -> Self::Mutable<'t>;
}
impl<T: Table> OptTable for TableRow<T> {
type Schema = T::Schema;
type Select = (T::Select, TableRow<T>);
type Mutable<'t> = Mutable<'t, T>;
fn select_opt_mutable(
val: Expr<'_, Self::Schema, Self>,
) -> Select<'_, Self::Schema, Self::Select> {
(T::into_select(val.clone()), val).into_select()
}
fn into_mutable<'t>((inner, row_id): Self::Select) -> Self::Mutable<'t> {
Mutable::new(T::select_mutable(inner), row_id)
}
}
impl<T: Table> OptTable for Option<TableRow<T>> {
type Schema = T::Schema;
type Select = Option<(T::Select, TableRow<T>)>;
type Mutable<'t> = Option<Mutable<'t, T>>;
fn select_opt_mutable(
val: Expr<'_, Self::Schema, Self>,
) -> Select<'_, Self::Schema, Self::Select> {
crate::optional(|row| {
let val = row.and(val);
row.then_select((T::into_select(val.clone()), val))
})
}
fn into_mutable<'t>(val: Self::Select) -> Self::Mutable<'t> {
val.map(TableRow::<T>::into_mutable)
}
}
pub struct Expr<'column, S, T: DbTyp> {
pub(crate) _local: PhantomData<*const ()>,
pub(crate) inner: Rc<AdHoc<dyn Fn(&mut ValueBuilder) -> sea_query::Expr, T>>,
pub(crate) _p: PhantomData<&'column ()>,
pub(crate) _p2: PhantomData<S>,
pub(crate) ext: OnceCell<Box<T::Ext<'static>>>,
}
#[cfg_attr(test, mutants::skip)]
impl<S, T: DbTyp> Debug for Expr<'_, S, T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "Expr of type {}", std::any::type_name::<T>())
}
}
impl<'column, S, T: DbTyp> Expr<'column, S, T> {
#[doc(hidden)]
pub fn _migrate<OldS>(prev: impl IntoExpr<'column, OldS>) -> Self {
let prev = DynTypedExpr::erase(prev);
Self::adhoc(move |b| (prev.func)(b))
}
}
pub fn adhoc_expr<S, T: DbTyp>(
f: impl 'static + Fn(&mut ValueBuilder) -> sea_query::Expr,
) -> Expr<'static, S, T> {
Expr::adhoc(f)
}
pub fn new_column<'x, S, C: DbTyp, T: Table>(
table: impl IntoExpr<'x, S, Typ = TableRow<T>>,
name: &'static str,
) -> Expr<'x, S, C> {
let table = table.into_expr().inner;
let possible_null = table.maybe_optional;
Expr::adhoc_promise(
move |b| {
let main_column = table.build_expr(b);
b.get_join::<T>(main_column, table.maybe_optional, name)
},
possible_null,
)
}
pub fn unique_from_joinable<'inner, T: Table>(
j: impl IntoJoinable<'inner, T::Schema, Typ = TableRow<T>>,
) -> Expr<'inner, T::Schema, Option<TableRow<T>>> {
let list = j.into_joinable().conds;
::rust_query::private::adhoc_expr(move |_b| {
let list = list
.iter()
.map(|(name, col)| (*name, (col.func)(_b)))
.collect();
_b.get_unique::<T>(list)
})
}
pub struct AdHoc<F: ?Sized, T: ?Sized> {
maybe_optional: bool,
_p: PhantomData<T>,
func: F,
}
impl<F: ?Sized + Fn(&mut ValueBuilder) -> sea_query::Expr, T> AdHoc<F, T> {
pub fn build_expr(&self, b: &mut ValueBuilder) -> sea_query::Expr {
(self.func)(b)
}
}
impl<S, T: DbTyp> Expr<'_, S, T> {
pub(crate) fn adhoc(f: impl 'static + Fn(&mut ValueBuilder) -> sea_query::Expr) -> Self {
Self::adhoc_promise(f, true)
}
pub(crate) fn adhoc_promise(
f: impl 'static + Fn(&mut ValueBuilder) -> sea_query::Expr,
maybe_optional: bool,
) -> Self {
Self::new(Rc::new(AdHoc {
func: f,
maybe_optional,
_p: PhantomData,
}))
}
pub(crate) fn new(val: Rc<AdHoc<dyn Fn(&mut ValueBuilder) -> sea_query::Expr, T>>) -> Self {
Self {
_local: PhantomData,
inner: val,
_p: PhantomData,
_p2: PhantomData,
ext: OnceCell::new(),
}
}
}
impl<S, T: DbTyp> Clone for Expr<'_, S, T> {
fn clone(&self) -> Self {
Self {
_local: PhantomData,
inner: self.inner.clone(),
_p: self._p,
_p2: self._p2,
ext: OnceCell::new(),
}
}
}
#[derive(Clone)]
pub struct DynTypedExpr {
pub func: Rc<dyn Fn(&mut ValueBuilder) -> sea_query::Expr>,
}
impl DynTypedExpr {
pub fn new(f: impl 'static + Fn(&mut ValueBuilder) -> sea_query::Expr) -> Self {
Self { func: Rc::new(f) }
}
pub fn erase<'x, S>(expr: impl IntoExpr<'x, S>) -> Self {
let typed = expr.into_expr().inner;
Self::new(move |b| typed.build_expr(b))
}
}
impl<'t, T: Table> Deref for Expr<'t, T::Schema, TableRow<T>> {
type Target = T::Ext2<'t>;
fn deref(&self) -> &Self::Target {
T::covariant_ext(self.ext.get_or_init(|| {
let expr = Expr {
_local: PhantomData,
inner: self.inner.clone(),
_p: PhantomData::<&'static ()>,
_p2: PhantomData,
ext: OnceCell::new(),
};
Box::new(T::build_ext2(&expr))
}))
}
}