razeri-plugin-contract 0.18.0

The dialect contract Razeri plugins are written against — rendering traits, data types, platform identity
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
// Copyright (C) 2026 Gabriel Shea
// Licensed under either of the Apache License, Version 2.0 or the MIT
// license, at your option.

//! **How this engine performs an upsert** — a shape the dialect describes, not
//! a switch it flips.
//!
//! Razeri's generated load is an upsert: match a row by its key, update it if
//! it is there, insert it if it is not. Engines spell that four ways, and the
//! question *"do you support MERGE?"* has no honest answer on most of them:
//!
//! - some have `MERGE`, and place restrictions on it;
//! - some have no `MERGE` and their own upsert statement instead;
//! - some have no upsert **statement** at all and an upserting TABLE ENGINE
//!   instead. `ClickHouse` is that, measured: no `MERGE`, no
//!   `ON DUPLICATE KEY`, no `ON CONFLICT` — every statement-level spelling
//!   refused on the live engine — and a `ReplacingMergeTree` that collapses
//!   rows sharing the sorting key in background merges. Razeri composes the
//!   load in that idiom: plain versioned `INSERT`s, deduplicated by the
//!   engine, read back through the engine's own read-time guarantee;
//! - some have none of the above, and the honest answer is a refusal.
//!
//! A boolean cannot say this. An engine that merely lacks `MERGE` is not
//! thereby a `MySQL`-style upsert engine, and defaulting it to another
//! vendor's grammar emits SQL it does not have — which is why the answer is a
//! shape, not a flag.
//!
//! ## Two refusals, and why they are different values
//!
//! [`MergeShape::Unsupported`] is a dialect saying *"I measured, and my engine
//! has neither — here is what it has instead"*. [`MergeShape::None`] is the
//! default: nobody has said anything. Both refuse, and the reader is owed a
//! different sentence by each, which is why they are not one variant.

/// How this engine performs the upsert Razeri's load is.
///
/// The default is [`MergeShape::None`]: an engine Razeri has not been told
/// about gets a refusal rather than another vendor's statement.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub enum MergeShape {
    /// `MERGE INTO … USING … WHEN MATCHED … WHEN NOT MATCHED …`, with whatever
    /// this engine's `MERGE` will not do.
    Merge(MergeRestrictions),
    /// No `MERGE`; this engine has an upsert statement of its own.
    Upsert(UpsertForm),
    /// No upsert **statement** at all — the TABLE ENGINE deduplicates, and the
    /// composed load leans into that instead of borrowing a grammar.
    ///
    /// The load is a plain `INSERT … SELECT`; rows that share the target's
    /// sorting key collapse in the engine's background merges, the greatest
    /// value of [`InsertDedup::version_column`] deciding the survivor; and a
    /// read that needs the collapsed answer *now* says
    /// [`InsertDedup::final_read_modifier`]. Razeri's deterministic surrogate
    /// key is what makes this sound: re-inserting a member computes the same
    /// key every time, so there is no lookup and no identity race — the two
    /// halves an upsert statement exists to coordinate.
    ///
    /// This composes a load ([`MergeShape::composes_a_load`] is `true`), and it
    /// is still not a `MERGE`: [`MergeShape::merges`] stays `false`, so every
    /// statement family that needs a real `MERGE` — the single-`WHEN` identity
    /// sync included — takes its join-based spelling here, never a correlated
    /// one. The engine that carries this shape refuses correlated subqueries
    /// outright, which is measured, not assumed.
    InsertDedup(InsertDedup),
    /// **Neither, measured — and this engine's own sentence about it.**
    ///
    /// Razeri composes no load here, and the string is what the reader gets
    /// instead: it lands in the generated `loads.sql` where the load would have
    /// been, so it is the only thing an operator has to act on. Write it
    /// fix-first — what this engine offers *instead* before what it lacks, and
    /// name the near form, because a near form the reader will find alone is
    /// the thing that costs them a day.
    ///
    /// It is prose that lands inside a SQL comment, so it must never spell a
    /// complete statement.
    Unsupported(&'static str),
    /// **The dialect has not answered.**
    ///
    /// Razeri refuses rather than emitting a statement from a different
    /// vendor's grammar, and the conformance kit reports the silence. A dialect
    /// whose engine really has neither says so with
    /// [`MergeShape::Unsupported`], which is a claim rather than a silence and
    /// carries the words to prove it.
    #[default]
    None,
}

