use crate::traits::SQLiteTable;
use crate::values::SQLiteValue;
use drizzle_core::{SQL, SQLModel, ToSQL, Token};
use std::fmt::Debug;
use std::marker::PhantomData;
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;
impl InsertInitial {
#[inline]
pub const fn new() -> Self {
Self
}
}
impl InsertValuesSet {
#[inline]
pub const fn new() -> Self {
Self
}
}
impl InsertReturningSet {
#[inline]
pub const fn new() -> Self {
Self
}
}
impl InsertOnConflictSet {
#[inline]
pub const fn new() -> Self {
Self
}
}
#[derive(Debug, Clone)]
pub enum Conflict<
'a,
T: IntoIterator<Item: ToSQL<'a, SQLiteValue<'a>>> = Vec<SQL<'a, SQLiteValue<'a>>>,
> {
Ignore {
target: Option<T>,
},
Update {
target: T,
set: Box<SQL<'a, SQLiteValue<'a>>>,
target_where: Box<Option<SQL<'a, SQLiteValue<'a>>>>,
set_where: Box<Option<SQL<'a, SQLiteValue<'a>>>>,
},
}
impl<'a> Default for Conflict<'a> {
fn default() -> Self {
Self::Ignore { target: None }
}
}
impl<'a, T> Conflict<'a, T>
where
T: IntoIterator<Item: ToSQL<'a, SQLiteValue<'a>>>,
{
pub fn update<S, TW, SW>(
target: T,
set: S,
target_where: Option<TW>,
set_where: Option<SW>,
) -> Self
where
S: ToSQL<'a, SQLiteValue<'a>>,
TW: ToSQL<'a, SQLiteValue<'a>>,
SW: ToSQL<'a, SQLiteValue<'a>>,
{
Conflict::Update {
target,
set: Box::new(set.to_sql()),
target_where: Box::new(target_where.map(|w| w.to_sql())),
set_where: Box::new(set_where.map(|w| w.to_sql())),
}
}
}
impl ExecutableState for InsertValuesSet {}
impl ExecutableState for InsertReturningSet {}
impl ExecutableState for InsertOnConflictSet {}
pub type InsertBuilder<'a, Schema, State, Table> = super::QueryBuilder<'a, Schema, State, Table>;
impl<'a, Schema, Table> InsertBuilder<'a, Schema, InsertInitial, Table>
where
Table: SQLiteTable<'a>,
{
#[inline]
pub fn values<I, T>(self, values: I) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
where
I: IntoIterator<Item = Table::Insert<T>>,
Table::Insert<T>: SQLModel<'a, SQLiteValue<'a>>,
{
let sql = crate::helpers::values::<'a, Table, T>(values);
InsertBuilder {
sql: self.sql.append(sql),
schema: PhantomData,
state: PhantomData,
table: PhantomData,
}
}
}
impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
pub fn on_conflict<TI>(
self,
conflict: Conflict<'a, TI>,
) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
where
TI: IntoIterator,
TI::Item: ToSQL<'a, SQLiteValue<'a>>,
{
let conflict_sql = match conflict {
Conflict::Ignore { target } => {
if let Some(target_iter) = target {
let cols = SQL::join(
target_iter.into_iter().map(|item| item.to_sql()),
Token::COMMA,
);
SQL::from_iter([Token::ON, Token::CONFLICT, Token::LPAREN])
.append(cols)
.push(Token::RPAREN)
.push(Token::DO)
.push(Token::NOTHING)
} else {
SQL::from_iter([Token::ON, Token::CONFLICT, Token::DO, Token::NOTHING])
}
}
Conflict::Update {
target,
set,
target_where,
set_where,
} => {
let target_cols =
SQL::join(target.into_iter().map(|item| item.to_sql()), Token::COMMA);
let mut sql = SQL::from_iter([Token::ON, Token::CONFLICT, Token::LPAREN])
.append(target_cols)
.push(Token::RPAREN);
if let Some(target_where) = *target_where {
sql = sql.push(Token::WHERE).append(target_where);
}
sql = sql
.push(Token::DO)
.push(Token::UPDATE)
.push(Token::SET)
.append(*set);
if let Some(set_where) = *set_where {
sql = sql.push(Token::WHERE).append(set_where);
}
sql
}
};
InsertBuilder {
sql: self.sql.append(conflict_sql),
schema: PhantomData,
state: PhantomData,
table: PhantomData,
}
}
#[inline]
pub fn returning(
self,
columns: impl ToSQL<'a, SQLiteValue<'a>>,
) -> InsertBuilder<'a, S, InsertReturningSet, T> {
let returning_sql = crate::helpers::returning(columns);
InsertBuilder {
sql: self.sql.append(returning_sql),
schema: PhantomData,
state: PhantomData,
table: PhantomData,
}
}
}
impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
#[inline]
pub fn returning(
self,
columns: impl ToSQL<'a, SQLiteValue<'a>>,
) -> InsertBuilder<'a, S, InsertReturningSet, T> {
let returning_sql = crate::helpers::returning(columns);
InsertBuilder {
sql: self.sql.append(returning_sql),
schema: PhantomData,
state: PhantomData,
table: PhantomData,
}
}
}