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}