use std::marker::PhantomData;
use crate::dialect::Dialect;
use crate::expr::{AssignsTo, Column, ColumnKey, ExprKind, IntoExpr, Value, Writable};
use crate::render::{QuerySink, Sink, render_and_list, render_expr, render_ident};
use crate::scope::{BaseTable, Superset, Table};
use crate::select::{Condition, Predicate};
use crate::statement::{Statement, WrittenTable};
pub trait UpdateRow: private::Sealed {
type Table: Table;
fn sets(self) -> Vec<(&'static str, Value)>;
}
mod private {
pub trait Sealed {}
}
#[doc(hidden)]
pub use private::Sealed as UpdateRowSealed;
pub struct Assignments<T> {
sets: Vec<(&'static str, ExprKind)>,
_marker: PhantomData<fn() -> T>,
}
impl<T> Clone for Assignments<T> {
fn clone(&self) -> Self {
Assignments {
sets: self.sets.clone(),
_marker: PhantomData,
}
}
}
impl<T> std::fmt::Debug for Assignments<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Assignments")
.field("sets", &self.sets)
.finish()
}
}
impl<T: Table> Assignments<T> {
pub fn set_to<C, V, Idxs>(_column: Column<C>, value: V) -> Self
where
C: ColumnKey<Table = T> + Writable,
V: IntoExpr,
V::Sql: AssignsTo<C::Sql>,
WrittenTable<T>: Superset<V::Req, Idxs>,
{
Assignments {
sets: vec![(<C as crate::row::Named>::NAME, value.into_expr().kind)],
_marker: PhantomData,
}
}
pub fn and_set_to<C, V, Idxs>(mut self, _column: Column<C>, value: V) -> Self
where
C: ColumnKey<Table = T> + Writable,
V: IntoExpr,
V::Sql: AssignsTo<C::Sql>,
WrittenTable<T>: Superset<V::Req, Idxs>,
{
let name = <C as crate::row::Named>::NAME;
self.sets.retain(|(col, _)| *col != name);
self.sets.push((name, value.into_expr().kind));
self
}
}
impl<T> Assignments<T> {
pub(crate) fn render_into<D: Dialect>(&self, sink: &mut dyn Sink) {
for (i, (col, value)) in self.sets.iter().enumerate() {
if i > 0 {
sink.text(", ");
}
render_ident::<D>(sink, col);
sink.text(" = ");
render_expr::<D>(value, sink);
}
}
pub fn from_row<R: UpdateRow<Table = T>>(row: R) -> Result<Self, NothingToSet> {
let mut sets: Vec<(&'static str, ExprKind)> = Vec::new();
for (col, value) in row.sets() {
sets.retain(|(name, _)| *name != col);
sets.push((col, ExprKind::Value(value)));
}
if sets.is_empty() {
return Err(NothingToSet);
}
Ok(Assignments {
sets,
_marker: PhantomData,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NothingToSet;
impl std::fmt::Display for NothingToSet {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_str("an UPDATE must set at least one column, but every field of this `*Update` is untouched")
}
}
impl std::error::Error for NothingToSet {}
pub struct UpdateSeed<D, T> {
_marker: PhantomData<fn() -> (D, T)>,
}
pub fn update<D, T: BaseTable>(_table: T) -> UpdateSeed<D, T> {
UpdateSeed {
_marker: PhantomData,
}
}
impl<D, T: Table> UpdateSeed<D, T> {
pub fn correlated<S, InnerSel>(
&self,
source: S,
selection: InnerSel,
) -> crate::select::Select<
D,
crate::scope::Cons<
crate::scope::TableSlot<S::Table, crate::scope::NotNull>,
WrittenTable<T>,
>,
InnerSel,
WrittenTable<T>,
>
where
S: crate::select::JoinSource<D>,
{
crate::select::correlated_with(source, selection)
}
pub fn set_to<C, V, Idxs>(self, column: Column<C>, value: V) -> Update<D, T>
where
C: ColumnKey<Table = T> + Writable,
V: IntoExpr,
V::Sql: AssignsTo<C::Sql>,
WrittenTable<T>: Superset<V::Req, Idxs>,
{
Update {
sets: Assignments::set_to(column, value),
wheres: Vec::new(),
_marker: PhantomData,
}
}
pub fn set(self, sets: Assignments<T>) -> Update<D, T> {
Update {
sets,
wheres: Vec::new(),
_marker: PhantomData,
}
}
}
fn render_set_clause<D: Dialect, T: Table>(
sets: &Assignments<T>,
wheres: &[ExprKind],
) -> QuerySink<D> {
let mut sink = QuerySink::<D>::new();
sink.text("UPDATE ");
render_ident::<D>(&mut sink, T::NAME);
sink.text(" SET ");
sets.render_into::<D>(&mut sink);
render_and_list::<D>(&mut sink, " WHERE ", wheres);
sink
}
pub struct Update<D, T: Table> {
sets: Assignments<T>,
wheres: Vec<ExprKind>,
_marker: PhantomData<fn() -> (D, T)>,
}
impl<D, T: Table> Update<D, T> {
pub fn correlated<S, InnerSel>(
&self,
source: S,
selection: InnerSel,
) -> crate::select::Select<
D,
crate::scope::Cons<
crate::scope::TableSlot<S::Table, crate::scope::NotNull>,
WrittenTable<T>,
>,
InnerSel,
WrittenTable<T>,
>
where
S: crate::select::JoinSource<D>,
{
crate::select::correlated_with(source, selection)
}
pub fn filter<C: Condition<D, WrittenTable<T>, Idxs>, Idxs>(mut self, cond: C) -> Self {
self.wheres.push(cond.into_predicate().into_kind());
self
}
pub fn filter_all(
mut self,
conds: impl IntoIterator<Item = Predicate<D, WrittenTable<T>>>,
) -> Self {
self.wheres
.extend(conds.into_iter().map(Predicate::into_kind));
self
}
}
impl<D, T: Table> Update<D, T> {
pub fn set_to<C, V, Idxs>(mut self, column: Column<C>, value: V) -> Self
where
C: ColumnKey<Table = T> + Writable,
V: IntoExpr,
V::Sql: AssignsTo<C::Sql>,
WrittenTable<T>: Superset<V::Req, Idxs>,
{
self.sets = self.sets.and_set_to(column, value);
self
}
}
impl<D: Dialect, T: Table> crate::statement::private::Sealed for Update<D, T> {}
impl<D: Dialect, T: Table> Statement for Update<D, T> {
type Dialect = D;
type Table = T;
fn render(&self) -> QuerySink<D> {
render_set_clause::<D, T>(&self.sets, &self.wheres)
}
}