squonk_ast/ast/dml.rs
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Moderately AI Inc.
3
4//! DML statement AST nodes: INSERT, UPDATE, DELETE, and MERGE.
5
6use super::{
7 AliasSpelling, Expr, Extension, Ident, Limit, NoExt, ObjectName, OrderByExpr, Query,
8 RelationInheritance, SelectItem, TableAlias, TableWithJoins, With,
9};
10use crate::vocab::Meta;
11use thin_vec::ThinVec;
12
13/// Which keyword spelled an [`Insert`]: standard `INSERT` or MySQL `REPLACE`.
14///
15/// MySQL `REPLACE [INTO] t ...` is a delete-then-insert upsert that shares INSERT's
16/// entire tail grammar — the `VALUES` / `SET` / `SELECT` sources, the target column
17/// list — and differs only in the lead keyword and in carrying none of the
18/// `OVERRIDING` / upsert / `RETURNING` tails (it *is* the conflict resolution, so it
19/// has no `ON DUPLICATE KEY UPDATE`). That is one canonical shape with
20/// two surface spellings, so it is a tag on [`Insert`] rather than a forked
21/// `Statement::Replace` node; the renderer reads it to re-emit the original keyword.
22#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
23#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
24#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
25pub enum InsertVerb {
26 /// `INSERT` — add rows, erroring on a duplicate key.
27 Insert,
28 /// `REPLACE` — MySQL's insert-or-replace (delete then insert on a duplicate key).
29 Replace,
30}
31
32/// DuckDB column-matching mode on `INSERT`: `BY NAME` or `BY POSITION`.
33///
34/// Written between the insert target and the source (`INSERT INTO t BY NAME SELECT …`).
35/// Gated by [`MutationSyntax::insert_column_matching`](crate::dialect::MutationSyntax).
36/// Engine rule (1.5.4): `BY NAME` cannot combine with an explicit column list and only
37/// applies to a SELECT source — the parser rejects the column-list combination; VALUES
38/// with `BY NAME` is left for the binder.
39#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
40#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
41#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
42pub enum InsertColumnMatching {
43 /// `BY NAME` — match source columns to targets by name (DuckDB).
44 ByName,
45 /// `BY POSITION` — match source columns to targets by position (the default).
46 ByPosition,
47}
48
49/// The SQLite `OR <action>` conflict-resolution algorithm on the mutation verb —
50/// `INSERT OR {REPLACE | IGNORE | ABORT | FAIL | ROLLBACK}` and the same tail on
51/// `UPDATE`. It selects what SQLite does when the statement hits a constraint
52/// violation, so it carries genuine new information (which algorithm) rather than a
53/// surface spelling; the [`Insert::or_action`] / [`Update::or_action`] slot models it
54/// and the parser gates it on [`MutationSyntax::or_conflict_action`](crate::dialect::MutationSyntax).
55///
56/// This is *distinct* from two same-named neighbours: it is not the PostgreSQL
57/// [`ConflictAction`] (the `ON CONFLICT DO {NOTHING | UPDATE}` upsert action — a
58/// different construct in a different clause position, which SQLite also has and which
59/// maps to [`Insert::upsert`]); and its [`Replace`](Self::Replace) variant is not the
60/// [`InsertVerb::Replace`] statement spelling — `INSERT OR REPLACE` and `REPLACE INTO`
61/// are equivalent SQLite semantics written as different source texts, so each keeps its
62/// own representation and round-trips through it (one is never folded onto the other).
63#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
64#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
65#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
66pub enum ConflictResolution {
67 /// `OR ROLLBACK` — abort the statement and roll back the enclosing transaction.
68 Rollback,
69 /// `OR ABORT` — abort the statement, reverting its own prior changes (the default
70 /// resolution when no `OR <action>` is written).
71 Abort,
72 /// `OR FAIL` — abort the statement without reverting the rows it already changed.
73 Fail,
74 /// `OR IGNORE` — skip the offending row and continue the statement.
75 Ignore,
76 /// `OR REPLACE` — delete the conflicting rows, then insert/update the new one.
77 Replace,
78}
79
80#[derive(Clone, Debug, PartialEq, Eq, Hash)]
81#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
82#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
83/// An SQL insert.
84pub struct Insert<X: Extension = NoExt> {
85 /// Whether this was spelled `INSERT` or MySQL `REPLACE` (a surface tag over one
86 /// shape; see [`InsertVerb`]).
87 pub verb: InsertVerb,
88 /// The SQLite `INSERT OR <action>` conflict-resolution algorithm on the verb, gated
89 /// by [`MutationSyntax::or_conflict_action`](crate::dialect::MutationSyntax); `None`
90 /// when unwritten (and always `None` on the [`InsertVerb::Replace`] spelling, which
91 /// *is* the conflict resolution and takes no `OR <action>`). A distinct slot from
92 /// [`upsert`](Self::upsert): the `OR <action>` prefix and the `ON CONFLICT` tail are
93 /// different constructs (SQLite forbids both on one statement), never one overloaded
94 /// field. See [`ConflictResolution`].
95 pub or_action: Option<ConflictResolution>,
96 /// Common table expressions visible to this statement.
97 pub with: Option<With<X>>,
98 /// Object targeted by this syntax.
99 pub target: InsertTarget,
100 /// DuckDB `BY NAME` / `BY POSITION` between target and source; `None` when unwritten.
101 pub column_matching: Option<InsertColumnMatching>,
102 /// Optional overriding for this syntax.
103 pub overriding: Option<InsertOverriding>,
104 /// Input source for this syntax.
105 pub source: InsertSource<X>,
106 /// MySQL 8.0.19+ row alias — `INSERT ... VALUES (...) AS <alias>[(<col>, ...)]`,
107 /// the modern replacement for `VALUES(<col>)` inside `ON DUPLICATE KEY UPDATE`.
108 /// Parsed between the [`source`](Self::source) and the [`upsert`](Self::upsert)
109 /// clause and gated by the same dialect data as `ON DUPLICATE KEY UPDATE`
110 /// ([`MutationSyntax::on_duplicate_key_update`](crate::dialect::MutationSyntax)) —
111 /// the alias is the input side of that MySQL upsert surface, and no shipped dialect
112 /// admits one without the other. Reuses the shared [`TableAlias`] shape (a name plus
113 /// an optional column-alias list), never a parallel alias type; `None`
114 /// when unwritten.
115 pub row_alias: Option<TableAlias>,
116 /// Upsert clause — insert-or-update on a unique-key conflict (gated by dialect
117 /// data). One canonical field for the mutually-exclusive dialect spellings (see
118 /// [`Upsert`]); boxed because the clause is heavy (its PostgreSQL arm carries two
119 /// child lists) and absent from the common non-upsert `INSERT`, so the box keeps
120 /// the inline `Insert` lean.
121 pub upsert: Option<Box<Upsert<X>>>,
122 /// Expressions returned by the statement.
123 pub returning: Option<Returning<X>>,
124 /// Source location and node identity.
125 pub meta: Meta,
126}
127
128#[derive(Clone, Debug, PartialEq, Eq, Hash)]
129#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
130#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
131/// An SQL insert target.
132pub struct InsertTarget {
133 /// Name referenced by this syntax.
134 pub name: ObjectName,
135 /// Alias assigned by this syntax.
136 pub alias: Option<Ident>,
137 /// How the source introduced `alias` (`INSERT INTO t AS x` vs `INSERT INTO t x`).
138 /// Meaningful only when `alias` is `Some`; [`AliasSpelling::As`] otherwise.
139 pub alias_spelling: AliasSpelling,
140 /// Columns in source order.
141 pub columns: ThinVec<Ident>,
142 /// Source location and node identity.
143 pub meta: Meta,
144}
145
146#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
147#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
148#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
149/// The SQL insert overriding forms represented by the AST.
150pub enum InsertOverriding {
151 /// `OVERRIDING SYSTEM VALUE` — allow explicit values for `GENERATED ALWAYS` identity columns.
152 SystemValue,
153 /// `OVERRIDING USER VALUE` — ignore supplied values for `GENERATED BY DEFAULT` identity columns.
154 UserValue,
155}
156
157#[derive(Clone, Debug, PartialEq, Eq, Hash)]
158#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
159#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
160/// The SQL insert source forms represented by the AST.
161pub enum InsertSource<X: Extension = NoExt> {
162 /// `DEFAULT VALUES` — insert a single all-defaults row.
163 DefaultValues {
164 /// The `DEFAULT VALUES` marker; see [`DefaultValue`].
165 default: DefaultValue,
166 /// Source location and node identity.
167 meta: Meta,
168 },
169 /// A `VALUES (…), …` row list.
170 Values {
171 /// Values in source order.
172 values: Box<InsertValues<X>>,
173 /// Source location and node identity.
174 meta: Meta,
175 },
176 /// An `INSERT … SELECT` query source.
177 Query {
178 /// Query governed by this node.
179 query: Box<Query<X>>,
180 /// Source location and node identity.
181 meta: Meta,
182 },
183 /// MySQL `SET <col> = {<expr> | DEFAULT} [, ...]` — the assignment-list source
184 /// equivalent to a single-row `(<cols>) VALUES (<values>)`. Gated by dialect data
185 /// ([`MutationSyntax::insert_set`](crate::dialect::MutationSyntax)) and reused by
186 /// both `INSERT ... SET` and `REPLACE ... SET`. It reuses the shared
187 /// [`UpdateAssignment`] shape (exactly as `UPDATE ... SET` and `ON DUPLICATE KEY
188 /// UPDATE` do), never a parallel assignment representation.
189 Set {
190 /// assignments in source order.
191 assignments: ThinVec<UpdateAssignment<X>>,
192 /// Source location and node identity.
193 meta: Meta,
194 },
195}
196
197#[derive(Clone, Debug, PartialEq, Eq, Hash)]
198#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
199#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
200/// An SQL insert values.
201pub struct InsertValues<X: Extension = NoExt> {
202 /// rows in source order.
203 pub rows: ThinVec<ThinVec<InsertValue<X>>>,
204 /// Source location and node identity.
205 pub meta: Meta,
206}
207
208#[derive(Clone, Debug, PartialEq, Eq, Hash)]
209#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
210#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
211/// The SQL insert value forms represented by the AST.
212pub enum InsertValue<X: Extension = NoExt> {
213 /// An ordinary value expression.
214 Expr {
215 /// Expression evaluated by this syntax.
216 expr: Expr<X>,
217 /// Source location and node identity.
218 meta: Meta,
219 },
220 /// A bare `DEFAULT` placeholder (use the column's default).
221 Default {
222 /// Explicit `DEFAULT` value.
223 default: DefaultValue,
224 /// Source location and node identity.
225 meta: Meta,
226 },
227}
228
229/// Shared target table shape for `UPDATE` and `DELETE`.
230#[derive(Clone, Debug, PartialEq, Eq, Hash)]
231#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
232#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
233pub struct DmlTarget {
234 /// Name referenced by this syntax.
235 pub name: ObjectName,
236 /// PostgreSQL `relation_expr` inheritance marker on the target relation (gated
237 /// by dialect data): the bare/`*` spellings include descendant tables, while
238 /// `ONLY table` / `ONLY (table)` suppress them.
239 pub inheritance: RelationInheritance,
240 /// Alias assigned by this syntax.
241 pub alias: Option<Ident>,
242 /// How the source introduced `alias` (`DELETE FROM t AS x` vs `DELETE FROM t x`).
243 /// Meaningful only when `alias` is `Some`; [`AliasSpelling::As`] otherwise.
244 pub alias_spelling: AliasSpelling,
245 /// Source location and node identity.
246 pub meta: Meta,
247}
248
249#[derive(Clone, Debug, PartialEq, Eq, Hash)]
250#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
251#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
252/// An SQL update.
253pub struct Update<X: Extension = NoExt> {
254 /// Common table expressions visible to this statement.
255 pub with: Option<With<X>>,
256 /// The SQLite `UPDATE OR <action>` conflict-resolution algorithm on the verb, gated
257 /// by [`MutationSyntax::or_conflict_action`](crate::dialect::MutationSyntax); `None`
258 /// when unwritten. The `UPDATE` counterpart of [`Insert::or_action`] — see
259 /// [`ConflictResolution`].
260 pub or_action: Option<ConflictResolution>,
261 /// Object targeted by this syntax.
262 pub target: DmlTarget,
263 /// assignments in source order.
264 pub assignments: ThinVec<UpdateAssignment<X>>,
265 /// from in source order.
266 pub from: ThinVec<TableWithJoins<X>>,
267 /// Predicate that filters input rows.
268 pub selection: Option<DmlSelection<X>>,
269 /// MySQL single-table `UPDATE ... ORDER BY <keys>` tail — orders the rows a
270 /// row-limited update touches. Parsed after [`selection`](Self::selection) and gated
271 /// by [`MutationSyntax::update_delete_tails`](crate::dialect::MutationSyntax); empty
272 /// when the dialect leaves the flag off or the clause is unwritten. Reuses the shared
273 /// [`OrderByExpr`] key shape, never a mutation-specific one.
274 pub order_by: ThinVec<OrderByExpr<X>>,
275 /// MySQL single-table `UPDATE ... LIMIT <count>` tail — caps how many rows the
276 /// update touches. Parsed after [`order_by`](Self::order_by) and gated by the same
277 /// [`MutationSyntax::update_delete_tails`](crate::dialect::MutationSyntax); `None`
278 /// when off or unwritten. Reuses the canonical [`Limit`] shape; MySQL's
279 /// mutation `LIMIT` is a bare row count, so `offset` stays `None`.
280 pub limit: Option<Limit<X>>,
281 /// Expressions returned by the statement.
282 pub returning: Option<Returning<X>>,
283 /// Source location and node identity.
284 pub meta: Meta,
285}
286
287/// One `UPDATE ... SET` assignment.
288///
289/// The single form assigns one column; the tuple form is the SQL multiple-column
290/// assignment (feature T641, PostgreSQL `'(' set_target_list ')' '=' a_expr`),
291/// gated by dialect data and kept a distinct variant so a single `(a) = (1)` and
292/// a single `a = 1` never alias to one shape.
293#[derive(Clone, Debug, PartialEq, Eq, Hash)]
294#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
295#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
296pub enum UpdateAssignment<X: Extension = NoExt> {
297 /// A single-column assignment (`<col> = <value>`).
298 Single {
299 /// Object targeted by this syntax.
300 target: ObjectName,
301 /// Value supplied by this syntax.
302 value: UpdateValue<X>,
303 /// Source location and node identity.
304 meta: Meta,
305 },
306 /// A multi-column assignment (`(<cols>) = <source>`).
307 Tuple {
308 /// targets in source order.
309 targets: ThinVec<ObjectName>,
310 /// Input source for this syntax.
311 source: UpdateTupleSource<X>,
312 /// Source location and node identity.
313 meta: Meta,
314 },
315}
316
317#[derive(Clone, Debug, PartialEq, Eq, Hash)]
318#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
319#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
320/// The SQL update value forms represented by the AST.
321pub enum UpdateValue<X: Extension = NoExt> {
322 /// An ordinary value expression.
323 Expr {
324 /// Expression evaluated by this syntax.
325 expr: Expr<X>,
326 /// Source location and node identity.
327 meta: Meta,
328 },
329 /// A bare `DEFAULT` (reset the column to its default).
330 Default {
331 /// Explicit `DEFAULT` value.
332 default: DefaultValue,
333 /// Source location and node identity.
334 meta: Meta,
335 },
336}
337
338/// Right-hand side of a multiple-column (`( ... ) = <source>`) assignment.
339///
340/// PostgreSQL maps the source positionally onto the target columns, so the three
341/// forms mirror the row-value sources the standard allows: a value row (with the
342/// `ROW` keyword optional), a row subquery, or a bare `DEFAULT` that defaults every
343/// target. `DEFAULT` is kept out of the expression grammar (like [`UpdateValue`]),
344/// so a per-element default inside a row stays a [`UpdateValue::Default`].
345#[derive(Clone, Debug, PartialEq, Eq, Hash)]
346#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
347#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
348pub enum UpdateTupleSource<X: Extension = NoExt> {
349 /// A `[ROW] (<values>)` value row assigned positionally to the targets.
350 Row {
351 /// Whether the explicit form was present in the source.
352 explicit: bool,
353 /// Values in source order.
354 values: ThinVec<UpdateValue<X>>,
355 /// Source location and node identity.
356 meta: Meta,
357 },
358 /// A row subquery (`(<query>)`) assigned positionally to the targets.
359 Subquery {
360 /// Query governed by this node.
361 query: Box<Query<X>>,
362 /// Source location and node identity.
363 meta: Meta,
364 },
365 /// A bare `DEFAULT` defaulting every target column.
366 Default {
367 /// The `DEFAULT` marker; see [`DefaultValue`].
368 default: DefaultValue,
369 /// Source location and node identity.
370 meta: Meta,
371 },
372}
373
374#[derive(Clone, Debug, PartialEq, Eq, Hash)]
375#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
376#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
377/// An SQL delete.
378pub struct Delete<X: Extension = NoExt> {
379 /// Common table expressions visible to this statement.
380 pub with: Option<With<X>>,
381 /// Object targeted by this syntax.
382 pub target: DmlTarget,
383 /// using in source order.
384 pub using: ThinVec<TableWithJoins<X>>,
385 /// Predicate that filters input rows.
386 pub selection: Option<DmlSelection<X>>,
387 /// MySQL single-table `DELETE ... ORDER BY <keys>` tail — the row-ordering half of
388 /// the row-limited delete. Parsed after [`selection`](Self::selection) and gated by
389 /// [`MutationSyntax::update_delete_tails`](crate::dialect::MutationSyntax); empty
390 /// when off or unwritten. Reuses the shared [`OrderByExpr`] key shape.
391 pub order_by: ThinVec<OrderByExpr<X>>,
392 /// MySQL single-table `DELETE ... LIMIT <count>` tail — caps how many rows the
393 /// delete removes. Parsed after [`order_by`](Self::order_by) and gated by the same
394 /// [`MutationSyntax::update_delete_tails`](crate::dialect::MutationSyntax); `None`
395 /// when off or unwritten. Reuses the canonical [`Limit`] shape, a bare
396 /// row count with `offset` left `None`.
397 pub limit: Option<Limit<X>>,
398 /// Expressions returned by the statement.
399 pub returning: Option<Returning<X>>,
400 /// Source location and node identity.
401 pub meta: Meta,
402}
403
404/// The `WHERE` filter on an `UPDATE` or `DELETE`.
405///
406/// A condition is the ordinary row filter; `WHERE CURRENT OF <cursor>` is the
407/// positioned form (gated by dialect data) that targets the row a named open
408/// cursor sits on. The two are mutually exclusive in the grammar, so one enum
409/// keeps an invalid "condition *and* cursor" state unrepresentable.
410#[derive(Clone, Debug, PartialEq, Eq, Hash)]
411#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
412#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
413pub enum DmlSelection<X: Extension = NoExt> {
414 /// A `WHERE <predicate>` row filter.
415 Where {
416 /// Predicate that controls this clause.
417 condition: Expr<X>,
418 /// Source location and node identity.
419 meta: Meta,
420 },
421 /// A `WHERE CURRENT OF <cursor>` positioned update/delete.
422 CurrentOf {
423 /// The open cursor whose current row is targeted.
424 cursor: Ident,
425 /// Source location and node identity.
426 meta: Meta,
427 },
428}
429
430#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
431#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
432#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
433/// An SQL default value.
434pub struct DefaultValue {
435 /// Source location and node identity.
436 pub meta: Meta,
437}
438
439/// `RETURNING <output> [, ...]` clause on a mutation statement (PostgreSQL).
440///
441/// The output list is a projection — `*`, `<table>.*`, or `<expr> [[AS] alias]` —
442/// so it reuses [`SelectItem`] rather than a parallel item type. A `RETURNING`
443/// clause is always non-empty; its absence is modelled by `Option<Returning>` on
444/// the statement.
445#[derive(Clone, Debug, PartialEq, Eq, Hash)]
446#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
447#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
448pub struct Returning<X: Extension = NoExt> {
449 /// Child items in source order.
450 pub items: ThinVec<SelectItem<X>>,
451 /// Source location and node identity.
452 pub meta: Meta,
453}
454
455/// `INSERT ...` upsert clause: insert-or-update on a unique-key conflict.
456///
457/// Dialects spell this with structurally different clauses — PostgreSQL's
458/// `ON CONFLICT` carries an explicit conflict arbiter and a do-nothing-or-update
459/// action, while MySQL's `ON DUPLICATE KEY UPDATE` infers the conflicting key and
460/// is only an assignment list — so this is one canonical *clause* enum (a
461/// forked shape, not a surface tag over one shape) rather than two `Insert` fields:
462/// an `INSERT` carries at most one upsert clause, and a single field makes the
463/// invalid "both clauses" state unrepresentable. Both arms reuse [`UpdateAssignment`]
464/// for the SET list — the shared assignment shape, never a parallel representation.
465///
466/// Each variant carries `meta` so the clause is a directly-addressable spanned node
467/// (the same wrapper-with-`meta` discipline [`super::Statement`] uses over its boxed
468/// statement nodes), not one whose span is reconstructed from its children.
469#[derive(Clone, Debug, PartialEq, Eq, Hash)]
470#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
471#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
472pub enum Upsert<X: Extension = NoExt> {
473 /// PostgreSQL `ON CONFLICT [<target>] DO {NOTHING | UPDATE SET …}`.
474 OnConflict {
475 /// The conflict target and action; see [`OnConflict`].
476 conflict: OnConflict<X>,
477 /// Source location and node identity.
478 meta: Meta,
479 },
480 /// MySQL `ON DUPLICATE KEY UPDATE <col> = <expr> [, ...]`. MySQL infers the
481 /// violated unique key (no arbiter) and has no `DO NOTHING`, so the clause is
482 /// just the assignment list — reusing [`UpdateAssignment`] exactly as the
483 /// PostgreSQL `DO UPDATE SET` action does.
484 OnDuplicateKeyUpdate {
485 /// assignments in source order.
486 assignments: ThinVec<UpdateAssignment<X>>,
487 /// Source location and node identity.
488 meta: Meta,
489 },
490}
491
492#[derive(Clone, Debug, PartialEq, Eq, Hash)]
493#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
494#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
495/// An SQL on conflict.
496pub struct OnConflict<X: Extension = NoExt> {
497 /// The conflict arbiter. `None` is the bare `ON CONFLICT DO NOTHING`, which
498 /// lets any unique violation trigger the action.
499 pub target: Option<ConflictTarget<X>>,
500 /// What to do on a conflict (`DO NOTHING`/`DO UPDATE`); see [`ConflictAction`].
501 pub action: ConflictAction<X>,
502 /// Source location and node identity.
503 pub meta: Meta,
504}
505
506#[derive(Clone, Debug, PartialEq, Eq, Hash)]
507#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
508#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
509/// The SQL conflict target forms represented by the AST.
510pub enum ConflictTarget<X: Extension = NoExt> {
511 /// `( <index-element> [, ...] ) [ WHERE <predicate> ]` — index inference with an
512 /// optional partial-index predicate. Each element is an expression so a bare
513 /// column and an index expression share one shape.
514 Index {
515 /// Columns in source order.
516 columns: ThinVec<Expr<X>>,
517 /// Predicate that controls this clause.
518 predicate: Option<Expr<X>>,
519 /// Source location and node identity.
520 meta: Meta,
521 },
522 /// An `ON CONFLICT ON CONSTRAINT <name>` named-constraint arbiter.
523 Constraint {
524 /// Name referenced by this syntax.
525 name: Ident,
526 /// Source location and node identity.
527 meta: Meta,
528 },
529}
530
531#[derive(Clone, Debug, PartialEq, Eq, Hash)]
532#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
533#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
534/// The SQL conflict action forms represented by the AST.
535pub enum ConflictAction<X: Extension = NoExt> {
536 /// `DO NOTHING` — silently ignore the conflicting row.
537 Nothing {
538 /// Source location and node identity.
539 meta: Meta,
540 },
541 /// `DO UPDATE SET <assignment> [, ...] [ WHERE <condition> ]`. The assignment
542 /// shape matches `UPDATE ... SET`, so it reuses [`UpdateAssignment`].
543 Update {
544 /// assignments in source order.
545 assignments: ThinVec<UpdateAssignment<X>>,
546 /// Predicate that filters input rows.
547 selection: Option<Expr<X>>,
548 /// Source location and node identity.
549 meta: Meta,
550 },
551}
552
553/// `MERGE INTO <target> [AS alias] USING <source> ON <condition> <when_clause>+`
554/// statement (SQL:2003 feature F312; the standard upsert).
555///
556/// One canonical shape: the standard models MERGE directly, so this is a
557/// shared node gated for *acceptance* by [`MutationSyntax::merge`](crate::dialect::MutationSyntax)
558/// — on in ANSI (SQL:2016) and PostgreSQL 15+, off in MySQL — never a dialect fork.
559/// The clause reuses the existing mutation shapes rather than parallel ones: the
560/// `MERGE INTO <target>` relation is the shared [`DmlTarget`] (carrying the same
561/// `ONLY`/`*` inheritance marker `UPDATE`/`DELETE` targets do), the `USING` source is
562/// a [`TableWithJoins`] (SQL:2016's `<table reference>` — a table, a derived subquery,
563/// or a joined table, exactly as in a `FROM` clause), and the `WHEN` actions reuse the
564/// `INSERT` value element ([`InsertValue`]) and the `UPDATE ... SET` assignment list
565/// ([`UpdateAssignment`]).
566///
567/// Boxed at the [`Statement`](super::Statement) seam (`Statement::Merge`) because MERGE
568/// is a fat, infrequent variant — its `when_clause` list and the embedded source and
569/// condition make it one of the widest statements — so boxing keeps the common
570/// `Statement` lean.
571#[derive(Clone, Debug, PartialEq, Eq, Hash)]
572#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
573#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
574pub struct Merge<X: Extension = NoExt> {
575 /// Optional statement-level `WITH` clause (`WITH ... MERGE INTO ...`; PostgreSQL
576 /// 15+ and DuckDB, probed on 1.5.4). Not standard surface — SQL:2016's
577 /// `<merge statement>` takes no `WITH` — so it is gated by
578 /// [`MutationSyntax::cte_before_merge`](crate::dialect::MutationSyntax), the
579 /// `MERGE` counterpart of [`Insert::with`]'s `cte_before_insert` gate.
580 pub with: Option<With<X>>,
581 /// `MERGE INTO <target>` — the table rows are merged into, plus its optional
582 /// correlation alias and PostgreSQL `ONLY`/`*` inheritance marker. Reuses the shared
583 /// [`DmlTarget`] shape (`UPDATE`/`DELETE` use the same one), so `MERGE INTO ONLY t`
584 /// and `MERGE INTO t *` ride the same `table_expressions.only` gate as those
585 /// statements — never a parallel target type.
586 pub target: DmlTarget,
587 /// `USING <source>` — the data source the target is merged against: a table, a
588 /// derived subquery, or a joined table, reusing the [`TableWithJoins`] shape (a
589 /// relation plus its chained joins, exactly as a `FROM` item; SQL:2016's
590 /// `USING <table reference>`) rather than a parallel source type. A bare
591 /// comma-separated list is not admitted — the merge source is one `<table
592 /// reference>`, so `USING a, b` rejects (engine-verified on pg_query 17).
593 pub using: TableWithJoins<X>,
594 /// The `ON <join predicate>` matching condition.
595 pub on: Expr<X>,
596 /// `WHEN [NOT] MATCHED ...` clauses, in source order. The parser guarantees at
597 /// least one; the standard requires a non-empty list.
598 pub clauses: ThinVec<MergeWhenClause<X>>,
599 /// `RETURNING ...` output clause (PostgreSQL 17+ and DuckDB, probed on 1.5.4;
600 /// where `merge_action()` is also admitted as an output expression). Rides the
601 /// same [`MutationSyntax::returning`](crate::dialect::MutationSyntax) gate as the
602 /// other DML statements — every shipped dialect with `merge` on either accepts
603 /// both or rejects both.
604 pub returning: Option<Returning<X>>,
605 /// Source location and node identity.
606 pub meta: Meta,
607}
608
609/// One `WHEN <match kind> [AND <predicate>] THEN <action>` arm of a [`Merge`].
610///
611/// [`match_kind`](Self::match_kind) distinguishes the three productions
612/// ([`MergeMatchKind`]) and, with them, the actions the parser admits: a
613/// `MATCHED ... THEN INSERT` (like a `NOT MATCHED ... THEN UPDATE`) is rejected. The
614/// single-enum-plus-[`MergeAction`] shape keeps all three productions one node.
615#[derive(Clone, Debug, PartialEq, Eq, Hash)]
616#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
617#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
618pub struct MergeWhenClause<X: Extension = NoExt> {
619 /// Which `WHEN` production (matched / not-matched by target/source); see [`MergeMatchKind`].
620 pub match_kind: MergeMatchKind,
621 /// Predicate that controls this clause.
622 pub condition: Option<Expr<X>>,
623 /// The `THEN` action; see [`MergeAction`].
624 pub action: MergeAction<X>,
625 /// Source location and node identity.
626 pub meta: Meta,
627}
628
629/// Which `WHEN` production a [`MergeWhenClause`] is, and thus which target/source
630/// pairing it fires on and which [`MergeAction`]s it admits.
631///
632/// PostgreSQL 17 (and DuckDB, probed on 1.5.4) split the unmatched case by *which*
633/// side is missing, so the standard two-way `MATCHED`/`NOT MATCHED` grows a third arm.
634/// A bare `WHEN NOT MATCHED` is `NOT MATCHED BY TARGET` (no target row for a source
635/// row → insert), mirroring pg_query's own `matchKind` collapse, so the two spellings
636/// fold to [`NotMatchedByTarget`](Self::NotMatchedByTarget). The `BY SOURCE`/`BY
637/// TARGET` spellings are gated by
638/// [`MutationSyntax::merge_when_not_matched_by`](crate::dialect::MutationSyntax) (off
639/// in ANSI — not SQL:2016 surface); the bare forms ride the
640/// [`MutationSyntax::merge`](crate::dialect::MutationSyntax) statement gate itself.
641#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
642#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
643#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
644pub enum MergeMatchKind {
645 /// `WHEN MATCHED` — a source row paired with a target row; admits `UPDATE`/`DELETE`/
646 /// `DO NOTHING`.
647 Matched,
648 /// `WHEN NOT MATCHED` / `WHEN NOT MATCHED BY TARGET` — an unpaired source row (no
649 /// target); admits `INSERT`/`DO NOTHING`.
650 NotMatchedByTarget,
651 /// `WHEN NOT MATCHED BY SOURCE` — an unpaired target row (no source); admits
652 /// `UPDATE`/`DELETE`/`DO NOTHING`, exactly as the `MATCHED` arm.
653 NotMatchedBySource,
654}
655
656/// The `THEN` action of a [`MergeWhenClause`].
657///
658/// Each arm reuses an existing mutation shape rather than forking one: `Insert` reuses
659/// the `INSERT` value element ([`InsertValue`], which carries `DEFAULT` as well as
660/// expressions) for its single `VALUES` row plus the shared identity-override marker
661/// ([`InsertOverriding`]), and `Update` reuses the `UPDATE ... SET` assignment list
662/// ([`UpdateAssignment`]). `InsertDefault` (`INSERT DEFAULT VALUES`) is a separate
663/// variant so its incompatibility with a column list and an `OVERRIDING` clause is
664/// unrepresentable — PostgreSQL rejects `INSERT (a) DEFAULT VALUES` and `INSERT
665/// OVERRIDING ... DEFAULT VALUES` (engine-verified on pg_query 17). `Delete` and
666/// `DoNothing` carry only `meta`.
667#[derive(Clone, Debug, PartialEq, Eq, Hash)]
668#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
669#[cfg_attr(feature = "serde-deserialize", derive(serde::Deserialize))]
670pub enum MergeAction<X: Extension = NoExt> {
671 /// `INSERT [(<column> [, ...])] [OVERRIDING {SYSTEM | USER} VALUE] VALUES (<value>
672 /// [, ...])` (a `NOT MATCHED [BY TARGET]` arm). The standard merge insert is a
673 /// single value row, so `values` is one row of [`InsertValue`] items — never the
674 /// multi-row [`InsertValues`], which would make an illegal multi-row merge insert
675 /// representable. `overriding` is the identity-column override
676 /// ([`InsertOverriding`], reused from top-level `INSERT`), gated by
677 /// [`MutationSyntax::merge_insert_overriding`](crate::dialect::MutationSyntax)
678 /// (SQL:2016 surface, but DuckDB rejects it in `MERGE`; probed on 1.5.4).
679 Insert {
680 /// Columns in source order.
681 columns: ThinVec<Ident>,
682 /// Optional overriding for this syntax.
683 overriding: Option<InsertOverriding>,
684 /// Values in source order.
685 values: ThinVec<InsertValue<X>>,
686 /// Source location and node identity.
687 meta: Meta,
688 },
689 /// `INSERT DEFAULT VALUES` (a `NOT MATCHED [BY TARGET]` arm) — insert a row of
690 /// column defaults, taking neither a column list nor an `OVERRIDING` clause. Gated
691 /// by [`MutationSyntax::merge_insert_default_values`](crate::dialect::MutationSyntax)
692 /// (PostgreSQL/DuckDB, not SQL:2016).
693 InsertDefault {
694 /// The `DEFAULT VALUES` marker; see [`DefaultValue`].
695 default: DefaultValue,
696 /// Source location and node identity.
697 meta: Meta,
698 },
699 /// `UPDATE SET <assignment> [, ...]` (a `MATCHED` or `NOT MATCHED BY SOURCE` arm).
700 /// Reuses [`UpdateAssignment`].
701 Update {
702 /// assignments in source order.
703 assignments: ThinVec<UpdateAssignment<X>>,
704 /// Source location and node identity.
705 meta: Meta,
706 },
707 /// DuckDB `UPDATE SET *` — copy every matching column from the source row.
708 /// Gated by [`MutationSyntax::merge_update_set_star`](crate::dialect::MutationSyntax).
709 UpdateStar {
710 /// Source location and node identity.
711 meta: Meta,
712 },
713 /// DuckDB `INSERT *` — insert every source column by position/name.
714 /// Gated by [`MutationSyntax::merge_insert_star_by_name`](crate::dialect::MutationSyntax).
715 InsertStar {
716 /// Source location and node identity.
717 meta: Meta,
718 },
719 /// DuckDB `INSERT BY NAME` / `INSERT BY NAME *` — insert matching columns by name.
720 /// Gated by [`MutationSyntax::merge_insert_star_by_name`](crate::dialect::MutationSyntax).
721 InsertByName {
722 /// True for the `INSERT BY NAME *` spelling; false for bare `INSERT BY NAME`.
723 star: bool,
724 /// Source location and node identity.
725 meta: Meta,
726 },
727 /// DuckDB `THEN ERROR` — raise when the arm fires.
728 /// Gated by [`MutationSyntax::merge_error_action`](crate::dialect::MutationSyntax).
729 Error {
730 /// Source location and node identity.
731 meta: Meta,
732 },
733 /// `THEN DELETE` — delete the matched target row.
734 Delete {
735 /// Source location and node identity.
736 meta: Meta,
737 },
738 /// `THEN DO NOTHING` — take no action for this row.
739 DoNothing {
740 /// Source location and node identity.
741 meta: Meta,
742 },
743}