#[cfg(not(feature = "std"))]
use crate::prelude::*;
use crate::traits::PostgresTable;
use crate::values::PostgresValue;
use core::fmt::Debug;
use core::marker::PhantomData;
use drizzle_core::{ConflictTarget, NamedConstraint, SQL, ToSQL, Token};
use super::ExecutableState;
#[derive(Debug, Clone, Copy, Default)]
pub struct InsertInitial;
#[derive(Debug, Clone, Copy, Default)]
pub struct InsertValuesSet;
#[derive(Debug, Clone, Copy, Default)]
pub struct InsertReturningSet;
#[derive(Debug, Clone, Copy, Default)]
pub struct InsertOnConflictSet;
#[derive(Debug, Clone, Copy, Default)]
pub struct InsertDoUpdateSet;
impl ExecutableState for InsertValuesSet {}
impl ExecutableState for InsertReturningSet {}
impl ExecutableState for InsertOnConflictSet {}
impl ExecutableState for InsertDoUpdateSet {}
#[derive(Debug, Clone)]
enum ConflictTargetKind<'a> {
Columns(Box<SQL<'a, PostgresValue<'a>>>),
Constraint(&'static str),
}
#[derive(Debug, Clone)]
pub struct OnConflictBuilder<'a, S, T> {
sql: SQL<'a, PostgresValue<'a>>,
target: ConflictTargetKind<'a>,
target_where: Option<SQL<'a, PostgresValue<'a>>>,
schema: PhantomData<S>,
table: PhantomData<T>,
}
impl<'a, S, T> OnConflictBuilder<'a, S, T> {
#[must_use]
pub fn r#where<E>(mut self, condition: E) -> Self
where
E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
E::SQLType: drizzle_core::types::BooleanLike,
{
self.target_where = Some(condition.to_sql());
self
}
fn into_parts(self) -> (SQL<'a, PostgresValue<'a>>, SQL<'a, PostgresValue<'a>>) {
let target = match self.target {
ConflictTargetKind::Columns(cols) => {
let mut t = SQL::from_iter([Token::ON, Token::CONFLICT, Token::LPAREN])
.append(*cols)
.push(Token::RPAREN);
if let Some(tw) = self.target_where {
t = t.push(Token::WHERE).append(tw);
}
t
}
ConflictTargetKind::Constraint(name) => {
SQL::from_iter([Token::ON, Token::CONFLICT, Token::ON, Token::CONSTRAINT])
.append(SQL::ident(name))
}
};
(self.sql, target)
}
#[must_use]
pub fn do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
let (sql, target) = self.into_parts();
InsertBuilder {
sql: sql.append(target.push(Token::DO).push(Token::NOTHING)),
schema: PhantomData,
state: PhantomData,
table: PhantomData,
marker: PhantomData,
row: PhantomData,
grouped: PhantomData,
}
}
pub fn do_update(
self,
set: impl ToSQL<'a, PostgresValue<'a>>,
) -> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
let (sql, target) = self.into_parts();
let conflict = target
.push(Token::DO)
.push(Token::UPDATE)
.push(Token::SET)
.append(set.to_sql());
InsertBuilder {
sql: sql.append(conflict),
schema: PhantomData,
state: PhantomData,
table: PhantomData,
marker: PhantomData,
row: PhantomData,
grouped: PhantomData,
}
}
}
pub type InsertBuilder<'a, Schema, State, Table, Marker = (), Row = ()> =
super::QueryBuilder<'a, Schema, State, Table, Marker, Row>;
type ReturningMarker<Table, Columns> = drizzle_core::Scoped<
<Columns as drizzle_core::IntoSelectTarget>::Marker,
drizzle_core::Cons<Table, drizzle_core::Nil>,
>;
type ReturningRow<Table, Columns> =
<<Columns as drizzle_core::IntoSelectTarget>::Marker as drizzle_core::ResolveRow<Table>>::Row;
type ReturningBuilder<'a, S, T, Columns> = InsertBuilder<
'a,
S,
InsertReturningSet,
T,
ReturningMarker<T, Columns>,
ReturningRow<T, Columns>,
>;
impl<'a, Schema, Table> InsertBuilder<'a, Schema, InsertInitial, Table>
where
Table: PostgresTable<'a>,
{
#[inline]
pub fn value<T>(
self,
value: Table::Insert<T>,
) -> InsertBuilder<'a, Schema, InsertValuesSet, Table> {
self.values([value])
}
#[inline]
pub fn values<I, T>(self, values: I) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
where
I: IntoIterator<Item = Table::Insert<T>>,
{
let sql = crate::helpers::values::<'a, Table, T>(values);
InsertBuilder {
sql: self.sql.append(sql),
schema: PhantomData,
state: PhantomData,
table: PhantomData,
marker: PhantomData,
row: PhantomData,
grouped: PhantomData,
}
}
}
impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
pub fn on_conflict<C: ConflictTarget<T>>(self, target: C) -> OnConflictBuilder<'a, S, T> {
let columns = target.conflict_columns();
let target_sql = SQL::join(columns.iter().map(|c| SQL::ident(*c)), Token::COMMA);
OnConflictBuilder {
sql: self.sql,
target: ConflictTargetKind::Columns(Box::new(target_sql)),
target_where: None,
schema: PhantomData,
table: PhantomData,
}
}
pub fn on_conflict_on_constraint<C: NamedConstraint<T>>(
self,
target: C,
) -> OnConflictBuilder<'a, S, T> {
OnConflictBuilder {
sql: self.sql,
target: ConflictTargetKind::Constraint(target.constraint_name()),
target_where: None,
schema: PhantomData,
table: PhantomData,
}
}
#[must_use]
pub fn on_conflict_do_nothing(self) -> InsertBuilder<'a, S, InsertOnConflictSet, T> {
let conflict_sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING]);
InsertBuilder {
sql: self.sql.append(conflict_sql),
schema: PhantomData,
state: PhantomData,
table: PhantomData,
marker: PhantomData,
row: PhantomData,
grouped: PhantomData,
}
}
#[inline]
pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
where
Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
Columns::Marker: drizzle_core::ResolveRow<T>,
{
let returning_sql = crate::helpers::returning(columns);
InsertBuilder {
sql: self.sql.append(returning_sql),
schema: PhantomData,
state: PhantomData,
table: PhantomData,
marker: PhantomData,
row: PhantomData,
grouped: PhantomData,
}
}
}
impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
#[inline]
pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
where
Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
Columns::Marker: drizzle_core::ResolveRow<T>,
{
let returning_sql = crate::helpers::returning(columns);
InsertBuilder {
sql: self.sql.append(returning_sql),
schema: PhantomData,
state: PhantomData,
table: PhantomData,
marker: PhantomData,
row: PhantomData,
grouped: PhantomData,
}
}
}
impl<'a, S, T> InsertBuilder<'a, S, InsertDoUpdateSet, T> {
pub fn r#where<E>(self, condition: E) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
where
E: drizzle_core::expr::Expr<'a, PostgresValue<'a>>,
E::SQLType: drizzle_core::types::BooleanLike,
{
let sql = self.sql.push(Token::WHERE).append(condition.to_sql());
InsertBuilder {
sql,
schema: PhantomData,
state: PhantomData,
table: PhantomData,
marker: PhantomData,
row: PhantomData,
grouped: PhantomData,
}
}
#[inline]
pub fn returning<Columns>(self, columns: Columns) -> ReturningBuilder<'a, S, T, Columns>
where
Columns: ToSQL<'a, PostgresValue<'a>> + drizzle_core::IntoSelectTarget,
Columns::Marker: drizzle_core::ResolveRow<T>,
{
let returning_sql = crate::helpers::returning(columns);
InsertBuilder {
sql: self.sql.append(returning_sql),
schema: PhantomData,
state: PhantomData,
table: PhantomData,
marker: PhantomData,
row: PhantomData,
grouped: PhantomData,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use drizzle_core::{SQL, ToSQL};
#[test]
fn test_insert_builder_creation() {
let builder = InsertBuilder::<(), InsertInitial, ()> {
sql: SQL::raw("INSERT INTO test"),
schema: PhantomData,
state: PhantomData,
table: PhantomData,
marker: PhantomData,
row: PhantomData,
grouped: PhantomData,
};
assert_eq!(builder.to_sql().sql(), "INSERT INTO test");
}
}