drizzle_sqlite/builder/
insert.rs

1use crate::values::SQLiteValue;
2use drizzle_core::{SQL, SQLModel, SQLTable, ToSQL};
3use std::fmt::Debug;
4use std::marker::PhantomData;
5
6// Import the ExecutableState trait
7use super::ExecutableState;
8
9//------------------------------------------------------------------------------
10// Type State Markers
11//------------------------------------------------------------------------------
12
13/// Marker for the initial state of InsertBuilder.
14#[derive(Debug, Clone, Copy, Default)]
15pub struct InsertInitial;
16
17/// Marker for the state after VALUES are set.
18#[derive(Debug, Clone, Copy, Default)]
19pub struct InsertValuesSet;
20
21/// Marker for the state after RETURNING clause is added.
22#[derive(Debug, Clone, Copy, Default)]
23pub struct InsertReturningSet;
24
25/// Marker for the state after ON CONFLICT is set.
26#[derive(Debug, Clone, Copy, Default)]
27pub struct InsertOnConflictSet;
28
29// Const constructors for insert marker types
30impl InsertInitial {
31    #[inline]
32    pub const fn new() -> Self {
33        Self
34    }
35}
36impl InsertValuesSet {
37    #[inline]
38    pub const fn new() -> Self {
39        Self
40    }
41}
42impl InsertReturningSet {
43    #[inline]
44    pub const fn new() -> Self {
45        Self
46    }
47}
48impl InsertOnConflictSet {
49    #[inline]
50    pub const fn new() -> Self {
51        Self
52    }
53}
54
55/// Conflict resolution strategies
56#[derive(Debug, Clone)]
57pub enum Conflict<
58    'a,
59    T: IntoIterator<Item: ToSQL<'a, SQLiteValue<'a>>> = Vec<SQL<'a, SQLiteValue<'a>>>,
60> {
61    /// Do nothing on conflict - ON CONFLICT DO NOTHING
62    Ignore {
63        /// Optional target columns to specify which constraint triggers the conflict
64        target: Option<T>,
65    },
66    /// Update on conflict - ON CONFLICT DO UPDATE
67    Update {
68        /// Target columns that trigger the conflict
69        target: T,
70        /// SET clause for what to update
71        set: Box<SQL<'a, SQLiteValue<'a>>>,
72        /// Optional WHERE clause for the conflict target (partial indexes)
73        /// This goes after the target: ON CONFLICT (col) WHERE condition
74        target_where: Box<Option<SQL<'a, SQLiteValue<'a>>>>,
75        /// Optional WHERE clause for the update (conditional updates)
76        /// This goes after the SET: DO UPDATE SET col = val WHERE condition
77        set_where: Box<Option<SQL<'a, SQLiteValue<'a>>>>,
78    },
79}
80
81impl<'a> Default for Conflict<'a> {
82    fn default() -> Self {
83        Self::Ignore { target: None }
84    }
85}
86
87impl<'a, T> Conflict<'a, T>
88where
89    T: IntoIterator<Item: ToSQL<'a, SQLiteValue<'a>>>,
90{
91    pub fn update(
92        target: T,
93        set: SQL<'a, SQLiteValue<'a>>,
94        target_where: Option<SQL<'a, SQLiteValue<'a>>>,
95        set_where: Option<SQL<'a, SQLiteValue<'a>>>,
96    ) -> Self {
97        Conflict::Update {
98            target,
99            set: Box::new(set),
100            target_where: Box::new(target_where),
101            set_where: Box::new(set_where),
102        }
103    }
104}
105
106// Mark states that can execute insert queries
107impl ExecutableState for InsertValuesSet {}
108impl ExecutableState for InsertReturningSet {}
109impl ExecutableState for InsertOnConflictSet {}
110
111//------------------------------------------------------------------------------
112// InsertBuilder Definition
113//------------------------------------------------------------------------------
114
115/// Builds an INSERT query specifically for SQLite
116pub type InsertBuilder<'a, Schema, State, Table> = super::QueryBuilder<'a, Schema, State, Table>;
117
118//------------------------------------------------------------------------------
119// Initial State Implementation
120//------------------------------------------------------------------------------
121
122impl<'a, Schema, Table> InsertBuilder<'a, Schema, InsertInitial, Table>
123where
124    Table: SQLTable<'a, SQLiteValue<'a>>,
125{
126    /// Sets values to insert and transitions to ValuesSet state
127    #[inline]
128    pub fn values<I, T>(self, values: I) -> InsertBuilder<'a, Schema, InsertValuesSet, Table>
129    where
130        I: IntoIterator<Item = Table::Insert<T>>,
131        Table::Insert<T>: SQLModel<'a, SQLiteValue<'a>>,
132    {
133        let sql = crate::helpers::values::<'a, Table, T>(values);
134        InsertBuilder {
135            sql: self.sql.append(sql),
136            schema: PhantomData,
137            state: PhantomData,
138            table: PhantomData,
139        }
140    }
141}
142
143//------------------------------------------------------------------------------
144// Post-VALUES Implementation
145//------------------------------------------------------------------------------
146
147impl<'a, S, T> InsertBuilder<'a, S, InsertValuesSet, T> {
148    /// Adds conflict resolution clause
149    pub fn on_conflict<TI>(
150        self,
151        conflict: Conflict<'a, TI>,
152    ) -> InsertBuilder<'a, S, InsertOnConflictSet, T>
153    where
154        TI: IntoIterator,
155        TI::Item: ToSQL<'a, SQLiteValue<'a>>,
156    {
157        let conflict_sql = match conflict {
158            Conflict::Ignore { target } => {
159                if let Some(target_iter) = target {
160                    let cols = SQL::join(target_iter.into_iter().map(|item| item.to_sql()), ", ");
161                    SQL::raw("ON CONFLICT (")
162                        .append(cols)
163                        .append_raw(") DO NOTHING")
164                } else {
165                    SQL::raw("ON CONFLICT DO NOTHING")
166                }
167            }
168            Conflict::Update {
169                target,
170                set,
171                target_where,
172                set_where,
173            } => {
174                let target_cols = SQL::join(target.into_iter().map(|item| item.to_sql()), ", ");
175                let mut sql = SQL::raw("ON CONFLICT (")
176                    .append(target_cols)
177                    .append_raw(")");
178
179                // Add target WHERE clause (for partial indexes)
180                if let Some(target_where) = *target_where {
181                    sql = sql.append_raw(" WHERE ").append(target_where);
182                }
183
184                sql = sql.append_raw(" DO UPDATE SET ").append(*set);
185
186                // Add set WHERE clause (for conditional updates)
187                if let Some(set_where) = *set_where {
188                    sql = sql.append_raw(" WHERE ").append(set_where);
189                }
190
191                sql
192            }
193        };
194
195        InsertBuilder {
196            sql: self.sql.append(conflict_sql),
197            schema: PhantomData,
198            state: PhantomData,
199            table: PhantomData,
200        }
201    }
202
203    /// Adds a RETURNING clause and transitions to ReturningSet state
204    #[inline]
205    pub fn returning(
206        self,
207        columns: impl ToSQL<'a, SQLiteValue<'a>>,
208    ) -> InsertBuilder<'a, S, InsertReturningSet, T> {
209        let returning_sql = crate::helpers::returning(columns);
210        InsertBuilder {
211            sql: self.sql.append(returning_sql),
212            schema: PhantomData,
213            state: PhantomData,
214            table: PhantomData,
215        }
216    }
217}
218
219//------------------------------------------------------------------------------
220// Post-ON CONFLICT Implementation
221//------------------------------------------------------------------------------
222
223impl<'a, S, T> InsertBuilder<'a, S, InsertOnConflictSet, T> {
224    /// Adds a RETURNING clause after ON CONFLICT
225    #[inline]
226    pub fn returning(
227        self,
228        columns: impl ToSQL<'a, SQLiteValue<'a>>,
229    ) -> InsertBuilder<'a, S, InsertReturningSet, T> {
230        let returning_sql = crate::helpers::returning(columns);
231        InsertBuilder {
232            sql: self.sql.append(returning_sql),
233            schema: PhantomData,
234            state: PhantomData,
235            table: PhantomData,
236        }
237    }
238}