use std::marker::PhantomData;
use crate::scope::{Concat, Cons, MaybeNull, Nil, Table, WrapNullable};
mod sql_type {
pub trait Sealed {}
}
pub trait SqlType: 'static + sql_type::Sealed {
type Native;
}
#[derive(Debug, Clone)]
pub(crate) enum ExprKind {
Column {
table: &'static str,
name: &'static str,
},
Value(Value),
BinOp {
op: BinOp,
lhs: Box<ExprKind>,
rhs: Box<ExprKind>,
},
And(Box<ExprKind>, Box<ExprKind>),
Or(Box<ExprKind>, Box<ExprKind>),
Always(bool),
Not(Box<ExprKind>),
IsNull {
expr: Box<ExprKind>,
negated: bool,
},
InList {
expr: Box<ExprKind>,
values: Vec<ExprKind>,
},
Exists {
body: Box<crate::select::SelectBody>,
selection: Vec<crate::render::SelectItem>,
negated: bool,
},
Template {
head: String,
rest: Vec<(ExprKind, String)>,
},
Cast {
expr: Box<ExprKind>,
target: CastTarget,
},
Func {
name: &'static str,
arg: Option<Box<ExprKind>>,
},
Window {
func: &'static str,
partition_by: Vec<ExprKind>,
order_by: Vec<(ExprKind, SortDir)>,
},
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SortDir {
Asc,
Desc,
}
#[doc(hidden)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum CastTarget {
BigInt,
Double,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BinOp {
Eq,
Ne,
Lt,
Lte,
Gt,
Gte,
Like,
}
#[derive(Debug, Clone, PartialEq)]
pub enum Value {
I32(i32),
I64(i64),
F64(f64),
Text(String),
Bool(bool),
Bytes(Vec<u8>),
NullI32,
NullI64,
NullF64,
NullText,
NullBool,
NullBytes,
#[cfg(feature = "chrono")]
Timestamptz(chrono::DateTime<chrono::Utc>),
#[cfg(feature = "chrono")]
NullTimestamptz,
#[cfg(feature = "chrono")]
Date(chrono::NaiveDate),
#[cfg(feature = "chrono")]
NullDate,
#[cfg(feature = "uuid")]
Uuid(uuid::Uuid),
#[cfg(feature = "uuid")]
NullUuid,
#[cfg(feature = "decimal")]
Numeric(rust_decimal::Decimal),
#[cfg(feature = "decimal")]
NullNumeric,
Placeholder(&'static str),
}
impl Value {
pub fn type_name(&self) -> &'static str {
match self {
Value::I32(_) | Value::NullI32 => "Integer",
Value::I64(_) | Value::NullI64 => "BigInt",
Value::F64(_) | Value::NullF64 => "Real",
Value::Text(_) | Value::NullText => "Text",
Value::Bool(_) | Value::NullBool => "Bool",
Value::Bytes(_) | Value::NullBytes => "Bytes",
Value::Placeholder(_) => "placeholder",
#[cfg(feature = "chrono")]
Value::Timestamptz(_) | Value::NullTimestamptz => "Timestamptz",
#[cfg(feature = "chrono")]
Value::Date(_) | Value::NullDate => "Date",
#[cfg(feature = "uuid")]
Value::Uuid(_) | Value::NullUuid => "Uuid",
#[cfg(feature = "decimal")]
Value::Numeric(_) | Value::NullNumeric => "Numeric",
}
}
}
macro_rules! value_from {
($ty:ty, $variant:ident) => {
impl From<$ty> for Value {
fn from(v: $ty) -> Self {
Value::$variant(v)
}
}
};
}
value_from!(i32, I32);
value_from!(i64, I64);
value_from!(f64, F64);
value_from!(String, Text);
value_from!(bool, Bool);
value_from!(Vec<u8>, Bytes);
#[cfg(feature = "chrono")]
value_from!(chrono::DateTime<chrono::Utc>, Timestamptz);
#[cfg(feature = "chrono")]
value_from!(chrono::NaiveDate, Date);
#[cfg(feature = "uuid")]
value_from!(uuid::Uuid, Uuid);
#[cfg(feature = "decimal")]
value_from!(rust_decimal::Decimal, Numeric);
impl From<&str> for Value {
fn from(v: &str) -> Self {
Value::Text(v.to_string())
}
}
pub struct Expr<Req, S: SqlType> {
pub(crate) kind: ExprKind,
_marker: PhantomData<fn() -> (Req, S)>,
}
impl<Req, S: SqlType> Expr<Req, S> {
pub(crate) fn from_kind(kind: ExprKind) -> Self {
Expr {
kind,
_marker: PhantomData,
}
}
}
impl<Req, S: SqlType> Clone for Expr<Req, S> {
fn clone(&self) -> Self {
Expr::from_kind(self.kind.clone())
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` isn't a SQL expression",
label = "a column, a literal, an aggregate, or a `sql!{{}}` fragment is; a `label!` name is not",
note = "an `Option` isn't one either: asking about NULL is `.is_null()`, and assigning it is `null::<Text>()` — `= NULL` is never true in SQL"
)]
pub trait IntoExpr {
type Sql: SqlType;
type Req;
fn into_expr(self) -> Expr<Self::Req, Self::Sql>;
}
impl<Req, S: SqlType> IntoExpr for Expr<Req, S> {
type Sql = S;
type Req = Req;
fn into_expr(self) -> Expr<Req, S> {
self
}
}
#[diagnostic::on_unimplemented(
message = "a `{Self}` expression can't be assigned to a `{Column}` column",
label = "the value has to fit the column: the same type, a narrower number, or a non-null value for a nullable column"
)]
pub trait AssignsTo<Column: SqlType>: SqlType {}
impl<T: SqlType> AssignsTo<T> for T {}
impl<T: SqlType> AssignsTo<crate::scope::Nullable<T>> for T {}
mod writable {
pub trait Sealed {}
}
#[doc(hidden)]
pub use writable::Sealed as WritableSealed;
#[diagnostic::on_unimplemented(
message = "`{Self}` isn't a column a statement can assign to",
label = "a primary-key or generated column is the database's to write, which is why `*Update` leaves it out too"
)]
pub trait Writable: WritableSealed {}
pub trait ColumnKey: crate::row::Spelled + Copy + 'static {
type Table: Table;
type Sql: SqlType;
}
pub struct Column<C: ColumnKey>(PhantomData<C>);
impl<C: ColumnKey> Column<C> {
pub const fn new() -> Self {
Column(PhantomData)
}
}
impl<C: ColumnKey> Default for Column<C> {
fn default() -> Self {
Self::new()
}
}
impl<C: ColumnKey> Clone for Column<C> {
fn clone(&self) -> Self {
*self
}
}
impl<C: ColumnKey> Copy for Column<C> {}
impl<C: ColumnKey> IntoExpr for Column<C> {
type Sql = C::Sql;
type Req = Cons<C::Table, Nil>;
fn into_expr(self) -> Expr<Self::Req, C::Sql> {
Expr::from_kind(ExprKind::Column {
table: <C::Table as Table>::NAME,
name: <C as crate::row::Named>::NAME,
})
}
}
pub struct Keyed<K, Req, S: SqlType> {
pub(crate) kind: ExprKind,
_marker: PhantomData<fn() -> (K, Req, S)>,
}
impl<K, Req, S: SqlType> Keyed<K, Req, S> {
pub(crate) fn from_kind(kind: ExprKind) -> Self {
Keyed {
kind,
_marker: PhantomData,
}
}
}
impl<K, Req, S: SqlType> Clone for Keyed<K, Req, S> {
fn clone(&self) -> Self {
Keyed::from_kind(self.kind.clone())
}
}
impl<K, Req, S: SqlType> IntoExpr for Keyed<K, Req, S> {
type Sql = S;
type Req = Req;
fn into_expr(self) -> Expr<Req, S> {
Expr::from_kind(self.kind)
}
}
#[diagnostic::on_unimplemented(
message = "`{Self}` and `{Other}` aren't comparable",
label = "both sides of a comparison must be the same SQL type, or two numeric ones",
note = "nullability doesn't matter here: a `Nullable<T>` compares with a `T`"
)]
pub trait Comparable<Other: SqlType>: SqlType {}
impl<T: SqlType> Comparable<T> for T {}
impl<T: SqlType> Comparable<crate::scope::Nullable<T>> for T {}
impl<T: SqlType> Comparable<T> for crate::scope::Nullable<T> {}
macro_rules! comparable_across {
($($a:ty => $b:ty),+ $(,)?) => {
$(
impl Comparable<$b> for $a {}
impl Comparable<$b> for crate::scope::Nullable<$a> {}
impl Comparable<crate::scope::Nullable<$b>> for $a {}
impl Comparable<crate::scope::Nullable<$b>> for crate::scope::Nullable<$a> {}
)+
};
}
comparable_across!(
Integer => BigInt,
BigInt => Integer,
Integer => Real,
Real => Integer,
BigInt => Real,
Real => BigInt,
);
macro_rules! assigns_across {
($($from:ty => $to:ty),+ $(,)?) => {
$(
impl AssignsTo<$to> for $from {}
impl AssignsTo<crate::scope::Nullable<$to>> for $from {}
impl AssignsTo<crate::scope::Nullable<$to>> for crate::scope::Nullable<$from> {}
)+
};
}
assigns_across!(
Integer => BigInt,
Integer => Real,
BigInt => Real,
);
#[cfg(feature = "decimal")]
assigns_across!(
Integer => Numeric,
BigInt => Numeric,
Real => Numeric,
);
#[cfg(feature = "decimal")]
comparable_across!(
Numeric => Integer,
Integer => Numeric,
Numeric => BigInt,
BigInt => Numeric,
Numeric => Real,
Real => Numeric,
);
pub trait LabelKey: crate::row::Spelled + Copy + 'static {}
pub type Declared<Req, S> = Keyed<crate::row::Anon, Req, S>;
impl<Req, S: SqlType> Expr<Req, S> {
pub fn decodes_as<S2: SqlType>(self) -> Declared<Req, S2> {
Keyed {
kind: self.kind,
_marker: PhantomData,
}
}
}
pub struct Labeled<K, Inner> {
pub(crate) inner: Inner,
_key: PhantomData<fn() -> K>,
}
impl<K, Inner: Clone> Clone for Labeled<K, Inner> {
fn clone(&self) -> Self {
Labeled::new(self.inner.clone())
}
}
impl<K, Inner> Labeled<K, Inner> {
pub(crate) fn new(inner: Inner) -> Self {
Labeled {
inner,
_key: PhantomData,
}
}
}
pub trait LabelExt: Sized {
fn label<K: LabelKey>(self, _key: K) -> Labeled<K, Self> {
Labeled::new(self)
}
}
impl<C: ColumnKey> LabelExt for Column<C> {}
impl<K, Req, S: SqlType> LabelExt for Keyed<K, Req, S> {}
impl<Req, S: SqlType> LabelExt for Expr<Req, S> {}
#[allow(clippy::wrong_self_convention)]
pub trait ExprMethods: IntoExpr + Sized {
fn eq<Rhs: IntoExpr>(self, rhs: Rhs) -> Expr<<Self::Req as Concat<Rhs::Req>>::Output, Bool>
where
Self::Sql: Comparable<Rhs::Sql>,
Self::Req: Concat<Rhs::Req>,
{
bin_op(BinOp::Eq, self, rhs)
}
fn ne<Rhs: IntoExpr>(self, rhs: Rhs) -> Expr<<Self::Req as Concat<Rhs::Req>>::Output, Bool>
where
Self::Sql: Comparable<Rhs::Sql>,
Self::Req: Concat<Rhs::Req>,
{
bin_op(BinOp::Ne, self, rhs)
}
fn lt<Rhs: IntoExpr>(self, rhs: Rhs) -> Expr<<Self::Req as Concat<Rhs::Req>>::Output, Bool>
where
Self::Sql: Comparable<Rhs::Sql>,
Self::Req: Concat<Rhs::Req>,
{
bin_op(BinOp::Lt, self, rhs)
}
fn lte<Rhs: IntoExpr>(self, rhs: Rhs) -> Expr<<Self::Req as Concat<Rhs::Req>>::Output, Bool>
where
Self::Sql: Comparable<Rhs::Sql>,
Self::Req: Concat<Rhs::Req>,
{
bin_op(BinOp::Lte, self, rhs)
}
fn gt<Rhs: IntoExpr>(self, rhs: Rhs) -> Expr<<Self::Req as Concat<Rhs::Req>>::Output, Bool>
where
Self::Sql: Comparable<Rhs::Sql>,
Self::Req: Concat<Rhs::Req>,
{
bin_op(BinOp::Gt, self, rhs)
}
fn gte<Rhs: IntoExpr>(self, rhs: Rhs) -> Expr<<Self::Req as Concat<Rhs::Req>>::Output, Bool>
where
Self::Sql: Comparable<Rhs::Sql>,
Self::Req: Concat<Rhs::Req>,
{
bin_op(BinOp::Gte, self, rhs)
}
fn is_null(self) -> Expr<Self::Req, Bool> {
Expr::from_kind(ExprKind::IsNull {
expr: Box::new(self.into_expr().kind),
negated: false,
})
}
fn is_not_null(self) -> Expr<Self::Req, Bool> {
Expr::from_kind(ExprKind::IsNull {
expr: Box::new(self.into_expr().kind),
negated: true,
})
}
fn and<Rhs: IntoExpr>(self, rhs: Rhs) -> Expr<<Self::Req as Concat<Rhs::Req>>::Output, Bool>
where
Self::Sql: BoolLike,
Rhs::Sql: BoolLike,
Self::Req: Concat<Rhs::Req>,
{
Expr::from_kind(ExprKind::And(
Box::new(self.into_expr().kind),
Box::new(rhs.into_expr().kind),
))
}
fn or<Rhs: IntoExpr>(self, rhs: Rhs) -> Expr<<Self::Req as Concat<Rhs::Req>>::Output, Bool>
where
Self::Sql: BoolLike,
Rhs::Sql: BoolLike,
Self::Req: Concat<Rhs::Req>,
{
Expr::from_kind(ExprKind::Or(
Box::new(self.into_expr().kind),
Box::new(rhs.into_expr().kind),
))
}
fn like<Rhs: IntoExpr>(self, rhs: Rhs) -> Expr<<Self::Req as Concat<Rhs::Req>>::Output, Bool>
where
Self::Sql: TextLike,
Rhs::Sql: TextLike,
Self::Req: Concat<Rhs::Req>,
{
bin_op(BinOp::Like, self, rhs)
}
fn is_in<I>(self, values: I) -> Expr<Self::Req, Bool>
where
I: IntoIterator,
I::Item: IntoExpr<Req = Nil>,
Self::Sql: Comparable<<I::Item as IntoExpr>::Sql>,
{
let values: Vec<ExprKind> = values.into_iter().map(|v| v.into_expr().kind).collect();
if values.is_empty() {
return Expr::from_kind(ExprKind::Always(false));
}
Expr::from_kind(ExprKind::InList {
expr: Box::new(self.into_expr().kind),
values,
})
}
}
pub fn any_of<Req, C: IntoExpr<Req = Req>>(conds: impl IntoIterator<Item = C>) -> Expr<Req, Bool>
where
C::Sql: BoolLike,
{
combine(conds, false)
}
pub fn all_of<Req, C: IntoExpr<Req = Req>>(conds: impl IntoIterator<Item = C>) -> Expr<Req, Bool>
where
C::Sql: BoolLike,
{
combine(conds, true)
}
fn combine<Req, C: IntoExpr<Req = Req>>(
conds: impl IntoIterator<Item = C>,
all: bool,
) -> Expr<Req, Bool>
where
C::Sql: BoolLike,
{
Expr::from_kind(fold_conditions(
conds.into_iter().map(|c| c.into_expr().kind),
all,
))
}
pub(crate) fn fold_conditions(kinds: impl IntoIterator<Item = ExprKind>, all: bool) -> ExprKind {
let mut folded: Option<ExprKind> = None;
for kind in kinds {
folded = Some(match folded {
None => kind,
Some(acc) if all => ExprKind::And(Box::new(acc), Box::new(kind)),
Some(acc) => ExprKind::Or(Box::new(acc), Box::new(kind)),
});
}
folded.unwrap_or(ExprKind::Always(all))
}
impl<T: IntoExpr> ExprMethods for T {}
fn bin_op<Lhs, Rhs>(
op: BinOp,
lhs: Lhs,
rhs: Rhs,
) -> Expr<<Lhs::Req as Concat<Rhs::Req>>::Output, Bool>
where
Lhs: IntoExpr,
Rhs: IntoExpr,
Lhs::Req: Concat<Rhs::Req>,
{
Expr::from_kind(ExprKind::BinOp {
op,
lhs: Box::new(lhs.into_expr().kind),
rhs: Box::new(rhs.into_expr().kind),
})
}
#[diagnostic::on_unimplemented(
message = "`LIKE` needs a text expression, and `{Self}` isn't one",
label = "only `Text` and `Nullable<Text>` columns and expressions accept `.like(..)`"
)]
pub trait TextLike: SqlType {}
impl TextLike for Text {}
impl TextLike for crate::scope::Nullable<Text> {}
#[diagnostic::on_unimplemented(
message = "a condition has to be a boolean expression, and `{Self}` isn't one",
label = "expected `Bool` or `Nullable<Bool>`"
)]
pub trait BoolLike: SqlType {}
impl BoolLike for Bool {}
impl BoolLike for crate::scope::Nullable<Bool> {}
impl<Req, S: BoolLike> std::ops::Not for Expr<Req, S> {
type Output = Expr<Req, S>;
fn not(self) -> Self::Output {
Expr::from_kind(ExprKind::Not(Box::new(self.kind)))
}
}
impl<K, Req, S: BoolLike> std::ops::Not for Keyed<K, Req, S> {
type Output = Keyed<K, Req, S>;
fn not(self) -> Self::Output {
Keyed::from_kind(ExprKind::Not(Box::new(self.kind)))
}
}
impl<C: ColumnKey> std::ops::Not for Column<C>
where
C::Sql: BoolLike,
{
type Output = Expr<Cons<C::Table, Nil>, C::Sql>;
fn not(self) -> Self::Output {
Expr::from_kind(ExprKind::Not(Box::new(self.into_expr().kind)))
}
}
pub trait NullValue: SqlType {
const NULL_VALUE: Value;
}
pub fn null<S: NullValue>() -> Expr<Nil, crate::scope::Nullable<S>> {
Expr::from_kind(ExprKind::Value(S::NULL_VALUE))
}
mod raw_arg {
pub trait Sealed {}
impl<T: super::IntoExpr> Sealed for T {}
}
macro_rules! sql_leaf_type {
($name:ident, $native:ty, $null_variant:ident) => {
pub struct $name;
impl sql_type::Sealed for $name {}
impl SqlType for $name {
type Native = $native;
}
impl crate::scope::wrap::Sealed<MaybeNull> for $name {}
impl crate::scope::WrapNullable<MaybeNull> for $name {
type Output = crate::scope::Nullable<$name>;
}
impl IntoExpr for $native {
type Sql = $name;
type Req = Nil;
fn into_expr(self) -> Expr<Nil, $name> {
Expr::from_kind(ExprKind::Value(Value::from(self)))
}
}
impl crate::select::SingleColumn for $native {}
impl crate::select::SingleColumn for ::std::option::Option<$native> {}
impl NullValue for $name {
const NULL_VALUE: Value = Value::$null_variant;
}
impl crate::insert::IntoColumnValue<$native> for $native {
fn into_column_value(self) -> $native {
self
}
}
impl crate::insert::IntoColumnValue<::std::option::Option<$native>> for $native {
fn into_column_value(self) -> ::std::option::Option<$native> {
::std::option::Option::Some(self)
}
}
impl crate::insert::IntoColumnValue<::std::option::Option<$native>>
for ::std::option::Option<$native>
{
fn into_column_value(self) -> ::std::option::Option<$native> {
self
}
}
impl crate::insert::IntoColumnValue<crate::insert::Defaultable<$native>> for $native {
fn into_column_value(self) -> crate::insert::Defaultable<$native> {
crate::insert::Defaultable::Value(self)
}
}
impl crate::insert::IntoColumnValue<crate::insert::Defaultable<$native>>
for ::std::option::Option<$native>
{
fn into_column_value(self) -> crate::insert::Defaultable<$native> {
match self {
::std::option::Option::Some(v) => crate::insert::Defaultable::Value(v),
::std::option::Option::None => crate::insert::Defaultable::Default,
}
}
}
impl
crate::insert::IntoColumnValue<
crate::insert::Defaultable<::std::option::Option<$native>>,
> for $native
{
fn into_column_value(
self,
) -> crate::insert::Defaultable<::std::option::Option<$native>> {
crate::insert::Defaultable::Value(::std::option::Option::Some(self))
}
}
impl
crate::insert::IntoColumnValue<
crate::insert::Defaultable<::std::option::Option<$native>>,
> for ::std::option::Option<$native>
{
fn into_column_value(
self,
) -> crate::insert::Defaultable<::std::option::Option<$native>> {
match self {
::std::option::Option::Some(v) => {
crate::insert::Defaultable::Value(::std::option::Option::Some(v))
}
::std::option::Option::None => crate::insert::Defaultable::Default,
}
}
}
impl raw_arg::Sealed for ::std::option::Option<$native> {}
impl RawArg for ::std::option::Option<$native> {
type Req = Nil;
fn into_raw_arg(self) -> RawSlot {
RawSlot(ExprKind::Value(Value::from(self)))
}
}
impl crate::row::SameShape<$native> for $native {}
impl crate::row::SameShape<::std::option::Option<$native>>
for ::std::option::Option<$native>
{
}
impl From<::std::option::Option<$native>> for Value {
fn from(v: ::std::option::Option<$native>) -> Self {
match v {
::std::option::Option::Some(x) => Value::from(x),
::std::option::Option::None => Value::$null_variant,
}
}
}
};
}
sql_leaf_type!(Integer, i32, NullI32);
sql_leaf_type!(BigInt, i64, NullI64);
sql_leaf_type!(Real, f64, NullF64);
sql_leaf_type!(Text, String, NullText);
sql_leaf_type!(Bool, bool, NullBool);
sql_leaf_type!(Bytes, Vec<u8>, NullBytes);
#[cfg(feature = "chrono")]
sql_leaf_type!(Timestamptz, chrono::DateTime<chrono::Utc>, NullTimestamptz);
#[cfg(feature = "chrono")]
sql_leaf_type!(Date, chrono::NaiveDate, NullDate);
#[cfg(feature = "uuid")]
sql_leaf_type!(Uuid, uuid::Uuid, NullUuid);
#[cfg(feature = "decimal")]
sql_leaf_type!(Numeric, rust_decimal::Decimal, NullNumeric);
impl IntoExpr for &String {
type Sql = Text;
type Req = Nil;
fn into_expr(self) -> Expr<Nil, Text> {
Expr::from_kind(ExprKind::Value(Value::Text(self.clone())))
}
}
impl IntoExpr for &str {
type Sql = Text;
type Req = Nil;
fn into_expr(self) -> Expr<Nil, Text> {
Expr::from_kind(ExprKind::Value(Value::Text(self.to_string())))
}
}
macro_rules! text_column_value {
($borrowed:ty) => {
impl crate::insert::IntoColumnValue<String> for $borrowed {
fn into_column_value(self) -> String {
self.to_string()
}
}
impl crate::insert::IntoColumnValue<Option<String>> for $borrowed {
fn into_column_value(self) -> Option<String> {
Some(self.to_string())
}
}
impl crate::insert::IntoColumnValue<crate::insert::Defaultable<String>> for $borrowed {
fn into_column_value(self) -> crate::insert::Defaultable<String> {
crate::insert::Defaultable::Value(self.to_string())
}
}
impl crate::insert::IntoColumnValue<crate::insert::Defaultable<Option<String>>>
for $borrowed
{
fn into_column_value(self) -> crate::insert::Defaultable<Option<String>> {
crate::insert::Defaultable::Value(Some(self.to_string()))
}
}
};
}
text_column_value!(&str);
text_column_value!(&String);
impl<S: SqlType> sql_type::Sealed for crate::scope::Nullable<S> {}
impl<S: SqlType> SqlType for crate::scope::Nullable<S> {
type Native = Option<S::Native>;
}
crate::row::expr_key!(
Count,
HasCount,
count,
"The identity a selected `count(*)` is filed under in a row.",
'c',
'o',
'u',
'n',
't'
);
pub(crate) fn count_item() -> crate::render::SelectItem {
crate::render::SelectItem::bare(count_star())
}
fn count_star() -> ExprKind {
ExprKind::Func {
name: "count",
arg: None,
}
}
pub fn count() -> Keyed<Count, Nil, BigInt> {
Keyed::from_kind(count_star())
}
#[diagnostic::on_unimplemented(
message = "`min`/`max` need an ordered expression, and `{Self}` isn't one",
label = "numbers, text, and dates/timestamps are ordered; booleans, bytes and UUIDs are not — `bool_or`/`bool_and` are the aggregate a flag wants, and aren't built yet"
)]
pub trait Ordered: SqlType + WrapNullable<MaybeNull> {}
impl Ordered for Integer {}
impl Ordered for BigInt {}
impl Ordered for Real {}
impl Ordered for Text {}
#[cfg(feature = "decimal")]
impl Ordered for Numeric {}
#[cfg(feature = "chrono")]
impl Ordered for Timestamptz {}
#[cfg(feature = "chrono")]
impl Ordered for Date {}
impl<S: Ordered> Ordered for crate::scope::Nullable<S> {}
#[diagnostic::on_unimplemented(
message = "`sum`/`avg` need a numeric expression, and `{Self}` isn't one",
label = "only `Integer`, `BigInt` and `Real` columns and expressions (and `Numeric`, with the `decimal` feature) can be summed or averaged"
)]
pub trait Summable: SqlType {
type Sum: SqlType;
const SUM_CAST: Option<CastTarget>;
const AVG_CAST: Option<CastTarget> = Some(CastTarget::Double);
}
impl Summable for Integer {
type Sum = crate::scope::Nullable<BigInt>;
const SUM_CAST: Option<CastTarget> = None;
}
impl Summable for BigInt {
type Sum = crate::scope::Nullable<BigInt>;
const SUM_CAST: Option<CastTarget> = Some(CastTarget::BigInt);
}
impl Summable for Real {
type Sum = crate::scope::Nullable<Real>;
const SUM_CAST: Option<CastTarget> = None;
const AVG_CAST: Option<CastTarget> = None;
}
#[cfg(feature = "decimal")]
impl Summable for Numeric {
type Sum = crate::scope::Nullable<Numeric>;
const SUM_CAST: Option<CastTarget> = None;
const AVG_CAST: Option<CastTarget> = Some(CastTarget::Double);
}
impl<T: Summable> Summable for crate::scope::Nullable<T> {
type Sum = T::Sum;
const SUM_CAST: Option<CastTarget> = T::SUM_CAST;
const AVG_CAST: Option<CastTarget> = T::AVG_CAST;
}
pub struct Agg<Op, C>(PhantomData<fn() -> (Op, C)>);
#[doc(hidden)]
impl<Op, C: crate::row::Named> crate::row::NamedSealed for Agg<Op, C> {}
#[doc(hidden)]
impl<Op: 'static, C: crate::row::Named + 'static> crate::row::Named for Agg<Op, C> {
type Name = <C as crate::row::Named>::Name;
const NAME: &'static str = <C as crate::row::Named>::NAME;
}
#[doc(hidden)]
impl<Op: 'static, C: crate::row::Spelled + 'static> crate::row::Spelled for Agg<Op, C> {}
fn aggregate_kind<C: ColumnKey>(name: &'static str, cast: Option<CastTarget>) -> ExprKind {
let call = ExprKind::Func {
name,
arg: Some(Box::new(ExprKind::Column {
table: <C::Table as Table>::NAME,
name: <C as crate::row::Named>::NAME,
})),
};
match cast {
Some(target) => ExprKind::Cast {
expr: Box::new(call),
target,
},
None => call,
}
}
macro_rules! aggregate {
($op:ident, $func:ident, $sql:literal, $out:ty, $bound:path, $cast:expr, $doc:literal) => {
#[doc = $doc]
pub struct $op;
#[doc = $doc]
pub fn $func<C: ColumnKey>(
_column: Column<C>,
) -> Keyed<Agg<$op, C>, Cons<C::Table, Nil>, $out>
where
C::Sql: $bound,
$out: SqlType,
{
Keyed::from_kind(aggregate_kind::<C>($sql, $cast))
}
};
}
aggregate!(
Sum,
sum,
"sum",
<C::Sql as Summable>::Sum,
Summable,
<C::Sql as Summable>::SUM_CAST,
"`sum(column)`. NULL over zero rows, so the result is always nullable."
);
aggregate!(
Min,
min,
"min",
<C::Sql as WrapNullable<MaybeNull>>::Output,
Ordered,
None,
"`min(column)`. NULL over zero rows."
);
aggregate!(
Max,
max,
"max",
<C::Sql as WrapNullable<MaybeNull>>::Output,
Ordered,
None,
"`max(column)`. NULL over zero rows."
);
aggregate!(
Avg,
avg,
"avg",
crate::scope::Nullable<Real>,
Summable,
<C::Sql as Summable>::AVG_CAST,
"`avg(column)`. NULL over zero rows."
);
aggregate!(
CountOf,
count_of,
"count",
BigInt,
SqlType,
None,
"`count(column)` — non-NULL values, unlike `count()`'s `count(*)` rows."
);
pub trait RawArg: raw_arg::Sealed {
type Req;
#[doc(hidden)]
fn into_raw_arg(self) -> RawSlot;
}
impl<T: IntoExpr> RawArg for T {
type Req = T::Req;
fn into_raw_arg(self) -> RawSlot {
RawSlot(self.into_expr().kind)
}
}
pub struct RawSlot(ExprKind);
impl RawSlot {
fn into_kind(self) -> ExprKind {
self.0
}
}
#[diagnostic::on_unimplemented(
message = "a `sql!` fragment takes at most 8 `?` slots",
label = "split the fragment, or fold part of it into the builder"
)]
pub trait RawArgs {
type Req;
#[doc(hidden)]
fn into_raw_args(self) -> Vec<RawSlot>;
}
impl RawArgs for () {
type Req = Nil;
fn into_raw_args(self) -> Vec<RawSlot> {
Vec::new()
}
}
macro_rules! raw_args_tuple {
($head:ident $(, $rest:ident)*) => {
#[allow(non_snake_case)]
impl<$head: RawArg $(, $rest: RawArg)*> RawArgs for ($head, $($rest,)*)
where
($($rest,)*): RawArgs,
$head::Req: Concat<<($($rest,)*) as RawArgs>::Req>,
{
type Req = <$head::Req as Concat<<($($rest,)*) as RawArgs>::Req>>::Output;
fn into_raw_args(self) -> Vec<RawSlot> {
let ($head, $($rest,)*) = self;
let mut kinds = ::std::vec![RawArg::into_raw_arg($head)];
kinds.extend(RawArgs::into_raw_args(($($rest,)*)));
kinds
}
}
};
}
raw_args_tuple!(A);
raw_args_tuple!(A, B);
raw_args_tuple!(A, B, C);
raw_args_tuple!(A, B, C, D);
raw_args_tuple!(A, B, C, D, E);
raw_args_tuple!(A, B, C, D, E, F);
raw_args_tuple!(A, B, C, D, E, F, G);
raw_args_tuple!(A, B, C, D, E, F, G, H);
#[doc(hidden)]
pub const fn placeholder_count(sql: &str) -> usize {
let bytes = sql.as_bytes();
let mut i = 0;
let mut count = 0;
while i < bytes.len() {
if bytes[i] == b'?' {
count += 1;
}
i += 1;
}
count
}
#[doc(hidden)]
pub fn raw_expr<S: SqlType, Args: RawArgs>(
sql: &'static str,
args: Args,
) -> Declared<Args::Req, S> {
Keyed::from_kind(template(sql, args.into_raw_args()))
}
fn template(sql: &'static str, args: Vec<RawSlot>) -> ExprKind {
let mut pieces: Vec<String> = vec![String::new()];
for c in sql.chars() {
match c {
'?' => pieces.push(String::new()),
c => pieces.last_mut().expect("one piece to start").push(c),
}
}
let mut pieces = pieces.into_iter();
let head = pieces.next().unwrap_or_default();
let rest = args
.into_iter()
.map(RawSlot::into_kind)
.zip(pieces)
.collect();
ExprKind::Template { head, rest }
}
#[doc(hidden)]
pub fn placeholder<S: SqlType>(name: &'static str) -> Expr<Nil, S> {
Expr::from_kind(ExprKind::Value(Value::Placeholder(name)))
}