/// What this engine's `MERGE` will not do.
///
/// Everything defaults to *no restriction*, so a dialect names only what it
/// has measured. Each field exists because an engine answered an error, not
/// because a manual implied one.
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
#[non_exhaustive]
pub struct MergeRestrictions {
    /// **This engine refuses a `MERGE` carrying only one `WHEN` clause.**
    ///
    /// `Redshift`'s answer is *"MERGE statement with a single WHEN clause is
    /// not enabled"* — a whole statement rejected, not a clause ignored, and
    /// with no session setting that lifts it.
    ///
    /// It matters because Razeri composes one merge of each kind: the core
    /// load, which carries `WHEN MATCHED` and `WHEN NOT MATCHED` and is fine,
    /// and the identity load, which carries one `WHEN NOT MATCHED` and is not.
    /// An engine that sets this takes the portable `INSERT … WHERE NOT EXISTS`
    /// and `DELETE … WHERE NOT EXISTS` pair for the second, which is the same
    /// two effects in two statements.
    pub requires_two_when_clauses: bool,

    /// **This engine's planner reads one slice only from a literal in the
    /// statement text.**
    ///
    /// A load scoped to one day filters both sides of its statement by that
    /// day, and the day reaches the statement as a parameter the caller binds:
    /// `tgt.submission_date = p_slice`. On an engine that answers `false` the
    /// planner treats that bound value as the constant it is, and the day's
    /// partition is the only one read. On an engine that answers `true` the
    /// parameter is opaque until after the plan is chosen, so every partition
    /// is read to write one — the price of the whole history, every day.
    ///
    /// An engine that sets this takes the day through its own dynamic-execution
    /// form instead, with the value formatted into the statement as a literal,
    /// so the planner sees the constant before it chooses.
    ///
    /// The default is `false`: a bound parameter is enough. `BigQuery`,
    /// measured, reads exactly the same bytes from a bound parameter as from a
    /// literal, and an engine whose planner differs is the one whose own
    /// dialect can say so.
    pub slice_pruning_needs_literal: bool,
}

impl MergeRestrictions {
    /// **No restriction Razeri has measured** — the ordinary answer for an
    /// engine whose `MERGE` takes what Razeri composes.
    #[must_use]
    pub const fn none() -> Self {
        Self { requires_two_when_clauses: false, slice_pruning_needs_literal: false }
    }

    /// **This engine refuses a `MERGE` carrying only one `WHEN` clause.** See
    /// [`MergeRestrictions::requires_two_when_clauses`].
    #[must_use]
    pub const fn requiring_two_when_clauses() -> Self {
        Self { requires_two_when_clauses: true, slice_pruning_needs_literal: false }
    }

    /// **This engine's planner reads one slice only from a literal.** See
    /// [`MergeRestrictions::slice_pruning_needs_literal`].
    #[must_use]
    pub const fn requiring_a_literal_slice() -> Self {
        Self { requires_two_when_clauses: false, slice_pruning_needs_literal: true }
    }

    /// The same restrictions with the literal-slice one added, so an engine
    /// that has two of them states both without a constructor per pair.
    #[must_use]
    pub const fn and_a_literal_slice(self) -> Self {
        Self { slice_pruning_needs_literal: true, ..self }
    }
}

/// How an engine whose TABLE ENGINE deduplicates takes Razeri's load.
///
/// Both fields are spellings the composed load and the generated views embed,
/// so they live on the shape rather than as separate renderer hooks — one fact,
/// one place, nothing for a consistency gate to hold together.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub struct InsertDedup {
    /// **The Razeri-owned audit column that decides the surviving row** when
    /// two rows share the sorting key. Mandatory: without a version column the
    /// engine keeps whichever row a background merge saw last, which is
    /// nondeterministic, and Razeri refuses nondeterminism. The materializer
    /// verifies the named column is one the versioned table really carries; a
    /// design that cannot carry it is refused by name, never downgraded.
    pub version_column: &'static str,
    /// The modifier a read of the versioned table appends when it needs the
    /// engine's one-row-per-key guarantee at read time rather than after the
    /// next background merge (`FINAL` on the engine that carries this shape).
    /// It is a read COST, which is why only the generated views say it and the
    /// load itself never does.
    pub final_read_modifier: &'static str,
}

