razeri-plugin-contract 0.7.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
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
// Copyright (C) 2026 Gabriel Shea
// Licensed under either of the Apache License, Version 2.0 or the MIT
// license, at your option.

//! **The one derivation of a surrogate key**, composed from the vocabulary a
//! dialect states.
//!
//! [`SidSpelling`] says what one engine calls `LOWER` and `TRIM`, how it joins
//! strings, how it casts a value to text and how it spells a 64-bit hash. This
//! module turns those five answers into the expression a warehouse runs:
//!
//! ```text
//! guard( hash( normalize(part₁) ‖ '|' ‖ … ‖ normalize(partₙ) ) )
//! ```
//!
//! It is published so that a dialect author can render their own key
//! expression and run it. Without it, *"the same key on every engine"* is a
//! claim the person making it cannot check — and a key that differs by one
//! character keys a warehouse to numbers nothing else reproduces.
//!
//! # What a dialect decides, and what it does not
//!
//! The **vocabulary** is the dialect's, and it is the only thing that varies.
//! The **derivation** is fixed: there is no table of engine names here and no
//! way to add one, so a third party spells their own engine's key by stating a
//! [`SidSpelling`] and nothing else.
//!
//! Two expressions built from the same five fields differ **only** in those
//! fields. That is what makes the claim a proof rather than an assumption, and
//! it is why a dialect states a vocabulary instead of writing an expression.
//!
//! # The parts of the derivation
//!
//! - **normalize** is `LOWER(TRIM(x))` as this engine spells it, and it is the
//!   only normalization permitted — the natural value alone, never salted, so
//!   one natural key reaches one number in every context. The canonical reading
//!   is the SQL majority's: trim the ASCII space and nothing else, fold case by
//!   the locale-independent simple mapping, one code point to one;
//! - the parts are normalized **then** joined, never the other way round. A
//!   `TRIM` over a concatenation reaches only the outer edges, which would
//!   leave interior spaces beside the separator inside the hash input;
//! - **guard** is the [`NULL_SID`] sentinel, and it fires when **any** part is
//!   NULL: any NULL component keys the whole member to the sentinel;
//! - **hash** is either this engine's own spelling or a call to the
//!   [`HELPER_FUNCTION_NAME`] function, whose body is that same spelling. The
//!   two sites are one derivation — see [`HashSite`].
//!
//! # Why the normalization can appear twice
//!
//! Where the hash is reached through the helper, the helper normalizes its own
//! input as well, so the rendered call reads `generate_sid(LOWER(TRIM(x)))` and
//! the normalization runs twice. It is the same string both times: `TRIM` of an
//! already-trimmed value and `LOWER` of an already-lowered one are identities,
//! so no key moves. Spelling it at the call site anyway is what makes every
//! engine one shape — a composite must be normalized before it is joined, and
//! the helper only ever sees the joined result.

/// **The sentinel a member with any NULL key part is keyed to**, `(-2^63) + 2`.
///
/// One number, on every engine, for one reason: a NULL natural key is an
/// absence rather than a value, and two absences are not the same member. It
/// sits two above the minimum so that a warehouse can still reserve the
/// minimum itself.
pub const NULL_SID: i64 = -9_223_372_036_854_775_806_i64;

/// **The name of the surrogate-key function — the only one.**
///
/// One public name everywhere, whoever defines it. On engines where the hash is
/// an ordinary SQL expression, Razeri emits a function of this name whose body
/// is the derivation below. On engines where the hash lives outside SQL — a
/// compiled library, an assembly, a registered handler — the dialect's
/// prerequisites install an artifact under this same name and
/// [`SidSpelling::hash`] names it directly, so nothing emits a second
/// definition.
///
/// A second name is a second contract. There is no alias and no transitional
/// spelling.
pub const HELPER_FUNCTION_NAME: &str = "generate_sid";

/// `İ`, LATIN CAPITAL LETTER I WITH DOT ABOVE (U+0130).
///
/// The **only** code point whose simple and full Unicode lowercase mappings
/// differ with no context at all: the full mapping adds a combining dot the
/// simple one does not. Named so the character cannot be mistaken for an
/// ordinary `I` at a glance in the one place it is load-bearing.
///
/// It is not the whole story. Full case conversion also carries a *conditional*
/// mapping — [`CAPITAL_SIGMA`] — which is a different fact with a different
/// proof. Both are in [`PRE_FOLD_SUBSTITUTIONS`], for the same purpose and for
/// different reasons.
pub const DOTTED_CAPITAL_I: char = '\u{0130}';

