Skip to main content

toasty_core/stmt/
insert.rs

1use super::{
2    Assignments, InsertTarget, Node, Projection, Query, Returning, Statement, Visit, VisitMut,
3};
4use crate::schema::db::ColumnId;
5use crate::stmt;
6
7/// An `INSERT` statement that creates new records.
8///
9/// Combines an [`InsertTarget`] (where to insert), a [`Query`] source
10/// (the values to insert), optional [`Upsert`] conflict handling, and an
11/// optional [`Returning`] clause.
12///
13/// # Examples
14///
15/// ```ignore
16/// use toasty_core::stmt::{Insert, InsertTarget, Query, Values, Expr};
17/// use toasty_core::schema::app::ModelId;
18///
19/// let insert = Insert {
20///     target: InsertTarget::Model(ModelId(0)),
21///     source: Query::values(Values::new(vec![Expr::null()])),
22///     upsert: None,
23///     returning: None,
24/// };
25/// assert!(insert.target.is_model());
26/// ```
27#[derive(Debug, Clone, PartialEq)]
28pub struct Insert {
29    /// The target to insert into (model, table, or scoped query).
30    pub target: InsertTarget,
31
32    /// The source query providing values to insert.
33    pub source: Query,
34
35    /// Optional conflict handling that turns this insert into an upsert.
36    pub upsert: Option<Box<Upsert>>,
37
38    /// Optional `RETURNING` clause to return data from the insertion.
39    pub returning: Option<Returning>,
40}
41
42/// Conflict handling attached to an [`Insert`].
43///
44/// The target selects one primary-key or unique-constraint conflict. `Update`
45/// applies the normalized [`shared`](Self::shared) assignments to the matching
46/// row, while `Ignore` leaves it unchanged.
47///
48/// Before normalization, [`shared`](Self::shared),
49/// [`defaults`](Self::defaults), [`update_defaults`](Self::update_defaults),
50/// [`create`](Self::create), and [`update`](Self::update) contain the
51/// declarative assignments. The engine first routes `update_defaults` to any
52/// branch without an explicit assignment. Normalization then writes the create
53/// branch into the insert source, overlays the update branch onto `shared`, and
54/// clears `create` and `update`. Defaults remain available to non-SQL drivers
55/// and are cleared before SQL serialization. The engine also stores model-field
56/// targets before lowering and database-column targets afterward. SQL drivers
57/// receive the normalized, lowered form inside
58/// [`Operation::QuerySql`](crate::driver::Operation::QuerySql); non-SQL drivers
59/// receive it inside [`Operation::Upsert`](crate::driver::Operation::Upsert).
60#[derive(Debug, Clone, PartialEq)]
61pub struct Upsert {
62    /// The unique constraint that selects the conflicting row.
63    pub target: UpsertTarget,
64
65    /// Assignments applied to both the create and update branches.
66    ///
67    /// Normalization derives create values from these assignments and retains
68    /// the assignments for conflict updates.
69    pub shared: Assignments,
70
71    /// Values declared with `#[default]` on model fields.
72    ///
73    /// These supply omitted create fields and initialize shared mutations.
74    /// Explicit create assignments override them.
75    pub defaults: Assignments,
76
77    /// Values declared with `#[update]` on model fields.
78    ///
79    /// Before verification, the engine routes each value to the create branch,
80    /// update branch, or both according to which branches already have an
81    /// explicit assignment.
82    pub update_defaults: Assignments,
83
84    /// Assignments applied only when the insert creates a record.
85    ///
86    /// Explicit `on_create` assignments replace defaults and shared
87    /// assignments for the same field.
88    pub create: Assignments,
89
90    /// Assignments applied only when the target matches an existing record.
91    ///
92    /// These override shared assignments for the same field and may reference
93    /// stored columns or fields projected from
94    /// [`ExprIncoming`](super::ExprIncoming), the row proposed by the insert source.
95    pub update: Assignments,
96
97    /// Whether to update or ignore a conflicting row.
98    pub action: UpsertAction,
99}
100
101/// The fields or columns that identify the selected upsert conflict.
102#[derive(Debug, Clone, PartialEq)]
103pub enum UpsertTarget {
104    /// Model-field projections used before engine lowering.
105    Fields(Vec<Projection>),
106
107    /// Database columns sent to the driver after engine lowering.
108    Columns(Vec<ColumnId>),
109}
110
111/// Action to take when an upsert finds an existing row.
112#[derive(Debug, Clone, Copy, PartialEq, Eq)]
113pub enum UpsertAction {
114    /// Update the conflicting row.
115    Update,
116
117    /// Leave the conflicting row unchanged and return no row.
118    Ignore,
119}
120
121impl Insert {
122    /// Merges another `Insert` into this one by appending its value rows.
123    ///
124    /// Both inserts must target the same model, and both sources must be
125    /// `VALUES` expressions.
126    pub fn merge(&mut self, other: Self) {
127        match (&self.target, &other.target) {
128            (InsertTarget::Model(a), InsertTarget::Model(b)) if a == b => {}
129            _ => todo!("handle this case"),
130        }
131
132        match (&mut self.source.body, other.source.body) {
133            (stmt::ExprSet::Values(self_values), stmt::ExprSet::Values(other_values)) => {
134                for expr in other_values.rows {
135                    self_values.rows.push(expr);
136                }
137            }
138            (self_source, other) => todo!("self={:#?}; other={:#?}", self_source, other),
139        }
140    }
141}
142
143impl Statement {
144    /// Returns `true` if this statement is an [`Insert`].
145    pub fn is_insert(&self) -> bool {
146        matches!(self, Statement::Insert(..))
147    }
148
149    /// Returns `true` if this statement is an [`Insert`] with an upsert action.
150    pub fn is_upsert(&self) -> bool {
151        matches!(self, Statement::Insert(insert) if insert.upsert.is_some())
152    }
153
154    /// Attempts to return a reference to an inner [`Insert`].
155    ///
156    /// * If `self` is a [`Statement::Insert`], a reference to the inner [`Insert`] is
157    ///   returned wrapped in [`Some`].
158    /// * Else, [`None`] is returned.
159    pub fn as_insert(&self) -> Option<&Insert> {
160        match self {
161            Self::Insert(insert) => Some(insert),
162            _ => None,
163        }
164    }
165
166    /// Consumes `self` and attempts to return the inner [`Insert`].
167    ///
168    /// * If `self` is a [`Statement::Insert`], inner [`Insert`] is returned wrapped in
169    ///   [`Some`].
170    /// * Else, [`None`] is returned.
171    pub fn into_insert(self) -> Option<Insert> {
172        match self {
173            Self::Insert(insert) => Some(insert),
174            _ => None,
175        }
176    }
177
178    /// Consumes `self` and returns the inner [`Insert`].
179    ///
180    /// # Panics
181    ///
182    /// If `self` is not a [`Statement::Insert`].
183    pub fn into_insert_unwrap(self) -> Insert {
184        match self {
185            Self::Insert(insert) => insert,
186            v => panic!("expected `Insert`, found {v:#?}"),
187        }
188    }
189}
190
191impl From<Insert> for Statement {
192    fn from(src: Insert) -> Self {
193        Self::Insert(src)
194    }
195}
196
197impl Node for Insert {
198    fn visit<V: Visit>(&self, mut visit: V) {
199        visit.visit_stmt_insert(self);
200    }
201
202    fn visit_mut<V: VisitMut>(&mut self, mut visit: V) {
203        visit.visit_stmt_insert_mut(self);
204    }
205}