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}