/// `Σ`, GREEK CAPITAL LETTER SIGMA (U+03A3).
///
/// Its **simple** lowercase is [`SMALL_SIGMA`] in every position. Its **full**
/// lowercase is [`SMALL_SIGMA`] too — *except* word-finally, where Unicode's
/// final-sigma condition makes it `ς` (U+03C2). It is the one
/// language-independent conditional mapping there is.
///
/// **Word-finally, not string-finally**: an engine returns `ς` for a sigma
/// before a space and before a hyphen alike, so a substitution that handled
/// only the last character of a value would be a local approximation. Replacing
/// every `Σ` is exact, because the simple mapping has no context to lose.
pub const CAPITAL_SIGMA: char = '\u{03A3}';

/// `σ`, GREEK SMALL LETTER SIGMA (U+03C3) — the **simple** lowercase of
/// [`CAPITAL_SIGMA`] in every position, and therefore what the canonical
/// normalization produces.
///
/// Note the direction: `ς` is left alone. Its own simple lowercase is itself,
/// so `ς` and `σ` are two code points and two keys. Folding one into the other
/// would be a third normalization rather than a repair.
pub const SMALL_SIGMA: char = '\u{03C3}';

/// **What has to be replaced before an engine's `LOWER` runs**, so that the
/// composition is exactly the canonical simple case mapping.
///
/// Applied in order, each substitution wrapping the previous one, by
/// [`normalize`] on every engine. The list is a property of the canonical fold
/// rather than of any engine: every engine whose `LOWER` performs full case
/// conversion needs this identical pair and differs only in what it calls
/// `LOWER` and `REPLACE`.
///
/// Each entry maps a code point to a spelling whose full and simple lowercase
/// agree, so the engine's own `LOWER` can then run over the whole value
/// untouched:
///
/// - [`DOTTED_CAPITAL_I`] → `'I'`, whose lowercase is a bare `i` everywhere —
///   the *unconditional* divergence;
/// - [`CAPITAL_SIGMA`] → [`SMALL_SIGMA`], which `LOWER` then leaves alone
///   because it is already lowercase — the *conditional* one.
pub const PRE_FOLD_SUBSTITUTIONS: &[(char, char)] =
    &[(DOTTED_CAPITAL_I, 'I'), (CAPITAL_SIGMA, SMALL_SIGMA)];

use crate::sid_spelling::{Concat, Lower, SidSpelling, TextCast, Trim};

