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