impl InsertDedup {
    /// The shape, with both spellings stated.
    #[must_use]
    pub const fn new(version_column: &'static str, final_read_modifier: &'static str) -> Self {
        Self { version_column, final_read_modifier }
    }
}

/// The upsert statement an engine without `MERGE` spells instead.
///
/// A form is added here when Razeri has *composed* the load in it, not when an
/// engine is known to have one: naming a form Razeri cannot write is how a
/// dialect ends up emitting another vendor's grammar.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
#[non_exhaustive]
pub enum UpsertForm {
    /// `INSERT … ON DUPLICATE KEY UPDATE`. It reads the table's own unique keys
    /// rather than naming a conflict target, so an engine that spells
    /// `ON CONFLICT` is not this: that engine is
    /// [`OnConflictDoUpdate`](Self::OnConflictDoUpdate).
    ///
    /// **Two engines have it and exactly two of Razeri's dialects name it** —
    /// `MySQL` and `MariaDB`. Naming the form is a claim about an engine,
    /// never a default: an engine that merely lacks `MERGE` does not thereby
    /// speak this grammar. `ClickHouse`, asked, answers *"Syntax error …
    /// failed at position 68 (`DUPLICATE`)"* and states
    /// [`MergeShape::Unsupported`] instead.
    OnDuplicateKeyUpdate,
    /// `INSERT … ON CONFLICT (<key>) DO UPDATE SET <column> = EXCLUDED.<column>`.
    ///
    /// It names its conflict target, and Razeri names the core table's primary
    /// key there: the hash key, the version, and the slice where the table has
    /// one. The row that arrived is read as `EXCLUDED`. The target must be a
    /// primary key or a unique index the engine holds, column for column, or
    /// the engine refuses the statement, which is why Razeri names the key its
    /// own DDL declared and nothing else.
    ///
    /// `PostgreSQL`'s grammar, and the upsert of `SQLite`, `DuckDB` and
    /// `CockroachDB` as well. Naming it is a claim that the engine runs this
    /// statement, not that it lacks `MERGE`: an engine with a `MERGE` Razeri
    /// composes says [`MergeShape::Merge`], whatever else it has.
    OnConflictDoUpdate,
}

impl MergeShape {
    /// Whether Razeri's two-clause core load can be a `MERGE` here.
    #[must_use]
    pub const fn merges(&self) -> bool {
        matches!(self, Self::Merge(_))
    }

    /// Whether a **one**-clause `MERGE` runs here — the identity load's shape.
    ///
    /// An engine with `MERGE` can still answer no, and one does.
    #[must_use]
    pub const fn merges_a_single_when_clause(&self) -> bool {
        match self {
            Self::Merge(restrictions) => !restrictions.requires_two_when_clauses,
            _ => false,
        }
    }

    /// **Whether the day a scoped load is given has to reach this engine as a
    /// literal** for it to read that day's partition instead of all of them.
    ///
    /// `false` is the ordinary answer: the parameter the caller binds to the
    /// load procedure is constant enough for the planner. A shape that is not a
    /// `MERGE` answers `false` as well — it carries no restrictions, and Razeri
    /// binds the day rather than formatting it in.
    #[must_use]
    pub const fn slice_predicate_needs_a_literal(&self) -> bool {
        match self {
            Self::Merge(restrictions) => restrictions.slice_pruning_needs_literal,
            _ => false,
        }
    }

    /// The engine's own upsert statement, where it has one instead of `MERGE`.
    #[must_use]
    pub const fn upsert_form(&self) -> Option<UpsertForm> {
        match self {
            Self::Upsert(form) => Some(*form),
            _ => None,
        }
    }

    /// The insert-and-deduplicate composition, where the table engine is the
    /// upsert — see [`MergeShape::InsertDedup`].
    #[must_use]
    pub const fn insert_dedup(&self) -> Option<InsertDedup> {
        match self {
            Self::InsertDedup(form) => Some(*form),
            _ => None,
        }
    }