/// Where the hash itself is spelled.
#[derive(Debug, Clone, Copy)]
pub enum HashSite<'a> {
    /// Inline this engine's own hash spelling, from [`SidSpelling::hash`].
    Native,
    /// Inline this engine's own hash spelling, in the schema Razeri installed
    /// it in — [`SidPrerequisiteHome::Schema`](crate::SidPrerequisiteHome).
    ///
    /// The schema, a dot, and then [`SidSpelling::hash`] exactly as [`Native`]
    /// writes it. So a dialect that names a home owes a `hash` whose opening
    /// text BEGINS with the function's own name: `razeri_xxh64(` qualifies to
    /// `razeri.razeri_xxh64(`, while a spelling that opened with a cast —
    /// `CAST(xxHash64(` — would put the schema in front of the cast and name
    /// nothing. Every engine that opens with a cast has a server-global
    /// function and names no home, which is why the two facts have never met.
    ///
    /// [`Native`]: Self::Native
    NativeIn(&'a str),
    /// Call the [`HELPER_FUNCTION_NAME`] function by this — possibly
    /// schema-qualified — name. Its body is the [`HashSite::Native`] spelling,
    /// so the two sites are the same derivation.
    ///
    /// **The qualifier is the function's home, not the caller's schema.** One
    /// function is created per deployment unit, so a call from any other schema
    /// of that unit has to name the schema that has it. A caller that qualifies
    /// with its own schema names a function nothing created.
    Helper(&'a str),
}

/// **`TRIM(expr)` in this engine's spelling** — the space around a value
/// taken off, and nothing else.
///
/// Public because the canonical normalization is not the only thing that has
/// to take the space off a value the source wrote: reading an amount the
/// source spelled its own way starts the same way, and two spellings of
/// `TRIM` would be two answers to one question.
///
/// Parameters:
/// - `spelling`: this engine's vocabulary;
/// - `expr`: an already-rendered value expression.
#[must_use]
pub fn normalize_trim(spelling: SidSpelling, expr: &str) -> String {
    match spelling.trim {
        Trim::Function(name) => format!("{name}({expr})"),
        Trim::LtrimRtrim => format!("LTRIM(RTRIM({expr}))"),
        Trim::FunctionWithSpaceSet(name) => format!("{name}({expr}, ' ')"),
    }
}

/// **`LOWER(TRIM(expr))` in this engine's spelling** — the canonical
/// normalization, and the only one permitted: the natural value alone, never
/// salted.
///
/// Public because what an engine's fold and trim return is a separate
/// measurement from what its hash returns, and a test that can make only the
/// first one still proves half the claim. A test that re-spelled the
/// normalization would be testing itself.
///
/// Parameters:
/// - `spelling`: this engine's vocabulary;
/// - `expr`: an already-rendered value expression.
#[must_use]
pub fn normalize(spelling: SidSpelling, expr: &str) -> String {
    let trimmed = normalize_trim(spelling, expr);
    let Lower { function, replace, pattern_literal_prefix: prefix, match_collation } =
        spelling.lower;
    // The subject is pinned to a binary collation where the engine would
    // otherwise match a replacement pattern under the value's own collation —
    // which, under a case-insensitive one, also replaces the final sigma the
    // canonical mapping leaves alone.
    let subject = match match_collation {
        Some(collation) => format!("{trimmed} COLLATE {collation}"),
        None => trimmed,
    };
    let substituted = PRE_FOLD_SUBSTITUTIONS.iter().fold(subject, |expr, (from, to)| {
        format!("{replace}({expr}, {prefix}'{from}', {prefix}'{to}')")
    });
    format!("{function}({substituted})")
}

/// **`CAST(expr AS text)` in this engine's spelling**, total over NULL.
///
/// Public for the reason [`normalize_trim`] is public: turning a value into
/// text is not something only the key derivation does. A caller that has to
/// read a value back out of an engine as text — because the transport it
/// arrives over encodes the engine's own types in a form the same engine will
/// not accept back — needs this engine's cast and not a second opinion about
/// it. Two spellings of one cast are two answers to one question.
///
/// Parameters:
/// - `spelling`: this engine's vocabulary;
/// - `expr`: an already-rendered value expression.
#[must_use]
pub fn cast_to_text(spelling: SidSpelling, expr: &str) -> String {
    match spelling.cast_type {
        TextCast::Direct(ty) => format!("CAST({expr} AS {ty})"),
        TextCast::ClickHouseNullTolerant(ty) => {
            format!("assumeNotNull(CAST({expr} AS Nullable({ty})))")
        }
    }
}

/// **The join, written for an engine whose `CONCAT` takes exactly two
/// arguments**: the parts and the separators between them, folded left to
/// right, one call per pair.
///
/// The items are the same items the variadic call lists, in the same order, so
/// the concatenated string is the same string and the key does not move.
///
/// An empty key has nothing to nest, and the bare call is what the variadic
/// form renders for it — this form answers the same way rather than inventing a
/// second degenerate spelling.
fn nest_pairwise(name: &str, normalized: &[String]) -> String {
    let mut parts = normalized.iter();
    let Some(first) = parts.next() else { return format!("{name}()") };
    parts.fold(first.clone(), |left, next| format!("{name}({name}({left}, '|'), {next})"))
}

/// **The derivation**: normalize each part, join on `'|'`, hash the result.
///
/// `text_args` are already-rendered value expressions, already cast to text
/// where the caller needs it — a column reference inside a load statement, a
/// parameter name inside the key function's own body. Callers name the value
/// differently, so composing the name here would mean knowing which caller was
/// asking.
///
/// The result carries **no NULL guard**; [`guard_null_members`] wraps this
/// rather than each engine's arm spelling a guard of its own.
///
/// Parameters:
/// - `text_args`: the key's parts, already cast to text;
/// - `spelling`: this engine's vocabulary;
/// - `site`: whether the hash is inlined or reached through the function.
#[must_use]
pub fn sid_derivation(text_args: &[String], spelling: SidSpelling, site: HashSite<'_>) -> String {
    let normalized: Vec<String> = text_args.iter().map(|arg| normalize(spelling, arg)).collect();
    let joined = if normalized.len() == 1 {
        normalized[0].clone()
    } else {
        match spelling.concat {
            Concat::Function(name) => format!("{name}({})", normalized.join(", '|', ")),
            Concat::FunctionBinary(name) => nest_pairwise(name, &normalized),
            Concat::Plus => normalized.join(" + '|' + "),
        }
    };
    match site {
        HashSite::Native => format!("{}{joined}{}", spelling.hash.0, spelling.hash.1),
        HashSite::NativeIn(home) => {
            format!("{home}.{}{joined}{}", spelling.hash.0, spelling.hash.1)
        }
        HashSite::Helper(name) => format!("{name}({joined})"),
    }
}

/// **The [`NULL_SID`] sentinel, wrapped around the derivation.**
///
/// `raw_args` are the arguments **before** any cast — a cast preserves
/// NULL-ness, and `CAST(x AS TEXT) IS NULL` only makes the guard harder to
/// read.
///
/// **Any NULL component of a composite keys the whole member to the sentinel.**
/// That closes a collision as well as an omission: on several engines
/// `CONCAT('x', '|', NULL)` is `'x|'`, byte-identical to
/// `CONCAT('x', '|', '')`, so `('boston', NULL)` and `('boston', '')` would be
/// one member. Testing the components before they are joined is the only place
/// the distinction still exists.
///
/// The guard is a `CASE WHEN` rather than a `COALESCE` because it tests the
/// **input**. A `COALESCE` over the hash's *output* reaches the sentinel only
/// where the hash propagates NULL, and on at least one engine the hash of NULL
/// is a perfectly ordinary number.
///
/// An empty key renders no guard rather than an empty one.
///
/// Parameters:
/// - `raw_args`: the key's parts as the caller wrote them, before any cast;
/// - `derivation`: the expression [`sid_derivation`] produced.
#[must_use]
pub fn guard_null_members(raw_args: &[String], derivation: String) -> String {
    if raw_args.is_empty() {
        return derivation;
    }
    let test = raw_args.iter().map(|arg| format!("{arg} IS NULL")).collect::<Vec<_>>().join(" OR ");
    format!("CASE WHEN {test} THEN {NULL_SID} ELSE {derivation} END")
}

/// **The surrogate key as one expression**: cast, derive, guard.
///
/// This is the whole composition, and it is what every producer of a key
/// expression emits — the load that keys a dimension, the body of the
/// per-entity key function, the formula a design can name. Rendering it for
/// your own engine and running it is how you check that your
/// [`SidSpelling`] reaches the number every other engine reaches.
///
/// ```
/// use razeri_plugin_contract::{ANSI_SID_SPELLING, sid};
///
/// let key = sid::sid_expression(ANSI_SID_SPELLING, None, &["t.city".to_owned()]);
/// assert!(key.starts_with("CASE WHEN t.city IS NULL THEN "));
/// assert!(key.contains("generate_sid("));
/// ```
///
/// # `helper_home` is the key function's schema, never the caller's
///
/// The only thing a schema does here is qualify the call to
/// [`HELPER_FUNCTION_NAME`] on the engines whose
/// [`SidSpelling::through_helper`] is true. Where the key function is an
/// installed artifact instead, its own name is rendered and this argument is
/// ignored entirely — on at least one engine a compiled function lives server
/// globally and a qualified call to it does not resolve at all.
///
/// So the schema this wants is the one the function was **created** in, which
/// is a property of the deployment unit rather than of whichever entity is
/// being keyed. `None` renders the call unqualified, which is correct wherever
/// nothing is qualified: an engine without schemas, or a design that names no
/// schema.
///
/// Parameters:
/// - `spelling`: this engine's vocabulary;
/// - `helper_home`: the schema the key function was created in, where the call
///   needs qualifying;
/// - `parts`: the key's parts as raw value expressions, in order.
#[must_use]
pub fn sid_expression(
    spelling: SidSpelling,
    helper_home: Option<&str>,
    parts: &[String],
) -> String {
    let qualified = helper_home
        .map_or_else(|| HELPER_FUNCTION_NAME.to_owned(), |s| format!("{s}.{HELPER_FUNCTION_NAME}"));
    sid_expression_through(spelling, &qualified, parts)
}

/// **The same expression, with the helper's name already spelled by the
/// target.**
///
/// [`sid_expression`] writes the helper's name the way most engines read it —
/// the schema, a dot, the name, all bare. That is a name on every engine that
/// leaves an unquoted word alone and a *different* name on one that folds it,
/// where the function was created under the spelling that engine's own DDL
/// wrote. A caller holding the dialect spells the name first and hands it in
/// here, so the call and the `CREATE FUNCTION` say the same word.
///
/// Parameters:
/// - `spelling`: this engine's vocabulary;
/// - `helper`: the key helper's name, qualified and spelled;
/// - `parts`: the key's parts as raw value expressions, in order.
#[must_use]
pub fn sid_expression_through(spelling: SidSpelling, helper: &str, parts: &[String]) -> String {
    let cast: Vec<String> = parts.iter().map(|arg| cast_to_text(spelling, arg)).collect();
    let site = if spelling.through_helper { HashSite::Helper(helper) } else { HashSite::Native };
    guard_null_members(parts, sid_derivation(&cast, spelling, site))
}

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

    /// A vocabulary for the composition tests. It is a fixture, not an engine:
    /// what is under test here is the derivation, and every real engine's
    /// vocabulary comes off its own dialect.
    const HELPER_ROUTED: SidSpelling =
        SidSpelling { platform: "test", hash: ("pg_xxhash64(", ")"), ..ANSI_SID_SPELLING };

    /// An empty key cannot be guarded, and must not render `CASE WHEN  THEN` —
    /// an empty test is worse than no test.
    #[test]
    fn an_empty_key_renders_no_guard_rather_than_an_empty_one() {
        assert!(!sid_expression(HELPER_ROUTED, None, &[]).contains("CASE WHEN"));
    }

    /// **Every spelling renders both substitutions, in order.**
    ///
    /// Whether an engine's `LOWER` is the simple mapping or the full one is
    /// decided by the collation its database was created with rather than by
    /// the engine, so the substitution is unconditional: it costs nothing where
    /// the lowercase is already simple, and it is the difference between one
    /// number and two where it is not.
    #[test]
    fn a_spelling_pre_replaces_both_divergent_code_points() {
        assert_eq!(
            normalize(HELPER_ROUTED, "x"),
            "LOWER(REPLACE(REPLACE(TRIM(x), '\u{0130}', 'I'), '\u{03A3}', '\u{03C3}'))"
        );
    }

    /// **An engine that matches a replacement pattern under the value's own
    /// collation gets the subject pinned and the pattern prefixed.**
    #[test]
    fn a_collation_sensitive_replacement_pins_its_subject_and_prefixes_its_pattern() {
        let pinned = SidSpelling {
            lower: Lower {
                pattern_literal_prefix: "N",
                match_collation: Some("Latin1_General_BIN2"),
                ..ANSI_SID_SPELLING.lower
            },
            ..HELPER_ROUTED
        };
        assert_eq!(
            normalize(pinned, "x"),
            "LOWER(REPLACE(REPLACE(TRIM(x) COLLATE Latin1_General_BIN2, N'\u{0130}', N'I'), \
             N'\u{03A3}', N'\u{03C3}'))"
        );
    }

    /// **A spelling that does not route through the key function ignores the
    /// schema.**
    ///
    /// An engine whose key function is an installed artifact must render a
    /// **bare** call: the artifact is registered where that engine registers
    /// artifacts, not in a schema Razeri created.
    #[test]
    fn a_spelling_that_inlines_its_artifact_ignores_the_schema_it_is_passed() {
        let external =
            SidSpelling { hash: ("generate_sid(", ")"), through_helper: false, ..HELPER_ROUTED };
        let qualified = sid_expression(external, Some("acme"), &["x".to_owned()]);
        let bare = sid_expression(external, None, &["x".to_owned()]);
        assert_eq!(
            qualified, bare,
            "passing a schema moved the key function's name — the artifact is not in a schema \
             Razeri created"
        );
        assert!(!qualified.contains("acme."), "the emitted call qualifies an installed artifact");
    }

    /// The call through the key function and the inlined native spelling are
    /// the same derivation over the same normalized input — the only difference
    /// is where the hash is written down.
    #[test]
    fn the_helper_site_and_the_native_site_normalize_identically() {
        let args = ["a".to_owned(), "b".to_owned()];
        let native = sid_derivation(&args, HELPER_ROUTED, HashSite::Native);
        let helper = sid_derivation(&args, HELPER_ROUTED, HashSite::Helper(HELPER_FUNCTION_NAME));
        let a = "LOWER(REPLACE(REPLACE(TRIM(a), '\u{0130}', 'I'), '\u{03A3}', '\u{03C3}'))";
        let b = "LOWER(REPLACE(REPLACE(TRIM(b), '\u{0130}', 'I'), '\u{03A3}', '\u{03C3}'))";
        assert_eq!(native, format!("pg_xxhash64(CONCAT({a}, '|', {b}))"));
        assert_eq!(helper, format!("generate_sid(CONCAT({a}, '|', {b}))"));
    }

    /// **Any NULL part keys the whole member to the sentinel**, and the guard
    /// tests the parts rather than the hash's result.
    #[test]
    fn any_null_part_reaches_the_sentinel_and_the_guard_tests_the_input() {
        let composite = sid_expression(HELPER_ROUTED, None, &["a".to_owned(), "b".to_owned()]);
        assert!(composite.starts_with("CASE WHEN a IS NULL OR b IS NULL THEN "));
        assert!(composite.contains(&NULL_SID.to_string()));
        assert!(!composite.contains("COALESCE"));
    }

    /// **An engine whose `CONCAT` takes exactly two arguments gets one call per
    /// pair**, nested left to right, over the same items the variadic call
    /// lists.
    #[test]
    fn a_binary_concat_nests_one_call_per_pair() {
        let spelling = SidSpelling { concat: Concat::FunctionBinary("CONCAT"), ..HELPER_ROUTED };
        let part = |arg: &str| normalize(spelling, arg);
        let two = ["a".to_owned(), "b".to_owned()];
        assert_eq!(
            sid_derivation(&two, spelling, HashSite::Native),
            format!("pg_xxhash64(CONCAT(CONCAT({}, '|'), {}))", part("a"), part("b"))
        );
        let three = ["a".to_owned(), "b".to_owned(), "c".to_owned()];
        assert_eq!(
            sid_derivation(&three, spelling, HashSite::Native),
            format!(
                "pg_xxhash64(CONCAT(CONCAT(CONCAT(CONCAT({}, '|'), {}), '|'), {}))",
                part("a"),
                part("b"),
                part("c")
            )
        );
    }

    /// **A one-part key is not joined at all**, on this form as on the other
    /// two: there is nothing to join, and a one-argument `CONCAT` is a call an
    /// engine with a two-argument one does not have either.
    #[test]
    fn a_binary_concat_leaves_a_one_part_key_unjoined() {
        let binary = SidSpelling { concat: Concat::FunctionBinary("CONCAT"), ..HELPER_ROUTED };
        let one = ["a".to_owned()];
        assert_eq!(
            sid_derivation(&one, binary, HashSite::Native),
            sid_derivation(&one, HELPER_ROUTED, HashSite::Native)
        );
        assert!(!sid_derivation(&one, binary, HashSite::Native).contains("CONCAT"));
    }

    /// An empty key renders the bare call — the same degenerate spelling the
    /// variadic form renders, so the two forms stay comparable where there is
    /// nothing to join.
    #[test]
    fn a_binary_concat_renders_the_bare_call_for_an_empty_key() {
        let binary = SidSpelling { concat: Concat::FunctionBinary("CONCAT"), ..HELPER_ROUTED };
        assert_eq!(
            sid_derivation(&[], binary, HashSite::Native),
            sid_derivation(&[], HELPER_ROUTED, HashSite::Native)
        );
        assert_eq!(sid_derivation(&[], binary, HashSite::Native), "pg_xxhash64(CONCAT())");
    }

    /// **The three join forms are three spellings of one string.** Read the
    /// items out of each rendering and they are the same items in the same
    /// order — which is why a dialect may answer whichever one its engine
    /// parses without moving a key.
    #[test]
    fn the_three_join_forms_concatenate_the_same_items_in_the_same_order() {
        let parts = ["a".to_owned(), "b".to_owned(), "c".to_owned()];
        let items = |spelling| {
            let rendered = sid_derivation(&parts, spelling, HashSite::Native);
            let mut found = Vec::new();
            for arg in ["a", "b", "c"] {
                let normalized = normalize(spelling, arg);
                let at = rendered.find(&normalized).expect("every part is joined");
                found.push((at, arg));
            }
            found.sort_unstable();
            (
                found.into_iter().map(|(_, arg)| arg).collect::<Vec<_>>(),
                rendered.matches("'|'").count(),
            )
        };
        let variadic = items(HELPER_ROUTED);
        let binary =
            items(SidSpelling { concat: Concat::FunctionBinary("CONCAT"), ..HELPER_ROUTED });
        let operator = items(SidSpelling { concat: Concat::Plus, ..HELPER_ROUTED });
        assert_eq!(variadic, (vec!["a", "b", "c"], 2));
        assert_eq!(binary, variadic);
        assert_eq!(operator, variadic);
    }

    /// The sentinel is two above the smallest 64-bit integer, and it is spelled
    /// once.
    #[test]
    fn the_sentinel_is_two_above_the_smallest_integer() {
        assert_eq!(NULL_SID, i64::MIN + 2);
    }
}