Skip to main content

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}