    /// **Why Razeri cannot compose a load here**, in words, or `None` if it
    /// can. The sentence is the one a reader acts on.
    ///
    /// The one accessor every surface reads — the materializer that declines to
    /// compose the load, and the `loads.sql` that says so where an operator
    /// opens it — so neither one holds a second copy of the words.
    #[must_use]
    pub const fn refusal(&self) -> Option<&'static str> {
        match self {
            Self::Unsupported(orientation) => Some(orientation),
            Self::None => Some(
                "this engine states no MERGE and no upsert statement Razeri can compose, so \
                 there is no generated load for it. A dialect says which it has by returning \
                 MergeShape::Merge or MergeShape::Upsert; until then Razeri refuses rather than \
                 emitting another engine's grammar.",
            ),
            _ => None,
        }
    }

    /// Whether Razeri composes a load for this engine at all.
    ///
    /// The one question the materializer asks before it builds the load
    /// procedures — asked of the shape, never of a dialect's name.
    #[must_use]
    pub const fn composes_a_load(&self) -> bool {
        self.refusal().is_none()
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    /// A dialect that has said nothing gets a refusal, not `MySQL`'s
    /// statement. That inversion is the whole point of the type.
    #[test]
    fn a_dialect_that_states_nothing_refuses_rather_than_borrowing_a_grammar() {
        let shape = MergeShape::default();
        assert!(!shape.merges());
        assert!(!shape.merges_a_single_when_clause());
        assert_eq!(shape.upsert_form(), None);
        assert!(shape.refusal().is_some());
        assert!(!shape.composes_a_load());
    }

    /// **A measured refusal and a silent one are different values**, and the
    /// reader gets a different sentence from each. They were one variant, and a
    /// dialect that had measured could not say so.
    #[test]
    fn a_measured_refusal_carries_the_engines_own_sentence_and_a_silent_one_does_not() {
        let measured = MergeShape::Unsupported("this engine de-duplicates in the table engine");
        assert_eq!(
            measured.refusal(),
            Some("this engine de-duplicates in the table engine"),
            "a stated refusal answers in its own words"
        );
        assert_ne!(
            MergeShape::None.refusal(),
            measured.refusal(),
            "silence and a measured answer must not read the same"
        );
        assert!(!measured.composes_a_load());
        assert!(!measured.merges());
        assert_eq!(measured.upsert_form(), None);
    }

    /// The shapes that DO compose a load carry no refusal, so nothing
    /// downstream has to special-case them.
    #[test]
    fn a_shape_that_composes_a_load_carries_no_refusal() {
        for shape in [
            MergeShape::Merge(MergeRestrictions::none()),
            MergeShape::Merge(MergeRestrictions::requiring_two_when_clauses()),
            MergeShape::Upsert(UpsertForm::OnDuplicateKeyUpdate),
            MergeShape::InsertDedup(InsertDedup::new("_loaded_at", "FINAL")),
        ] {
            assert_eq!(shape.refusal(), None, "{shape:?} composes a load");
            assert!(shape.composes_a_load(), "{shape:?} composes a load");
        }
    }

    /// **Insert-and-dedup composes a load and is still not a `MERGE`.** Every
    /// family that needs a real `MERGE` — the single-`WHEN` identity sync
    /// included — must keep taking its non-merge spelling on this shape, and
    /// the two spellings the composition embeds travel on the shape itself.
    #[test]
    fn insert_dedup_composes_a_load_without_claiming_a_merge() {
        let shape = MergeShape::InsertDedup(InsertDedup::new("_loaded_at", "FINAL"));
        assert!(shape.composes_a_load());
        assert!(!shape.merges());
        assert!(!shape.merges_a_single_when_clause());
        assert_eq!(shape.upsert_form(), None, "the engine has no upsert statement");
        let form = shape.insert_dedup().expect("the composition's spellings");
        assert_eq!(form.version_column, "_loaded_at");
        assert_eq!(form.final_read_modifier, "FINAL");
        assert_eq!(
            MergeShape::Merge(MergeRestrictions::none()).insert_dedup(),
            None,
            "a merge engine is not an engine-dedup engine"
        );
    }

    /// An engine can have `MERGE` and still refuse the one-clause form, so the
    /// shape answers the two questions separately and both are asked.
    #[test]
    fn a_merge_with_a_clause_minimum_answers_the_two_questions_differently() {
        let restricted = MergeShape::Merge(MergeRestrictions::requiring_two_when_clauses());
        assert!(restricted.merges());
        assert!(!restricted.merges_a_single_when_clause());

        let unrestricted = MergeShape::Merge(MergeRestrictions::none());
        assert!(unrestricted.merges());
        assert!(unrestricted.merges_a_single_when_clause());
    }

    /// **A bound parameter is constant enough until an engine says otherwise.**
    /// The default matters more than the exception here: a dialect that has
    /// never been asked the question must not be made to render the day as a
    /// literal, because formatting a value into statement text is the thing
    /// every engine's planner reads differently.
    #[test]
    fn a_slice_bound_as_a_parameter_prunes_unless_an_engine_says_it_needs_a_literal() {
        let ordinary = MergeRestrictions::none();
        assert!(!ordinary.slice_pruning_needs_literal);
        assert!(!MergeRestrictions::default().slice_pruning_needs_literal);
        assert!(!MergeShape::Merge(ordinary).slice_predicate_needs_a_literal());

        let literal_only = MergeRestrictions::requiring_a_literal_slice();
        assert!(literal_only.slice_pruning_needs_literal);
        assert!(!literal_only.requires_two_when_clauses, "one restriction is not the other");
        assert!(MergeShape::Merge(literal_only).slice_predicate_needs_a_literal());
    }

    /// **The two restrictions are independent, and an engine with both states
    /// both.** They were one constructor apiece, and an engine that had two of
    /// them could only say one.
    #[test]
    fn an_engine_with_two_restrictions_states_both() {
        let both = MergeRestrictions::requiring_two_when_clauses().and_a_literal_slice();
        assert!(both.requires_two_when_clauses);
        assert!(both.slice_pruning_needs_literal);

        let shape = MergeShape::Merge(both);
        assert!(shape.merges());
        assert!(!shape.merges_a_single_when_clause());
        assert!(shape.slice_predicate_needs_a_literal());

        assert_eq!(
            MergeRestrictions::none().and_a_literal_slice(),
            MergeRestrictions::requiring_a_literal_slice(),
            "adding the one restriction to none of them is stating that one"
        );
    }

    /// A shape that is not a `MERGE` carries no restrictions, so it never asks
    /// for a literal: Razeri binds the day there and formats nothing in.
    #[test]
    fn a_shape_that_is_not_a_merge_never_asks_for_a_literal_slice() {
        for shape in [
            MergeShape::Upsert(UpsertForm::OnDuplicateKeyUpdate),
            MergeShape::InsertDedup(InsertDedup::new("_loaded_at", "FINAL")),
            MergeShape::Unsupported("this engine has neither"),
            MergeShape::None,
        ] {
            assert!(!shape.slice_predicate_needs_a_literal(), "{shape:?}");
        }
    }

    /// An upsert engine is not a merge engine, and neither is a refusal.
    #[test]
    fn an_upsert_engine_is_not_a_merge_engine() {
        let upsert = MergeShape::Upsert(UpsertForm::OnDuplicateKeyUpdate);
        assert!(!upsert.merges());
        assert_eq!(upsert.upsert_form(), Some(UpsertForm::OnDuplicateKeyUpdate));
        assert!(upsert.refusal().is_none());
    }

    /// **`ON CONFLICT` is its own form, and a claim like the other one.** It
    /// composes a load, it is not a `MERGE`, and it carries no restriction, so
    /// every family that needs a real `MERGE` takes its other spelling here.
    #[test]
    fn an_on_conflict_engine_composes_a_load_and_is_not_a_merge_engine() {
        let shape = MergeShape::Upsert(UpsertForm::OnConflictDoUpdate);
        assert!(shape.composes_a_load());
        assert_eq!(shape.refusal(), None);
        assert!(!shape.merges());
        assert!(!shape.merges_a_single_when_clause());
        assert!(!shape.slice_predicate_needs_a_literal());
        assert_eq!(shape.upsert_form(), Some(UpsertForm::OnConflictDoUpdate));
        assert_ne!(
            shape.upsert_form(),
            MergeShape::Upsert(UpsertForm::OnDuplicateKeyUpdate).upsert_form(),
            "the two upsert statements are two grammars"
        );
    }
}