Skip to main content

razeri_plugin_contract/
renderer.rs

1// Copyright (C) 2026 Gabriel Shea
2// Licensed under either of the Apache License, Version 2.0 or the MIT
3// license, at your option.
4
5//! Layered rendering traits for SQL generation.
6//!
7//! The hierarchy is designed so that implementing [`SqlRenderer`] (4 methods)
8//! gives you ANSI-standard defaults for all higher-level traits.
9
10use crate::DataType;
11use crate::bulk_load_form::BulkLoadForm;
12use crate::constraint_enforcement::ConstraintEnforcement;
13use crate::date_format::DateFormat;
14use crate::evolution::{ColumnEvolution, Written, WrittenObject};
15use crate::flatten::{Flatten, FlattenRequest};
16use crate::large_object::{HeldAs, TextLengthUnit};
17use crate::number_format::NumberFormat;
18use crate::procedure_log_level::ProcedureLogLevel;
19use crate::routine_form::{ProcedureForm, ScalarFunctionForm, TableFunctionForm};
20use crate::routine_spec::RoutineSpec;
21use crate::sid_spelling::{SidHelperBody, SidSpelling, UNSPELLED_SID};
22
23/// **Where the aggregated expression goes** in a form returned by
24/// [`SqlRenderer::median_aggregate_form`].
25///
26/// The form is a template rather than finished SQL so that *which function*
27/// and *how a column is written* stay in different hands — the dialect answers
28/// the first, the query renderer the second. A form must carry this token
29/// exactly once.
30pub const AGGREGATE_ARGUMENT: &str = "{argument}";
31
32/// **What kind of value an aggregate would have to collapse** — the axis
33/// [`SqlRenderer::render_single_row_aggregate`] varies on, held to three arms
34/// because three is what the measurements distinguish.
35///
36/// Not the full type lattice: `MAX` behaves identically across every numeric,
37/// textual and temporal type on every platform razeri reaches, and splitting
38/// them would invite per-type arms nothing has measured.
39#[derive(Debug, Clone, Copy, PartialEq, Eq)]
40pub enum AggregableKind {
41    /// Numeric, textual and temporal — every platform orders these and every
42    /// platform has `MAX` for them.
43    Orderable,
44    /// A truth value. `PostgreSQL` and SQL Server have no `MAX` for it and each
45    /// spells the collapse differently.
46    Boolean,
47    /// `uuid`, JSON, binary, interval, array, map — the families with no
48    /// ordering to borrow. Availability is per platform and is measured, never
49    /// assumed.
50    Opaque,
51}
52
53/// **A clock bound once for a whole load script** — what
54/// [`SqlRenderer::render_inline_clock_binding`] answers on an engine whose
55/// loads ship as flat statement lists rather than procedure bodies.
56///
57/// The `statements` run first and capture the engine's current moment under
58/// one name; `reference` is the expression every later statement of the same
59/// run reads it back by. One binding, many readers, is what makes a version's
60/// close and its successor's open the same instant by construction.
61#[derive(Debug, Clone, PartialEq, Eq)]
62pub struct InlineClockBinding {
63    /// The statement(s) that bind the moment, in run order, each terminated.
64    pub statements: Vec<String>,
65    /// The expression that reads the bound moment back.
66    pub reference: String,
67}
68
69/// **One delimited source file, in every spelling an engine's file relation may
70/// ask for it in** — what
71/// [`SqlRenderer::delimited_file_relation_for`] is handed.
72///
73/// One struct rather than four arguments, because a slot that grows an argument
74/// is a slot every dialect has to be recompiled for; one that grows a field is
75/// not. Each is composed once, by the caller that knows the answer, so no
76/// dialect has to take a path apart to find a file name in it.
77#[derive(Debug, Clone, Copy)]
78pub struct DelimitedFile<'a> {
79    /// The whole path to the file, already a single-quoted SQL string literal
80    /// and already escaped. Put it where the relation takes a path, and do not
81    /// quote it again.
82    pub path_literal: &'a str,
83    /// The directory the file sits in, already a single-quoted SQL string
84    /// literal — for an engine that resolves a file name against a directory
85    /// rather than opening a path.
86    pub directory_literal: &'a str,
87    /// The file's own name inside that directory, bare: no quotes and no path
88    /// in front of it.
89    pub name: &'a str,
90    /// **The file's columns, in the order the file writes them**, spelled
91    /// exactly as its header row spells them and with no quoting applied.
92    ///
93    /// Empty where the caller measured no header — a bundle assembled by hand.
94    /// An engine that cannot read a file without its columns declared answers
95    /// `None` to an empty list rather than guessing at one.
96    pub columns: &'a [String],
97    /// **How the file's lines end**, as Razeri measured it when it read the
98    /// file: [`LineEnding::CrLf`] for a carriage return and a newline,
99    /// [`LineEnding::Lf`] for a newline alone.
100    ///
101    /// Put it wherever the engine's read names a record delimiter. An engine
102    /// told `\n` for a file whose lines end `\r\n` either keeps the carriage
103    /// return on the last field, so a name is stored one character too long
104    /// and a number cannot be read, or, where the last field is quoted, reads
105    /// past the closing quote into the next line. An engine whose read finds
106    /// the ending itself can leave this alone, and should say so where it
107    /// states its read.
108    pub line_ending: LineEnding,
109}
110
111/// **One setting a statement was written with, for the engine it names.**
112///
113/// Razeri's statement builders carry settings written for one engine — a
114/// view's `security_barrier` for `PostgreSQL`, a table's data compression for
115/// SQL Server. They reach every dialect tagged with the engine they were
116/// written for, as that engine's dialect names itself
117/// ([`SqlRenderer::dialect_name`]), and a dialect spells the ones written for
118/// it and passes over the rest. Nothing in Razeri reads an engine's name to
119/// decide which settings apply.
120#[derive(Debug, Clone, Copy, PartialEq, Eq)]
121#[non_exhaustive]
122pub struct EngineSetting<'a> {
123    /// The engine the setting was written for, as its dialect names itself.
124    pub engine: &'a str,
125    /// The setting's name, in the engine's own words.
126    pub name: &'a str,
127    /// Its value, as the engine writes it.
128    pub value: &'a str,
129}
130
131impl<'a> EngineSetting<'a> {
132    /// One setting.
133    #[must_use]
134    pub const fn new(engine: &'a str, name: &'a str, value: &'a str) -> Self {
135        Self { engine, name, value }
136    }
137}
138
139/// **How the lines of a delimited file end.**
140///
141/// Measured, never assumed: [`LineEnding::of`] reads it off the file's own
142/// bytes, and a file with no line break at all reads as [`LineEnding::Lf`],
143/// which is what every engine reads by default.
144#[derive(Debug, Clone, Copy, Default, PartialEq, Eq, Hash)]
145pub enum LineEnding {
146    /// A newline alone, `\n`: every system but Windows.
147    #[default]
148    Lf,
149    /// A carriage return and then a newline, `\r\n`: a file written on
150    /// Windows.
151    CrLf,
152}
153
154impl LineEnding {
155    /// **How the lines of this text end**, decided by its first line break.
156    ///
157    /// The first break ends the header row, so it is written the way the rest
158    /// of the file is. Text with no newline at all is [`LineEnding::Lf`].
159    #[must_use]
160    pub fn of(text: &[u8]) -> Self {
161        match text.iter().position(|&byte| byte == b'\n') {
162            Some(at) if at > 0 && text[at - 1] == b'\r' => Self::CrLf,
163            _ => Self::Lf,
164        }
165    }
166
167    /// The ending's characters, as they stand in the file.
168    #[must_use]
169    pub const fn as_str(self) -> &'static str {
170        match self {
171            Self::Lf => "\n",
172            Self::CrLf => "\r\n",
173        }
174    }
175
176    /// The ending spelled with backslashes, `\n` or `\r\n`: the two or four
177    /// characters an engine that reads backslash escapes in a string literal
178    /// takes for it. Put it inside the quotes of a record delimiter, on an
179    /// engine that reads them that way.
180    #[must_use]
181    pub const fn escaped(self) -> &'static str {
182        match self {
183            Self::Lf => "\\n",
184            Self::CrLf => "\\r\\n",
185        }
186    }
187}
188
189/// **What a table's `CREATE TABLE` is about to say** — what
190/// [`SqlRenderer::render_table_suffix`] is handed to decide the clause after
191/// the column list.
192///
193/// One struct rather than several arguments, so the slot can learn more about a
194/// table without changing its signature. Razeri builds it; a dialect reads it.
195/// A dialect's own tests build one with [`TableSuffixRequest::new`] and the
196/// `with_` methods, which is also why it cannot be written out field by field
197/// outside this crate.
198#[derive(Debug, Clone, Copy, PartialEq, Eq)]
199#[non_exhaustive]
200pub struct TableSuffixRequest<'a> {
201    /// Whether the table is dropped at the end of its session, its transaction
202    /// or its batch. A transient table that outlives its session is not
203    /// temporary.
204    pub temporary: bool,
205    /// The table's primary-key columns in key order, unquoted. Empty on a table
206    /// with no primary key.
207    pub primary_key: &'a [String],
208    /// The columns the design's storage plan asked this table be clustered
209    /// by, unquoted, and only those this table has. Empty on most tables.
210    pub clustering_columns: &'a [String],
211    /// The column whose greatest value survives when rows share a key, on the
212    /// one table Razeri versions that way: the target of an engine whose
213    /// [`MergeShape`](crate::MergeShape) is
214    /// [`InsertDedup`](crate::InsertDedup). `None` on every other table.
215    pub version_column: Option<&'a str>,
216}
217
218impl<'a> TableSuffixRequest<'a> {
219    /// A persistent table with no primary key, no clustering columns and no
220    /// version column.
221    #[must_use]
222    pub const fn new() -> Self {
223        Self { temporary: false, primary_key: &[], clustering_columns: &[], version_column: None }
224    }
225
226    /// The same table, temporary or not.
227    #[must_use]
228    pub const fn with_temporary(mut self, temporary: bool) -> Self {
229        self.temporary = temporary;
230        self
231    }
232
233    /// The same table, with these primary-key columns.
234    #[must_use]
235    pub const fn with_primary_key(mut self, columns: &'a [String]) -> Self {
236        self.primary_key = columns;
237        self
238    }
239
240    /// The same table, with these clustering columns.
241    #[must_use]
242    pub const fn with_clustering_columns(mut self, columns: &'a [String]) -> Self {
243        self.clustering_columns = columns;
244        self
245    }
246
247    /// The same table, with this version column.
248    #[must_use]
249    pub const fn with_version_column(mut self, column: Option<&'a str>) -> Self {
250        self.version_column = column;
251        self
252    }
253}
254
255impl Default for TableSuffixRequest<'_> {
256    fn default() -> Self {
257        Self::new()
258    }
259}
260
261/// Level 1 — Core SQL rendering. REQUIRED: every dialect must implement these 4 methods.
262///
263/// These methods define the absolute minimum surface for rendering SQL:
264/// data types, identifier quoting, qualified names, and timestamps.
265pub trait SqlRenderer: Send + Sync {
266    /// Render a [`DataType`] as platform-specific SQL.
267    fn render_data_type(&self, dt: &DataType) -> String;
268
269    /// **The type text for a routine's PARAMETER**, which is not always the text
270    /// a column carries.
271    ///
272    /// Most engines spell a type one way wherever it appears, and for those this
273    /// hook is [`Self::render_data_type`] under another name — which is the
274    /// default, so a dialect with nothing to say says nothing.
275    ///
276    /// **Oracle is the reason it exists.** A column there takes
277    /// `NUMBER(19, 0)` and `VARCHAR2(100)`; the same types in a parameter list
278    /// are a compile error (`PLS-00103`, reported at the opening parenthesis),
279    /// and the bare `NUMBER` and `VARCHAR2` are what a parameter clause takes.
280    /// The length and the precision belong on the local declaration instead,
281    /// which keeps them. A dialect there answers:
282    ///
283    /// ```rust,ignore
284    /// fn render_parameter_data_type(&self, dt: &DataType) -> String {
285    ///     let column = self.render_data_type(dt);
286    ///     match column.find('(') {
287    ///         Some(at) => column[..at].trim_end().to_owned(),
288    ///         None => column,
289    ///     }
290    /// }
291    /// ```
292    ///
293    /// It is what fills
294    /// [`RoutineParameter::data_type`](crate::routine_spec::RoutineParameter::data_type),
295    /// so a dialect that answers it does not also post-process the string
296    /// Razeri handed it.
297    ///
298    /// Parameters:
299    /// - `dt`: the type the design declared.
300    fn render_parameter_data_type(&self, dt: &DataType) -> String {
301        self.render_data_type(dt)
302    }
303
304    /// **The type text for a column that ADMITS NULL** — the whole type, not a
305    /// modifier appended to one.
306    ///
307    /// Most engines spell nullability by SILENCE: a column declared without
308    /// `NOT NULL` admits one, and its type text is the same text a required
309    /// column carries. That is the default here, and for those engines this
310    /// hook is [`SqlRenderer::render_data_type`] under another name.
311    ///
312    /// **`ClickHouse` is the exception, and it is the reason this hook exists.**
313    /// There a bare type is NOT NULL — nullability is a property OF the type,
314    /// spelled `Nullable(T)` — so a renderer that expresses *nullable* by
315    /// omitting a keyword expresses nothing at all, and every `nullable:` a
316    /// design declares is discarded on that engine.
317    ///
318    /// # What a discarded `nullable:` costs, measured rather than assumed
319    ///
320    /// Not a loud refusal. `insert_null_as_default` **defaults to `1`** on
321    /// `ClickHouse` (24.10.2, server default, `changed = 0`), so
322    /// `INSERT … SELECT NULL` into a bare column does not raise — it writes the
323    /// TYPE'S DEFAULT, silently: `''` for `String`, `0` for `Int64`,
324    /// `1900-01-01` for `Date32` (which is razeri's own unknown-member date, so
325    /// an unknown attribute comes back indistinguishable from a deliberately
326    /// seeded one). Only with `insert_null_as_default = 0` does the engine
327    /// answer `Code: 70 … Cannot convert NULL to a non-nullable type`. **A
328    /// design's own data would be fabricated on that engine and honest on
329    /// every other**, and the DDL is where that is prevented.
330    ///
331    /// # A dialect overriding this owes a measurement
332    ///
333    /// Not every type can be wrapped. `ClickHouse` refuses
334    /// `Nullable(Array(…))`, `Nullable(Map(…))` and `Nullable(JSON)` outright,
335    /// so its override declines those and says so in its own rustdoc. A
336    /// wrapper that cannot be applied must be DECLINED by name, never applied
337    /// and hoped for: an unwrappable type rendered `Nullable(…)` turns a silent
338    /// wrong value into a `CREATE TABLE` that does not run.
339    fn render_nullable_data_type(&self, dt: &DataType) -> String {
340        self.render_data_type(dt)
341    }
342
343    /// Quote an identifier using platform conventions.
344    ///
345    /// `PostgreSQL`: `"name"`, `MySQL`: `` `name` ``, SQL Server: `[name]`
346    fn quote_identifier(&self, name: &str) -> String;
347
348    /// Render a fully qualified name (optional schema + name).
349    fn qualify_name(&self, schema: Option<&str>, name: &str) -> String;
350
351    /// Whether this dialect uses schemas in qualified names.
352    ///
353    /// `ClickHouse` uses only `database.object` (no schema level).
354    /// Default: `true` (most platforms use `catalog.schema.object`).
355    fn uses_schema_in_qualified_names(&self) -> bool {
356        true
357    }
358
359    /// **The name an object takes on an engine with no schemas**, where this
360    /// dialect keeps a design's layers apart by folding the schema into it —
361    /// `sales_raw` and `customers` become `sales_raw_customers` — or `None`,
362    /// the default, where the schema is dropped.
363    ///
364    /// It is asked only of a dialect whose
365    /// [`DialectCapabilities::supports_schemas`](crate::DialectCapabilities::supports_schemas)
366    /// is `false`. With `None`, every object of every layer lives under its
367    /// own bare name, so a design whose Raw and Core layers both hold a
368    /// `customers` is refused: each load would write over the other's rows.
369    /// Answer with a fold and Razeri keeps each object's schema and writes
370    /// what this returns wherever it names the object — its `CREATE`, every
371    /// statement that reads or writes it, every reference to it — so the same
372    /// design renders, and two objects are refused only where their folded
373    /// names are the same.
374    ///
375    /// Return the name unquoted; Razeri quotes it with
376    /// [`Self::quote_identifier`]. [`Self::qualify_name`] given a schema has
377    /// to answer the same object, quoted, because some statements are named
378    /// through it; the conformance report checks that the two agree.
379    fn fold_schema_into_name(&self, schema: &str, name: &str) -> Option<String> {
380        let _ = (schema, name);
381        None
382    }
383
384    /// Whether this dialect is the "agnostic" pseudo-dialect.
385    ///
386    /// Agnostic rendering produces unquoted, platform-neutral DDL.
387    /// Real dialect implementations should not override this.
388    fn is_agnostic_dialect(&self) -> bool {
389        false
390    }
391
392    /// Whether this dialect supports `SECURE VIEW` (Snowflake).
393    fn supports_secure_view(&self) -> bool {
394        false
395    }
396
397    /// Whether this dialect supports `GLOBAL TEMPORARY VIEW` (Databricks).
398    fn supports_global_temporary_view(&self) -> bool {
399        false
400    }
401
402    /// Render the platform's `CURRENT_TIMESTAMP` expression.
403    fn render_current_timestamp(&self) -> String;
404
405    /// **The schemas a catalog scan passes over** — the engine's own
406    /// bookkeeping, never a warehouse's contents.
407    ///
408    /// Read by [`Self::render_catalog_columns_query`] and
409    /// [`Self::render_catalog_object_times_query`] when no single schema was
410    /// named, so a scan of a whole warehouse sees the tables somebody created
411    /// and not the catalog describing them. The default names the one schema
412    /// the SQL standard puts the catalog in; an engine that keeps more of its
413    /// own next to it adds them.
414    fn catalog_system_schemas(&self) -> &'static [&'static str] {
415        &["information_schema"]
416    }
417
418    /// **The query that lists what the warehouse holds** — one row per column,
419    /// as `(schema, object, column)`.
420    ///
421    /// Razeri asks it to answer a question it cannot answer from a design: how
422    /// much of this warehouse razeri itself rendered. Columns rather than
423    /// object names alone, because the answer is read off both — a name carries
424    /// the tier razeri mints (`…_staging`, `…_current`), and a column carries
425    /// the bookkeeping only razeri writes (`…_sid`, `_loaded_at`). A scan that
426    /// saw names alone would either miss an entity whose target table is a
427    /// plain noun or count a table razeri never touched.
428    ///
429    /// The three columns are positional and in that order: every caller reads
430    /// row `0`, `1`, `2` and nothing reads a column by name, so an engine whose
431    /// catalog spells the headings differently needs no special case.
432    ///
433    /// `None` means **this engine states no catalog razeri can scan**, which is
434    /// an answer rather than an omission: the caller says so by name instead of
435    /// reporting an empty warehouse.
436    ///
437    /// Parameters:
438    /// - `schema`: the one schema to look in. `None` means the whole warehouse,
439    ///   minus [`Self::catalog_system_schemas`].
440    fn render_catalog_columns_query(&self, schema: Option<&str>) -> Option<String> {
441        Some(format!(
442            "SELECT table_schema, table_name, column_name\nFROM \
443             information_schema.columns\n{}\nORDER BY table_schema, table_name, column_name",
444            self.catalog_scope_predicate("table_schema", schema)
445        ))
446    }
447
448    /// **The query that says when each object was created**, as
449    /// `(schema, object, created_at)` — where the engine records it at all.
450    ///
451    /// A second, independent reading of the same question
452    /// [`Self::render_catalog_columns_query`] opens: the earliest moment razeri
453    /// can prove it was here. It is a `None` on most engines, and deliberately
454    /// so — `PostgreSQL` and `DuckDB` keep no creation time for a relation, and
455    /// inventing one would be worse than having none.
456    ///
457    /// Parameters:
458    /// - `schema`: the one schema to look in, or `None` for the whole
459    ///   warehouse.
460    fn render_catalog_object_times_query(&self, schema: Option<&str>) -> Option<String> {
461        let _ = schema;
462        None
463    }
464
465    /// The `WHERE` clause both catalog queries share: one named schema, or
466    /// every schema that is not the engine's own.
467    ///
468    /// Parameters:
469    /// - `column`: the catalog's own name for the schema column.
470    /// - `schema`: the one schema to look in, or `None` for the whole
471    ///   warehouse.
472    fn catalog_scope_predicate(&self, column: &str, schema: Option<&str>) -> String {
473        if let Some(one) = schema {
474            return format!("WHERE {column} = '{}'", one.replace('\'', "''"));
475        }
476        let excluded: Vec<String> = self
477            .catalog_system_schemas()
478            .iter()
479            .map(|name| format!("'{}'", name.replace('\'', "''")))
480            .collect();
481        format!("WHERE {column} NOT IN ({})", excluded.join(", "))
482    }
483
484    /// Return a stable string identifier for this dialect.
485    ///
486    /// A human-readable label in diagnostics and error messages, for **every
487    /// dialect** registered in the `PlatformRegistry` (e.g. `"postgresql"`,
488    /// `"sqlite"`, `"cockroachdb"`). For the engines Razeri ships, two
489    /// renderings are chosen by this name rather than asked of a trait method —
490    /// a bulk load's statement and a query's own syntax. Any other name gets
491    /// the generic form: the bulk load in ANSI, the query spelled through your
492    /// renderer. Keep it stable once a dialect is published.
493    ///
494    /// Convention: lowercase, hyphen-separated (e.g. `"postgresql"`,
495    /// `"sql-server"`, `"snowflake"`, `"sqlite"`).
496    ///
497    /// The default returns `"ansi"`.
498    fn dialect_name(&self) -> &'static str {
499        "ansi"
500    }
501
502    /// **How this engine spells a surrogate key.**
503    ///
504    /// Razeri derives every key one way — cast each part to text, escape and
505    /// join the parts of a composite, hash the result, guard NULLs with a
506    /// sentinel — and composes that derivation in exactly one place
507    /// ([`crate::sid`]). What a dialect decides is the *vocabulary*: how this
508    /// engine writes a replacement and a Unicode literal, how it joins
509    /// strings, how it casts a value to text, and how it spells a 64-bit hash.
510    ///
511    /// The default is [`UNSPELLED_SID`] — ANSI's vocabulary under a name that
512    /// says it is a guess. **A dialect that leaves it there is telling Razeri it
513    /// has not decided**, and the emitted SQL will call an `xxhash64` its engine
514    /// very likely does not have. Override it.
515    ///
516    /// If your engine has no portable 64-bit hash, say so rather than reaching
517    /// for the nearest-looking function: a hash that is deterministic within one
518    /// warehouse and equal to nothing outside it is the lock-in a portable key
519    /// exists to end. Name an artifact your dialect's prerequisites install, or
520    /// refuse the key and orient the reader.
521    fn sid_spelling(&self) -> SidSpelling {
522        UNSPELLED_SID
523    }
524
525    /// **The body of the surrogate-key helper, where SQL cannot spell it.**
526    ///
527    /// Razeri emits one function per warehouse — `generate_sid` — and normally
528    /// builds its body itself, from the vocabulary
529    /// [`sid_spelling`](Self::sid_spelling) states. The default here is `None`,
530    /// which says exactly that: *compose the derivation in my own SQL*.
531    ///
532    /// Answer with [`SidHelperBody`] only when there is no SQL that could hold
533    /// the derivation — the engine has no 64-bit hash under any name, so the
534    /// vocabulary has nothing to spell the hash with and the definition has to
535    /// be written in another language. `BigQuery` is the case Razeri ships:
536    /// `GoogleSQL` has no XXH64, and the function that looks like the answer
537    /// (`FARM_FINGERPRINT`) is a different number, so the body is JavaScript.
538    ///
539    /// **Do not use this to change the key.** The body computes the same number
540    /// every other engine computes — XXH64, seed 0, over the UTF-8 bytes of the
541    /// one argument, as given — and [`SidHelperBody`] says what it owes
542    /// in full. Reaching for a native hash that is merely deterministic keys a
543    /// warehouse to numbers nothing outside it can reproduce.
544    ///
545    /// A dialect that answers here should also state a
546    /// [`SidSpelling::hash`](crate::SidSpelling::hash) that no engine has, so
547    /// that any path which fell back to composing SQL fails loudly at deploy
548    /// time rather than hashing nothing and handing back the value.
549    fn sid_helper_body(&self) -> Option<SidHelperBody> {
550        None
551    }
552
553    /// **How this engine reads a delimited text file as a relation**, or `None`
554    /// where it cannot.
555    ///
556    /// A design can bind an entity to a source file, and Razeri then generates
557    /// the staging fill: an `INSERT … SELECT … FROM <this>`. That only works on
558    /// an engine that can open a file from inside a query. `DuckDB` can
559    /// (`read_csv_auto`); most cannot — `PostgreSQL` wants `COPY`, Snowflake a
560    /// stage, `SQLite` has nothing at all. Each of those is a bulk-load design
561    /// decision rather than a rendering detail, so the default is `None` and
562    /// the generated bundle says which platform declined and why instead of
563    /// emitting SQL the engine would refuse.
564    ///
565    /// `path_literal` arrives as a finished single-quoted SQL string literal,
566    /// already escaped — put it where the relation takes a path and do not
567    /// quote it again.
568    ///
569    /// **What the returned relation owes**, because the fill is written against
570    /// it and nothing checks these at render time:
571    ///
572    /// - the file's first row is its header, and the column names come from it,
573    ///   spelled exactly as the file spells them;
574    /// - every column reads as **text**, unparsed. The fill casts nothing: a
575    ///   date's layout and a currency's prefix are the source's business, and a
576    ///   reader that guessed a type would fail the load on its first row;
577    /// - it is a relation usable in a `FROM` clause, not a statement.
578    ///
579    /// The path is embedded in the generated file as it was spelled when the
580    /// bundle was built, so it must be one the engine resolves at run time.
581    fn delimited_file_relation(&self, path_literal: &str) -> Option<String> {
582        let _ = path_literal;
583        None
584    }
585
586    /// **How this engine reads a delimited text file as a relation, told
587    /// everything Razeri knows about the file** — or `None` where it cannot.
588    ///
589    /// The richer spelling of
590    /// [`delimited_file_relation`](Self::delimited_file_relation), and the one
591    /// the staging fill asks first. The default hands the path straight to that
592    /// method and ignores the rest, so a dialect that has stated one has stated
593    /// this too and its renders are byte for byte what they were.
594    ///
595    /// Override it where the path alone is not enough. Two engines' file reads
596    /// need more and cannot be given it any other way:
597    ///
598    /// - **the file's columns declared in the statement.** Oracle's external
599    ///   table answers `ORA-00906: missing left parenthesis` without them, and
600    ///   has no form that takes its column names from a header row;
601    /// - **a directory rather than a path.** The same engine answers
602    ///   `KUP-04125: no directory object specified for location 1 in LOCATION
603    ///   clause` to an absolute path, and resolves a file name against a named
604    ///   directory object instead — which
605    ///   [`delimited_file_prerequisites`](Self::delimited_file_prerequisites)
606    ///   is where such an object is made.
607    ///
608    /// Everything the returned relation owes, it owes for the reasons
609    /// [`delimited_file_relation`](Self::delimited_file_relation) states:
610    /// header from the first row, every column as text and unparsed, usable in
611    /// a `FROM` clause. **Declaring the columns does not license declaring
612    /// their types**: they are the source's text, and a reader that guessed
613    /// would fail the load on its first row.
614    fn delimited_file_relation_for(&self, file: &DelimitedFile<'_>) -> Option<String> {
615        self.delimited_file_relation(file.path_literal)
616    }
617
618    /// **Statements this engine needs run once before any of its file relations
619    /// resolve**, in order — and none by default.
620    ///
621    /// An engine that opens a path needs nothing here. An engine that opens a
622    /// file only through an object somebody made — Oracle's `DIRECTORY` — needs
623    /// that object made first, and the generated staging file is where it
624    /// belongs: it is the one file that knows which directory the sources were
625    /// read from.
626    ///
627    /// Each string is one statement without its terminator. Every one must be
628    /// safe to run again, because the file it lands in runs on every deploy.
629    ///
630    /// `directory_literal` arrives as a finished single-quoted SQL string
631    /// literal, already escaped.
632    ///
633    /// **Say what the statement costs.** An object like this is often
634    /// privileged, and a statement an operator cannot run is better read than
635    /// guessed at — put the privilege in a comment inside the statement, where
636    /// it travels with it.
637    fn delimited_file_prerequisites(&self, directory_literal: &str) -> Vec<String> {
638        let _ = directory_literal;
639        Vec::new()
640    }
641
642    /// **Statements this engine needs run before it can read THIS file**, in
643    /// order — and none by default.
644    ///
645    /// The per-file companion of
646    /// [`delimited_file_prerequisites`](Self::delimited_file_prerequisites),
647    /// and the slot an engine with no file relation at all reads a file
648    /// through. Where that method makes what every read shares — a directory
649    /// object, a permitted path — this one makes what belongs to one file, and
650    /// the staging fill emits it directly in front of the fill that reads it.
651    ///
652    /// Two kinds of engine answer it.
653    ///
654    /// - One whose file read is a **statement rather than a relation**. SAP
655    ///   HANA's is `IMPORT FROM CSV FILE`, which writes into a table that
656    ///   already exists; there is nothing to put in a `FROM` clause. Such a
657    ///   dialect lands the file's rows in a table here and answers that table
658    ///   from
659    ///   [`delimited_file_relation_for`](Self::delimited_file_relation_for).
660    /// - One whose relation needs a per-file object made first — a format, an
661    ///   external stage, a descriptor.
662    ///
663    /// Each string is one statement without its terminator. **Every one must be
664    /// safe to run again**, because the file it lands in runs on every deploy,
665    /// and safe to run beside the others: a second deploy stages what the
666    /// source holds now rather than adding a second copy of it.
667    ///
668    /// **Say what the statement costs.** A read like this is often privileged,
669    /// and a statement an operator cannot run is better read than guessed at —
670    /// put the privilege in a comment inside the statement, where it travels
671    /// with it.
672    fn delimited_file_statements_for(&self, file: &DelimitedFile<'_>) -> Vec<String> {
673        let _ = file;
674        Vec::new()
675    }
676
677    /// **The one-row table a query reads when it has nothing else to read**,
678    /// or `None` on an engine that takes a `SELECT` with no `FROM` at all.
679    ///
680    /// Some of what Razeri measures is a value rather than a row of a table —
681    /// the grand total a reconciliation compares against its sources is a sum
682    /// of counts, and there is nothing to select it from. Most engines take
683    /// `SELECT <value>` as it stands. SAP HANA refuses it at the next word and
684    /// wants `FROM DUMMY`, the one-row table it keeps for exactly this; Oracle
685    /// kept `DUAL` for the same reason until it began accepting the bare form.
686    ///
687    /// Answer the table's name, as a query would write it after `FROM`. Every
688    /// engine that answers `None` renders exactly what it rendered before.
689    fn render_one_row_source(&self) -> Option<String> {
690        None
691    }
692
693    /// **How this engine reads a delimited text file whose bytes are not
694    /// UTF-8**, or `None` where it states no way to.
695    ///
696    /// UTF-8 is the canonical form and every engine reads it, so
697    /// [`delimited_file_relation`](Self::delimited_file_relation) answers for
698    /// it and this is asked only about the other days. A design can name the
699    /// encoding to fall back to when a file's bytes are not UTF-8, and razeri
700    /// resolves that name once, to the one name the encoding standard files it
701    /// under — `windows-1252`, `shift_jis`, `koi8-r`. That name arrives here,
702    /// and an engine that has its own word for the same encoding translates it
703    /// the way an engine translates every other name razeri hands it.
704    ///
705    /// Everything the returned relation owes, it owes for the same reasons
706    /// [`delimited_file_relation`](Self::delimited_file_relation) says: header
707    /// from the first row, every column as text, usable in a `FROM` clause.
708    ///
709    /// **`None` is a complete answer, and the common one.** An engine that
710    /// reads no file at all says nothing here; so does an engine that reads
711    /// files but has no word for this particular encoding. Razeri then declines
712    /// that source by name and says what to do instead — convert the file to
713    /// UTF-8, or write the load by hand — rather than pointing the engine at
714    /// bytes it will read as some other language.
715    ///
716    /// **Answer only for encodings you have run.** An engine that accepts the
717    /// name and reads the bytes as something else is worse than one that
718    /// refuses the name: the first is a warehouse of plausible nonsense and the
719    /// second is a sentence on a terminal.
720    ///
721    /// `path_literal` arrives as a finished single-quoted SQL string literal,
722    /// already escaped — put it where the relation takes a path and do not
723    /// quote it again. `encoding` does not: it is a bare name, and an engine
724    /// that puts it in a string literal writes the quotes itself.
725    fn delimited_file_relation_in_encoding(
726        &self,
727        path_literal: &str,
728        encoding: &str,
729    ) -> Option<String> {
730        let _ = (path_literal, encoding);
731        None
732    }
733
734    /// **The `CAST` target spelling for a [`DataType`] on this engine.**
735    ///
736    /// A column declaration and a `CAST` target are two grammars on most
737    /// engines, so this is a separate answer from
738    /// [`render_data_type`](Self::render_data_type) and not a synonym for it.
739    /// `MySQL` takes `SIGNED` where it declares `BIGINT`; T-SQL takes
740    /// `NVARCHAR(MAX)` where ANSI says `TEXT`.
741    ///
742    /// Where the two *are* the same on your engine — which is the common case
743    /// on engines with one type vocabulary — say so by delegating:
744    ///
745    /// ```rust,ignore
746    /// fn render_cast_type(&self, dt: &DataType) -> String {
747    ///     self.render_data_type(dt)
748    /// }
749    /// ```
750    ///
751    /// The default is ANSI's cast vocabulary. Razeri's generated load opens
752    /// with a CTE that casts every source column to its declared type, so a
753    /// wrong name here fails the load on its first statement.
754    fn render_cast_type(&self, dt: &DataType) -> String {
755        crate::agnostic::render_cast_type_ansi(dt)
756    }
757
758    /// **Which stored-procedure grammar this engine speaks.**
759    ///
760    /// Razeri composes the procedure — its parameters, its steps, its comment —
761    /// and spells it in the form this returns. The default is
762    /// [`ProcedureForm::Agnostic`], which parses on no engine: it is the honest
763    /// answer for a dialect that has not said, and it is what an unlisted
764    /// platform has always been handed.
765    ///
766    /// It lives on `SqlRenderer` rather than on
767    /// [`ProcedureRenderer`] because the composer
768    /// holds the renderer as a [`DdlRenderer`], and a decision reachable only
769    /// through a trait the caller does not hold is a decision the caller makes
770    /// instead.
771    fn procedure_form(&self) -> ProcedureForm {
772        ProcedureForm::Agnostic
773    }
774
775    /// **Which scalar-function grammar this engine speaks.**
776    ///
777    /// The default is [`ScalarFunctionForm::PostgreSql`] — what an unlisted
778    /// dialect has always been handed, and a dangerous inheritance on a near
779    /// neighbour: `LANGUAGE SQL IMMUTABLE PARALLEL SAFE` is a *"syntax error at
780    /// or near `PARALLEL`"* on an engine that descends from `PostgreSQL` and
781    /// reports itself as one.
782    fn scalar_function_form(&self) -> ScalarFunctionForm {
783        ScalarFunctionForm::PostgreSql
784    }
785
786    /// **Which of Razeri's bulk-load grammars this engine speaks**, or `None`
787    /// where it speaks none of them.
788    ///
789    /// The bulk-load builder writes its statement in the form named here. The
790    /// default is `None`, and the builder then writes Razeri's own annotated
791    /// form, which no engine runs: name a form only where your engine takes
792    /// that statement as written.
793    fn bulk_load_form(&self) -> Option<BulkLoadForm> {
794        None
795    }
796
797    /// **The clause a view's settings are written in**, between its name and
798    /// `AS` — `PostgreSQL`'s `WITH (security_barrier = true)` — or `None` where
799    /// none of `settings` is this engine's, which is the default.
800    ///
801    /// See [`EngineSetting`]: spell the settings written for your engine and
802    /// pass over the rest.
803    fn render_view_options(&self, settings: &[EngineSetting<'_>]) -> Option<String> {
804        let _ = settings;
805        None
806    }
807
808    /// **The words a join's settings are written in**, in front of its join
809    /// keyword — `ClickHouse`'s `GLOBAL ANY` — or `None` where none of
810    /// `settings` is this engine's, which is the default.
811    fn render_join_options(&self, settings: &[EngineSetting<'_>]) -> Option<String> {
812        let _ = settings;
813        None
814    }
815
816    /// **Which table-valued-function grammar this engine speaks, and — where it
817    /// has none — the words it refuses in.**
818    ///
819    /// The default is [`TableFunctionForm::PostgreSql`], what an unlisted
820    /// dialect has always been handed. If your engine has no routine a query
821    /// selects `FROM`, answer [`TableFunctionForm::Unsupported`] with your own
822    /// sentence: Razeri then declines to build the routine at all and prints
823    /// **your** words rather than another engine's. A refusal written once per
824    /// artifact kind tells every author about whichever engine happened to need
825    /// it first, and the near form an author will otherwise find on their own
826    /// is exactly what costs them a day.
827    fn table_function_form(&self) -> TableFunctionForm {
828        TableFunctionForm::PostgreSql
829    }
830
831    // ═══════════════════════════════════════════════════════════════
832    // Routines this dialect spells itself
833    // ═══════════════════════════════════════════════════════════════
834
835    /// **Spell a whole stored procedure in this engine's own grammar** — the
836    /// method [`ProcedureForm::Custom`] promises.
837    ///
838    /// Razeri composes the routine and hands it over as a [`RoutineSpec`]: the
839    /// name, the parameters with their types already in your spelling, the
840    /// declarations, the body's statements, the comment. What comes back is the
841    /// complete `CREATE`, punctuation and all, and Razeri emits it unchanged.
842    ///
843    /// The default is `None`, which means *"I did not write one"*. It is only
844    /// ever reached by a dialect that answered
845    /// [`ProcedureForm::Custom`] and then did not override this — the
846    /// conformance kit reports that as blocking, and the emitted SQL carries a
847    /// comment saying the same thing where the procedure would have been.
848    ///
849    /// It lives on `SqlRenderer` rather than on [`ProcedureRenderer`] for the
850    /// reason [`Self::procedure_form`] does: the composer holds the renderer as
851    /// a [`DdlRenderer`], and an answer reachable only through a trait the
852    /// caller does not hold is an answer the caller makes up instead.
853    ///
854    /// Parameters:
855    /// - `routine`: the procedure Razeri composed.
856    fn render_procedure(&self, routine: &RoutineSpec) -> Option<String> {
857        let _ = routine;
858        None
859    }
860
861    /// **Spell a whole scalar function in this engine's own grammar** — the
862    /// method [`ScalarFunctionForm::Custom`] promises.
863    ///
864    /// The same contract as [`Self::render_procedure`]: a composed
865    /// [`RoutineSpec`] in, the complete `CREATE` out, and `None` only where a
866    /// dialect claimed a custom grammar and did not write the method.
867    ///
868    /// Parameters:
869    /// - `routine`: the function Razeri composed.
870    fn render_scalar_function(&self, routine: &RoutineSpec) -> Option<String> {
871        let _ = routine;
872        None
873    }
874
875    /// **Spell a whole table-valued function in this engine's own grammar** —
876    /// the method [`TableFunctionForm::Custom`] promises.
877    ///
878    /// The same contract as [`Self::render_procedure`]: a composed
879    /// [`RoutineSpec`] in, the complete `CREATE` out, and `None` only where a
880    /// dialect claimed a custom grammar and did not write the method.
881    ///
882    /// Parameters:
883    /// - `routine`: the function Razeri composed.
884    fn render_table_function(&self, routine: &RoutineSpec) -> Option<String> {
885        let _ = routine;
886        None
887    }
888
889    // ═══════════════════════════════════════════════════════════════
890    // Expression rendering extensions
891    // ═══════════════════════════════════════════════════════════════
892
893    /// Platform's `CURRENT_DATE` expression.
894    ///
895    /// `SqlServer`: `CAST(GETDATE() AS DATE)`, `ClickHouse`: `today()`, others: `CURRENT_DATE`
896    fn render_current_date(&self) -> String {
897        "CURRENT_DATE".to_string()
898    }
899
900    /// Render a boolean literal.
901    ///
902    /// SqlServer/ClickHouse: `1`/`0`, others: `TRUE`/`FALSE`
903    fn render_boolean_literal(&self, value: bool) -> String {
904        if value { "TRUE".to_string() } else { "FALSE".to_string() }
905    }
906
907    /// **Collapse a subquery that selects AT MOST ONE ROW into an aggregate**,
908    /// for the engines that will not accept an unaggregated correlated scalar.
909    ///
910    /// Razeri's SCD-2 and SCD-1 re-opens assign `col = (SELECT … WHERE
911    /// {correlation})`. Databricks refuses that shape by name
912    /// (`MUST_AGGREGATE_CORRELATED_SCALAR_SUBQUERY`) and Snowflake stopped
913    /// unnesting it the day the guard beside it became uncorrelated, so the
914    /// emission wraps the projection in an aggregate. **Which** aggregate is a
915    /// dialect fact and **whether one exists at all** is a dialect × type fact,
916    /// which is why this hook takes the kind and may answer `None`:
917    ///
918    /// - `MAX` is the obvious choice and is wrong for a BOOLEAN on two of the
919    ///   platforms razeri reaches. Measured on the running
920    ///   containers: `PostgreSQL` and Citus raise
921    ///   *"function max(boolean) does not exist"*, SQL Server raises
922    ///   *"Msg 8117: Operand data type bit is invalid for max operator"*, while
923    ///   `MariaDB` (`MAX(TRUE)` → `1`) and `ClickHouse` (`max(true)` → `true`)
924    ///   answer it happily;
925    /// - `PostgreSQL` has no `max()` for `uuid`, `jsonb` or `bytea` either
926    ///   (measured, same run), and SQL Server has one for both
927    ///   `uniqueidentifier` and `varbinary` (measured).
928    ///
929    /// **`None` means *this dialect cannot aggregate this kind* and is not a
930    /// failure.** The caller then emits the bare correlated scalar — the
931    /// prior shape, measured green on the whole
932    /// `PostgreSQL` family — and says so on the output, because that shape is
933    /// the one Databricks refuses.
934    ///
935    /// **Correctness does not come from the aggregate's meaning.** The subquery
936    /// is under a guard that is false for the entire load if any member
937    /// disagrees with itself, so it selects one row; `MAX`, `BOOL_OR` and
938    /// `BOOLOR_AGG` of one value are all that value. The choice between them is
939    /// availability, not semantics.
940    fn render_single_row_aggregate(
941        &self,
942        expression: &str,
943        kind: AggregableKind,
944    ) -> Option<String> {
945        let _ = kind;
946        Some(format!("MAX({expression})"))
947    }
948
949    /// **Take any one value of `expression` from a group**, for the engines
950    /// that cannot group on the column itself.
951    ///
952    /// A load collapses staged rows that repeat. Where the engine will not
953    /// group on a nested column
954    /// ([`crate::DialectCapabilities::groups_nested_values`]) the collapse
955    /// groups on the remaining columns and takes one value of that one, which
956    /// is what this renders.
957    ///
958    /// `ANY_VALUE` is the spelling `BigQuery`, Snowflake, Redshift, Databricks
959    /// and `MySQL` all use, so it is the default; an engine that spells it
960    /// differently, or reaches it through `FIRST`, says so here.
961    ///
962    /// Unlike [`Self::render_single_row_aggregate`] this one cannot decline:
963    /// it is asked only where the dialect has already said it cannot group on
964    /// the column, so there is no second shape to fall back to.
965    ///
966    /// Parameters:
967    /// - `expression`: the column reference, already spelled by the renderer
968    ///   that lays out the query.
969    fn render_any_value(&self, expression: &str) -> String {
970        format!("ANY_VALUE({expression})")
971    }
972
973    /// **The AGGREGATE median this engine has for `expression`, or `None` when
974    /// it has none.**
975    ///
976    /// Aggregate is the load-bearing word, and it is why this cannot be
977    /// answered from a function-name table. Four spellings are in the wild:
978    ///
979    /// - a plain `median(x)` aggregate — `duckdb`, `clickhouse`, `snowflake`;
980    /// - the SQL ordered-set aggregate
981    ///   `PERCENTILE_CONT(0.5) WITHIN GROUP (ORDER BY x)` — `postgres` and its
982    ///   family (measured on a running instance: it groups);
983    /// - a two-argument `PERCENTILE(x, 0.5)` — `databricks`;
984    /// - and a WINDOW function wearing one of those names, which is **not**
985    ///   this. `mariadb` has `MEDIAN(x) OVER (…)` and `sqlserver` has
986    ///   `PERCENTILE_CONT(…) OVER (…)`, and both are a syntax error in a
987    ///   `GROUP BY` select list (measured: `MariaDB` raises `ERROR 1064` and
988    ///   SQL Server raises *"Msg 10753: The function `PERCENTILE_CONT` must
989    ///   have an OVER clause"*). A window function returns one value per ROW,
990    ///   not one per group, so it is a different answer and not a fallback.
991    ///
992    /// **The default declines**, so a dialect that has never been asked the
993    /// question is never assumed to have the answer, and a dialect added
994    /// tomorrow opts in deliberately with a form somebody measured. The caller
995    /// turns `None` into a build-time refusal naming the column and the target
996    /// — razeri does not emit SQL its target cannot run.
997    ///
998    /// # Why a TEMPLATE rather than finished SQL
999    ///
1000    /// The aggregated expression is a column reference, and how a column
1001    /// reference is spelled — quoted, bracketed, upper-cased — belongs to the
1002    /// renderer that lays out the query, not to this answer. Handing back a
1003    /// form with [`AGGREGATE_ARGUMENT`] where the expression goes keeps the
1004    /// two apart: this trait owns *which function*, the query renderer owns
1005    /// *how the column is written*, and neither has to reimplement the other.
1006    fn median_aggregate_form(&self) -> Option<&'static str> {
1007        None
1008    }
1009
1010    /// Render a date literal.
1011    ///
1012    /// `ClickHouse`: `toDate('2024-01-15')`, others: `DATE '2024-01-15'`
1013    fn render_date_literal(&self, date: &str) -> String {
1014        format!("DATE '{date}'")
1015    }
1016
1017    /// Render a timestamp literal.
1018    ///
1019    /// `ClickHouse`: `toDateTime('...')`, others: `TIMESTAMP '...'`
1020    fn render_timestamp_literal(&self, ts: &str) -> String {
1021        format!("TIMESTAMP '{ts}'")
1022    }
1023
1024    /// Render an interval literal.
1025    ///
1026    /// `SqlServer`: `'P1Y2M3D'`, `ClickHouse`: `INTERVAL 1 YEAR`, others: `INTERVAL '1 year'`
1027    fn render_interval_literal(&self, interval: &str) -> String {
1028        format!("INTERVAL '{interval}'")
1029    }
1030
1031    /// Render the string concatenation operator.
1032    ///
1033    /// `SqlServer`: `+`, others: `||`
1034    fn concat_operator(&self) -> &'static str {
1035        "||"
1036    }
1037
1038    /// Render a NaN literal for a floating-point type.
1039    ///
1040    /// `PostgreSQL`: `'NaN'::float`, others: `NULL`
1041    fn render_nan_literal(&self, _dt: &DataType) -> String {
1042        "NULL".to_string()
1043    }
1044
1045    /// Render an infinity literal for a floating-point type.
1046    ///
1047    /// `PostgreSQL`: `'Infinity'::float` / `'-Infinity'::float`, others: `NULL`
1048    fn render_infinity_literal(&self, _positive: bool, _dt: &DataType) -> String {
1049        "NULL".to_string()
1050    }
1051
1052    /// Render the NULL-coalescing function name.
1053    ///
1054    /// `SqlServer`: `ISNULL`, `ClickHouse`: `ifNull`, others: `COALESCE`
1055    fn ifnull_function_name(&self) -> &'static str {
1056        "COALESCE"
1057    }
1058
1059    /// Render an IIF expression (or CASE WHEN equivalent).
1060    ///
1061    /// `SqlServer`: `IIF(cond, then, else)`, others: `CASE WHEN cond THEN then ELSE else END`
1062    fn render_iif(&self, cond: &str, then_val: &str, else_val: &str) -> String {
1063        format!("CASE WHEN {cond} THEN {then_val} ELSE {else_val} END")
1064    }
1065
1066    /// Render a `DATE_TRUNC` expression.
1067    ///
1068    /// `SqlServer`: `DATETRUNC(field, expr)`, `ClickHouse`: `toStartOfMonth(expr)`, others: `DATE_TRUNC('field', expr)`
1069    fn render_date_trunc(&self, field: &str, expr: &str) -> String {
1070        format!("DATE_TRUNC('{field}', {expr})")
1071    }
1072
1073    /// **One part of a date or a timestamp, as a whole number.**
1074    ///
1075    /// A design's `date_part('<part>', <column>)` hands this one of ten words,
1076    /// and each must answer the number named here, the same on every engine:
1077    ///
1078    /// | `field` | the number |
1079    /// |---|---|
1080    /// | `YEAR` | the year, such as 2026 |
1081    /// | `QUARTER` | 1 to 4 |
1082    /// | `MONTH` | 1 to 12 |
1083    /// | `ISOWEEK` | the ISO 8601 week, 1 to 53, weeks starting on Monday |
1084    /// | `DAY` | the day of the month, 1 to 31 |
1085    /// | `HOUR` | 0 to 23 |
1086    /// | `MINUTE` | 0 to 59 |
1087    /// | `SECOND` | 0 to 59, the whole second |
1088    /// | `ISODOW` | the ISO day of the week, Monday 1 to Sunday 7 |
1089    /// | `DOY` | the day of the year, 1 to 366 |
1090    ///
1091    /// The default writes `EXTRACT(<field> FROM <column>)`, which most engines
1092    /// refuse for `ISOWEEK`, `ISODOW` or `DOY`: override it, spelling each word
1093    /// your engine's way — `DATEPART(ISO_WEEK, c)` on SQL Server,
1094    /// `toISOWeek(c)` on `ClickHouse` — and run each of the ten on your engine.
1095    fn render_date_extract(&self, field: &str, column: &str) -> String {
1096        format!("EXTRACT({field} FROM {column})")
1097    }
1098
1099    /// Render a `TRY_CAST` expression.
1100    ///
1101    /// `ClickHouse`: `accurateCastOrNull(expr, 'Type')`, others: `TRY_CAST(expr AS Type)`
1102    fn render_try_cast(&self, expr: &str, target: &DataType) -> String {
1103        format!("TRY_CAST({} AS {})", expr, self.render_data_type(target))
1104    }
1105
1106    /// Render a named parameter placeholder.
1107    ///
1108    /// `SqlServer`: `@name`, `PostgreSQL`: `:name`, Databricks: `{name}`
1109    fn render_named_parameter(&self, name: &str) -> String {
1110        format!(":{name}")
1111    }
1112
1113    /// Keyword emitted for MERGE NOT MATCHED branch.
1114    ///
1115    /// SqlServer/Databricks: `WHEN NOT MATCHED BY TARGET`, others: `WHEN NOT MATCHED`.
1116    fn merge_not_matched_keyword(&self) -> &'static str {
1117        "WHEN NOT MATCHED"
1118    }
1119
1120    /// Whether MERGE supports `WHEN NOT MATCHED BY SOURCE`.
1121    fn supports_merge_not_matched_by_source(&self) -> bool {
1122        false
1123    }
1124
1125    /// Whether this platform takes a `MERGE` statement.
1126    ///
1127    /// **This is `DialectCapabilities::merge_shape().merges()`, restated where
1128    /// only a `SqlRenderer` is in hand** — the standalone `MergeStatement`
1129    /// builder takes this trait alone and cannot ask the capability surface.
1130    /// The two must answer identically; the capability/renderer consistency
1131    /// gate holds them to equality with no exceptions, so a dialect that
1132    /// overrides one and not the other is refused before it ships. An engine
1133    /// whose shape is an upsert statement, an engine-deduplicated insert, or a
1134    /// refusal answers `false` here.
1135    fn merge_supported(&self) -> bool {
1136        true
1137    }
1138
1139    /// Whether `WITH SCHEMA EVOLUTION` is supported for MERGE.
1140    fn merge_supports_schema_evolution_keyword(&self) -> bool {
1141        false
1142    }
1143
1144    /// Whether table hints are supported in MERGE target.
1145    fn merge_supports_table_hints(&self) -> bool {
1146        false
1147    }
1148
1149    /// Whether OUTPUT clause should be used for MERGE result projection.
1150    fn merge_uses_output_clause(&self) -> bool {
1151        false
1152    }
1153
1154    /// Whether RETURNING clause is supported for MERGE.
1155    fn merge_supports_returning_clause(&self) -> bool {
1156        false
1157    }
1158
1159    /// **The longest line this engine's own client reads intact from a script
1160    /// file**, or `None` where it reads any line.
1161    ///
1162    /// A generated statement can put a long expression on one line. An engine
1163    /// whose client misreads a line past some length is handed its scripts
1164    /// with every longer line broken between two words, outside any quoted
1165    /// text and outside any comment, which changes nothing the statement
1166    /// says.
1167    fn longest_script_line(&self) -> Option<usize> {
1168        None
1169    }
1170
1171    /// **How this engine writes a short name for a relation.**
1172    ///
1173    /// A composed load gives a table, a view, a common table expression or a
1174    /// subquery a short name and then addresses it by that name for the rest
1175    /// of the statement. Most engines take the word `AS` in front of it, and
1176    /// most of those also take the name on its own. One takes only the name:
1177    /// `FROM orders AS src` is refused outright, in a `FROM` and again in a
1178    /// `MERGE`.
1179    ///
1180    /// So the two words are the dialect's to write. `relation` arrives already
1181    /// quoted and qualified; `alias` arrives already quoted. Nothing here
1182    /// changes what the statement means — only how this engine spells it.
1183    fn render_relation_alias(&self, relation: &str, alias: &str) -> String {
1184        format!("{relation} AS {alias}")
1185    }
1186
1187    /// **How a statement that changes the shape of the warehouse is written
1188    /// inside a routine's body.**
1189    ///
1190    /// A generated load empties its landing table when it is finished, and on
1191    /// most engines that statement stands in the body exactly as it would
1192    /// stand on its own. One engine runs no such statement directly in a
1193    /// block: `TRUNCATE TABLE …` there is a parse error, and the same words
1194    /// handed back to the SQL engine as a string run. `statement` arrives
1195    /// terminated the way the rest of the body is.
1196    fn render_ddl_within_routine_body(&self, statement: &str) -> String {
1197        statement.to_owned()
1198    }
1199
1200    /// **How this engine empties a table and keeps it.**
1201    ///
1202    /// A generated load empties the staging table it fills before it writes
1203    /// the next batch into it. Most engines do that with `TRUNCATE TABLE`, and
1204    /// that is the default. One has no such statement and answers a syntax
1205    /// error at the word; it empties a table with `DELETE FROM` instead.
1206    ///
1207    /// `relation` arrives already quoted and qualified. The answer is one whole
1208    /// statement, ended with `;`.
1209    fn render_empty_table(&self, relation: &str) -> String {
1210        format!("TRUNCATE TABLE {relation};")
1211    }
1212
1213    /// **Whether this engine lets a `MERGE` target carry an alias.**
1214    ///
1215    /// Most do, and the composed load addresses the target through a short
1216    /// `tgt`. An engine that answers `false` gets the target's own qualified
1217    /// name in every position the alias would have stood — dropping the
1218    /// qualifier instead makes the shared column names ambiguous.
1219    fn merge_target_accepts_alias(&self) -> bool {
1220        true
1221    }
1222
1223    /// **How this engine writes the condition a `MERGE` matches rows on.**
1224    ///
1225    /// Most take the condition after the word `ON` and nothing else. One
1226    /// requires it in parentheses and answers `ORA-00969: missing ON keyword`
1227    /// without them — pointing at the condition rather than at the word, which
1228    /// is there.
1229    ///
1230    /// `condition` is the whole predicate, which may run over several lines.
1231    fn render_merge_on(&self, condition: &str) -> String {
1232        format!("ON {condition}")
1233    }
1234
1235    /// **Whether this engine refuses a `WITH` clause in front of `MERGE`.**
1236    ///
1237    /// The composed load types its staging rows in one common table expression
1238    /// and hashes them in a second. Where a leading `WITH` parses, the two sit
1239    /// in front of the statement and the source is the second one by name.
1240    /// Where it does not, the same two move inside the `USING` subquery, which
1241    /// is the same query in a position the grammar accepts.
1242    ///
1243    /// Answer `true` if `WITH … MERGE INTO …` is a syntax error on your engine.
1244    fn merge_source_requires_inlined_ctes(&self) -> bool {
1245        false
1246    }
1247
1248    /// **Whether a `WITH` clause parses in front of this engine's `INSERT`.**
1249    ///
1250    /// A load that writes rows without matching them — one slice replaced, or
1251    /// rows appended — types its staging rows in one common table expression
1252    /// and hashes them in a second, exactly as the matching load does. Most
1253    /// engines take those two inside the statement, after the column list and
1254    /// in front of the `SELECT`, which is where Razeri puts them by default.
1255    ///
1256    /// Answer `true` where the same two have to lead instead, because the
1257    /// engine reads a `WITH` after a table name as something else entirely:
1258    /// T-SQL parses it as a table hint and rejects the statement. Nothing here
1259    /// changes the query — only where the engine will accept it written down.
1260    fn insert_accepts_leading_ctes(&self) -> bool {
1261        false
1262    }
1263
1264    /// **Whether this engine can be given a function by `CREATE FUNCTION`.**
1265    ///
1266    /// Razeri derives a surrogate key through a small emitted function, and an
1267    /// engine whose SQL surface has no `CREATE FUNCTION` at all can never
1268    /// receive it — the definition has to come from application code instead.
1269    /// Such an engine gets no emitted helper and no per-entity key function,
1270    /// and its dialect states the substitute in its own words.
1271    ///
1272    /// This is about the **grammar**, not about who owns the name: an engine
1273    /// that can define functions but takes its key function from an installed
1274    /// artifact still answers `true` here.
1275    fn supports_sql_function_ddl(&self) -> bool {
1276        true
1277    }
1278
1279    /// **Whether a scalar function's body is a bare expression.**
1280    ///
1281    /// Most engines want a full `SELECT` in front of the value a function
1282    /// returns. Some take the expression alone and reject the `SELECT`.
1283    fn scalar_function_body_is_expression(&self) -> bool {
1284        false
1285    }
1286
1287    /// **Whether a scalar function belongs to a schema.**
1288    ///
1289    /// On most engines a function lives in a schema and both its definition and
1290    /// its call sites name it. On an engine that answers `false`, functions are
1291    /// server-global: `CREATE FUNCTION` takes no qualifier, and a qualified
1292    /// call names a function that does not exist.
1293    fn scalar_function_names_are_schema_qualified(&self) -> bool {
1294        true
1295    }
1296
1297    /// **Whether an unqualified scalar-function call resolves.**
1298    ///
1299    /// Most engines resolve a bare function name through a search path. An
1300    /// engine that answers `true` resolves none, so a call with no schema in
1301    /// front of it has to be given one — [`DialectCapabilities::default_schema`]
1302    /// is the
1303    /// schema the function was created in when the caller named none.
1304    ///
1305    /// [`DialectCapabilities::default_schema`]: crate::DialectCapabilities::default_schema
1306    fn scalar_function_call_requires_schema(&self) -> bool {
1307        false
1308    }
1309
1310    /// **How this engine invokes a stored procedure**, as a whole statement.
1311    ///
1312    /// The composed load calls its own identity step, and a deploy script that
1313    /// has no procedures at all has to recognise these calls in order to inline
1314    /// what they would have run — so the call is spelled once, here.
1315    fn render_procedure_call(&self, qualified_name: &str) -> String {
1316        format!("CALL {qualified_name}();")
1317    }
1318
1319    /// The schema separator for qualified names.
1320    ///
1321    /// Almost always `.`, but could differ for some platforms.
1322    fn schema_separator(&self) -> &'static str {
1323        "."
1324    }
1325
1326    /// The statement separator for scripts.
1327    ///
1328    /// Almost all platforms: `;\n`, `SqlServer`: `;\nGO\n`
1329    fn statement_separator(&self) -> &'static str {
1330        ";\n"
1331    }
1332
1333    // ═══════════════════════════════════════════════════════════════
1334    // UPDATE behavior hooks
1335    // ═══════════════════════════════════════════════════════════════
1336
1337    /// Whether the platform supports compound assignment operators (e.g., `col += value`).
1338    ///
1339    /// `SqlServer`: `true`, others: `false`.
1340    fn supports_compound_assignment(&self) -> bool {
1341        false
1342    }
1343
1344    /// Whether UPDATE supports FROM clause for join-based updates.
1345    ///
1346    /// `PostgreSQL`, `SqlServer`, Snowflake, `CitusDB`: `true`, others: `false`.
1347    fn supports_update_from(&self) -> bool {
1348        false
1349    }
1350
1351    /// **Whether an `UPDATE … FROM` takes a joined source whose columns carry
1352    /// the target's own names**, under an unqualified `SET`:
1353    /// `UPDATE t SET v = source.v FROM (SELECT k, v FROM s) source WHERE
1354    /// t.k = source.k`.
1355    ///
1356    /// Asked only where [`supports_update_from`](Self::supports_update_from)
1357    /// is `true`. The default is `true`. Oracle refuses that shape as
1358    /// ambiguous (*"ORA-00918: column ambiguously specified"*) and `PostgreSQL`
1359    /// refuses the qualified `SET` that would cure it, so an engine like
1360    /// Oracle answers `false` and Razeri writes the correlated update, which
1361    /// every engine runs.
1362    fn update_from_takes_a_source_naming_the_targets_columns(&self) -> bool {
1363        true
1364    }
1365
1366    /// **The statement that tells this engine's planner what a table now
1367    /// holds**, after Razeri wrote it in bulk, or `None` where the engine needs
1368    /// none.
1369    ///
1370    /// `table` is spelled and qualified as the statement names it. Razeri runs
1371    /// the answer after a staging fill and at the end of every load, inside
1372    /// the load's transaction, so answer only with a statement your engine
1373    /// takes there. The default is `None`: an engine that keeps what its
1374    /// planner needs as it writes. `PostgreSQL` answers `ANALYZE <table>;` —
1375    /// with nothing to go on, its planner joins a hundred thousand rows to a
1376    /// hundred thousand one pair at a time.
1377    fn render_refresh_statistics(&self, table: &str) -> Option<String> {
1378        let _ = table;
1379        None
1380    }
1381
1382    /// Whether the platform uses ALTER TABLE ... UPDATE syntax (`ClickHouse` mutations).
1383    fn uses_alter_table_update(&self) -> bool {
1384        false
1385    }
1386
1387    /// **Whether a mutation on this platform can read only the row it is
1388    /// mutating.**
1389    ///
1390    /// `uses_alter_table_update` above says how the mutation is SPELLED; this
1391    /// says what the mutation may LOOK AT, and the two are independent facts
1392    /// that happen to be true of the same engine today. A platform answering
1393    /// `true` here cannot express *assign this column from another relation*
1394    /// at all: neither the `SET` list nor the `WHERE` may correlate out to a
1395    /// second table, so no re-spelling of a correlated `UPDATE` compiles —
1396    /// what is available instead is `INSERT … SELECT` over a join and a
1397    /// mutation whose predicate is an uncorrelated `IN`.
1398    ///
1399    /// **Measured, and it is the reason the seam exists.** `ClickHouse`
1400    /// 24.10.2 answered razeri's emitted re-open with
1401    /// `Code: 47 … UNKNOWN_IDENTIFIER` while resolving the assignment's own
1402    /// subquery — after the statement PARSED, which is what made the
1403    /// distinction visible: the syntax was right and the reach was not.
1404    ///
1405    /// It sits here on [`SqlRenderer`] and **only** here, deliberately not
1406    /// duplicated onto `DialectCapabilities`: `SqlRenderer` is the trait
1407    /// razeri's generators call, so this is the definition that decides, and a
1408    /// second declaration of the same fact is one that can drift.
1409    ///
1410    /// Default `false`: every engine razeri reaches except `ClickHouse` lets a
1411    /// mutation correlate, and the ANSI correlated `UPDATE` those nine compile
1412    /// is unchanged by this flag existing.
1413    fn mutation_reads_only_its_own_rows(&self) -> bool {
1414        false
1415    }
1416
1417    /// **The spelling a SQL statement inside a generated procedure body reads
1418    /// a declared local variable by.**
1419    ///
1420    /// A versioning load binds its clock ONCE — a `Declare` step at the top of
1421    /// the procedure — and every close and every open reads the variable
1422    /// instead of the engine's clock, so there is no daylight between the
1423    /// close of one version and the open of the next. The DECLARATION is the
1424    /// procedure renderer's business; what the load generators need is the
1425    /// reference spelling to embed in the raw statements they compose, and
1426    /// that differs by procedure language:
1427    ///
1428    /// - bare `name` — PL/pgSQL, MySQL/MariaDB, and the default;
1429    /// - `@name` — T-SQL;
1430    /// - `:name` — Snowflake Scripting's bind syntax inside SQL statements;
1431    /// - `$name` — the agnostic format's marker.
1432    ///
1433    /// It mirrors the variable-reference spelling of Razeri's own procedure
1434    /// renderers — the two must agree, and each override says so beside the
1435    /// spelling.
1436    fn render_procedure_variable_reference(&self, name: &str) -> String {
1437        name.to_owned()
1438    }
1439
1440    /// **The statement that binds one query's answer to a declared local.**
1441    ///
1442    /// A procedure body asks a question — how many rows arrived, what the
1443    /// latest timestamp is — and keeps the answer in a variable the rest of the
1444    /// body reads. Every procedural language has that statement and no two
1445    /// spell it alike:
1446    ///
1447    /// - `SELECT COUNT(*) FROM stage INTO v_seen;` — PL/pgSQL;
1448    /// - `SELECT @v_seen = (SELECT COUNT(*) FROM stage);` — T-SQL;
1449    /// - `v_seen := (SELECT COUNT(*) FROM stage);` — Snowflake Scripting;
1450    /// - `EXECUTE IMMEDIATE 'SELECT COUNT(*) FROM stage' INTO v_seen;` —
1451    ///   Oracle's PL/SQL.
1452    ///
1453    /// `query` is the question, already carrying this dialect's variable
1454    /// spelling and with no trailing terminator; `variable` is the bare name of
1455    /// the local, so a grammar that wants the reference spelling asks
1456    /// [`Self::render_procedure_variable_reference`] for it and one that wants
1457    /// the bare name has it already. The answer is a whole statement,
1458    /// terminator included.
1459    ///
1460    /// The default names the query and the target in a form that belongs to no
1461    /// engine, because there is no spelling every engine shares. A dialect
1462    /// rendering its own routine grammar answers this and the body arrives
1463    /// ready to compile.
1464    fn render_procedure_query_into_variable(&self, variable: &str, query: &str) -> String {
1465        format!("EXECUTE {query} INTO {};", self.render_procedure_variable_reference(variable))
1466    }
1467
1468    /// **The statement that stops a procedure body with an error of its own.**
1469    ///
1470    /// A generated load checks what it was given and refuses to go on when the
1471    /// answer is wrong. Every procedural language can stop that way and no two
1472    /// write it alike:
1473    ///
1474    /// - `RAISE EXCEPTION '%', 'the run brought no rows';` — PL/pgSQL;
1475    /// - `THROW 50000, 'the run brought no rows', 1;` — T-SQL;
1476    /// - `SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'the run brought no rows';`
1477    ///   — `MySQL` and `MariaDB`;
1478    /// - `RAISE_APPLICATION_ERROR(-20001, 'the run brought no rows');` —
1479    ///   Oracle's PL/SQL.
1480    ///
1481    /// `message` is the message **already rendered as a SQL expression in this
1482    /// dialect's spelling**: a literal arrives quoted and escaped, and a
1483    /// variable arrives named the way
1484    /// [`Self::render_procedure_variable_reference`] names one, because a body
1485    /// may raise what it read rather than what it was written with. Put it
1486    /// where the statement wants it and do not quote it again. `error_code` is
1487    /// the number the design gave, if it gave one; an engine whose statement
1488    /// takes a number supplies its own when this is `None`. The answer is a
1489    /// whole statement, terminator included.
1490    ///
1491    /// The default names the raise in a form that belongs to no engine. A
1492    /// dialect rendering its own routine grammar answers this and the body
1493    /// arrives ready to compile.
1494    fn render_procedure_raise(&self, message: &str, error_code: Option<i32>) -> String {
1495        match error_code {
1496            Some(code) => format!("RAISE EXCEPTION [{code}] {message};"),
1497            None => format!("RAISE EXCEPTION {message};"),
1498        }
1499    }
1500
1501    /// **The statement that writes a message where an operator can read it.**
1502    ///
1503    /// A load says how it is going, and where that message goes is an engine's
1504    /// own business — a client notice, a print stream, an event table, a
1505    /// result set nobody reads:
1506    ///
1507    /// - `RAISE NOTICE '%', 'loaded';` — PL/pgSQL;
1508    /// - `PRINT 'loaded';` — T-SQL;
1509    /// - `SYSTEM$LOG('INFO', 'loaded');` — Snowflake Scripting;
1510    /// - `DBMS_OUTPUT.PUT_LINE('loaded');` — Oracle's PL/SQL, which has no
1511    ///   levels and ignores the one it is given.
1512    ///
1513    /// `message` arrives the same way [`Self::render_procedure_raise`] takes
1514    /// it: already a SQL expression in this dialect's spelling, quoted where it
1515    /// is a literal. `level` is the severity the design wrote; an engine whose
1516    /// statement carries one reads
1517    /// [`ProcedureLogLevel::as_str`] and spells it the way that statement
1518    /// wants. The answer is a whole statement, terminator included.
1519    ///
1520    /// The default names the level and the message in a form that belongs to no
1521    /// engine. A dialect rendering its own routine grammar answers this and the
1522    /// body arrives ready to compile. **An engine with no statement that
1523    /// writes a message answers `None`**, and the line is left out of the
1524    /// routine rather than written as a comment or a statement it refuses.
1525    fn render_procedure_log(&self, level: ProcedureLogLevel, message: &str) -> Option<String> {
1526        let word = match level {
1527            ProcedureLogLevel::Debug => "DEBUG",
1528            ProcedureLogLevel::Info => "NOTICE",
1529            ProcedureLogLevel::Warning => "WARNING",
1530            ProcedureLogLevel::Error => "EXCEPTION",
1531        };
1532        Some(format!("LOG {word} {message};"))
1533    }
1534
1535    /// **The statement a generated load asks a scalar question with and throws
1536    /// the answer away.**
1537    ///
1538    /// A load's guard evaluates one scalar and reads nothing back: it either
1539    /// passes silently or stops the load on the engine's own error. What
1540    /// differs between engines is not the question but the STATEMENT that may
1541    /// carry it, and the difference is a procedure-language rule rather than a
1542    /// SQL one:
1543    ///
1544    /// - a bare `SELECT` is a legal script statement everywhere, and is legal
1545    ///   inside the procedure bodies `MySQL`, `MariaDB` and T-SQL accept, where it
1546    ///   simply hands the caller a result set nobody reads — so that is the
1547    ///   default;
1548    /// - **PL/pgSQL refuses it.** A `SELECT` with no destination inside a
1549    ///   procedure body is `query has no destination for result data`, raised
1550    ///   the moment the procedure is called. PL/pgSQL's own discard form is
1551    ///   `PERFORM`, and the Postgres family overrides this to spell it.
1552    ///
1553    /// The engines that ship no stored procedure inline the same statement into
1554    /// a script, where the default is the only form that runs — and every such
1555    /// engine takes the default, so one answer per dialect is unambiguous.
1556    ///
1557    /// `alias` names the answered column for the forms that answer one; a
1558    /// discard form that answers nothing ignores it.
1559    fn render_discarded_scalar(&self, expression: &str, alias: &str) -> String {
1560        format!("SELECT {expression} AS {alias};")
1561    }
1562
1563    /// **How a script with no stored procedures binds the load's clock once**
1564    /// — same rule as [`Self::render_procedure_variable_reference`], on the
1565    /// engines where a load ships as a flat statement list rather than a
1566    /// procedure body.
1567    ///
1568    /// The statements run FIRST and bind the engine's current moment to
1569    /// `name`; the reference is the expression every close and open in the
1570    /// same run then reads, so a version's `record_end_timestamp` and its
1571    /// successor's `record_start_timestamp` are one value by construction.
1572    /// Every spelling must be RE-RUNNABLE in one session — a second load in
1573    /// the same session rebinds rather than failing — because a load script is
1574    /// run whole and run again.
1575    ///
1576    /// The default is a one-row temporary table capturing
1577    /// [`Self::render_current_timestamp`], read back through a scalar
1578    /// subquery. Not every engine takes it: `CockroachDB` 24.3 answers
1579    /// *"temporary tables are only supported experimentally"*, and an engine
1580    /// like that overrides this with a session value of its own (`set_config`
1581    /// and `current_setting` there). Engines with a native variable idiom
1582    /// override it too (`DuckDB`'s `SET VARIABLE`, Databricks' session
1583    /// variables); `ClickHouse` overrides only the temporary-table spelling its
1584    /// own DDL requires.
1585    fn render_inline_clock_binding(&self, name: &str, moment: &str) -> InlineClockBinding {
1586        InlineClockBinding {
1587            statements: vec![
1588                format!("DROP TABLE IF EXISTS {name};"),
1589                format!("CREATE TEMPORARY TABLE {name} AS SELECT {moment} AS load_clock;"),
1590            ],
1591            reference: format!("(SELECT load_clock FROM {name})"),
1592        }
1593    }
1594
1595    /// Whether UPDATE supports TOP(n) clause (`SqlServer`).
1596    fn update_supports_top_clause(&self) -> bool {
1597        false
1598    }
1599
1600    /// Whether UPDATE supports table hints (e.g., `WITH (NOLOCK)`) (`SqlServer`).
1601    fn update_supports_table_hints(&self) -> bool {
1602        false
1603    }
1604
1605    /// Whether UPDATE uses OUTPUT clause positioned before FROM (`SqlServer`).
1606    ///
1607    /// When `true`, RETURNING/OUTPUT is rendered between SET and FROM.
1608    /// When `false`, RETURNING is rendered at the end of the statement.
1609    fn update_uses_output_clause(&self) -> bool {
1610        false
1611    }
1612
1613    /// Whether UPDATE supports a RETURNING clause at the end of the statement.
1614    ///
1615    /// `PostgreSQL`, `CitusDB`: `true`. Others: `false`.
1616    fn update_supports_returning(&self) -> bool {
1617        false
1618    }
1619
1620    // ========================================================================
1621    // INSERT behavior hooks
1622    // ========================================================================
1623
1624    /// Whether INSERT uses OUTPUT clause positioned before VALUES/SELECT (`SqlServer`).
1625    ///
1626    /// When `true`, RETURNING/OUTPUT is rendered between column list and source.
1627    /// When `false`, RETURNING is rendered at the end of the statement.
1628    fn insert_uses_output_clause(&self) -> bool {
1629        false
1630    }
1631
1632    /// Whether INSERT supports RETURNING at the end (`PostgreSQL`, `CitusDB`).
1633    fn insert_supports_returning(&self) -> bool {
1634        false
1635    }
1636
1637    /// Whether INSERT supports table hints (e.g., `WITH (TABLOCK)`) (`SqlServer`).
1638    fn insert_supports_table_hints(&self) -> bool {
1639        false
1640    }
1641
1642    /// Whether INSERT OVERWRITE is supported (Snowflake, Databricks).
1643    fn supports_insert_overwrite(&self) -> bool {
1644        false
1645    }
1646
1647    /// Whether INSERT ... ON CONFLICT is supported (`PostgreSQL`, `CitusDB`).
1648    fn supports_on_conflict(&self) -> bool {
1649        false
1650    }
1651
1652    /// Whether OVERRIDING SYSTEM/USER VALUE clause is supported (`PostgreSQL`).
1653    fn supports_overriding_clause(&self) -> bool {
1654        false
1655    }
1656
1657    /// The keyword for returning data from DML: "OUTPUT" (`SqlServer`) or "RETURNING" (others).
1658    fn returning_clause_keyword(&self) -> &'static str {
1659        "RETURNING"
1660    }
1661
1662    /// Whether OUTPUT ... INTO is supported (`SqlServer`).
1663    fn supports_returning_into(&self) -> bool {
1664        false
1665    }
1666
1667    /// Whether multi-table INSERT (INSERT ALL) is supported (Snowflake).
1668    fn supports_multi_table_insert(&self) -> bool {
1669        false
1670    }
1671
1672    // ========================================================================
1673    // Function/procedure parameter behavior hooks
1674    // ========================================================================
1675
1676    /// Render the security mode clause for function/procedure definitions.
1677    ///
1678    /// Standard SQL: `SECURITY INVOKER`/`SECURITY DEFINER`.
1679    /// T-SQL: `EXECUTE AS CALLER`/`EXECUTE AS OWNER`.
1680    fn render_security_mode(&self, is_definer: bool) -> &'static str {
1681        if is_definer { "SECURITY DEFINER" } else { "SECURITY INVOKER" }
1682    }
1683
1684    /// Render a function parameter name.
1685    ///
1686    /// Standard SQL uses the bare name. T-SQL prefixes with `@`.
1687    fn render_function_parameter_name(&self, name: &str) -> String {
1688        name.to_string()
1689    }
1690
1691    /// Render explicit nullability for a function parameter, if the platform requires it.
1692    ///
1693    /// Most platforms omit nullability in parameter lists. T-SQL native
1694    /// compilation requires explicit `NULL`/`NOT NULL`.
1695    fn render_function_parameter_nullable(&self, _nullable: bool) -> Option<&'static str> {
1696        None
1697    }
1698
1699    /// Render the default-value clause for a function parameter.
1700    ///
1701    /// Standard SQL: `DEFAULT value`. T-SQL: `= value`.
1702    fn render_function_parameter_default(&self, value: &str) -> String {
1703        format!("DEFAULT {value}")
1704    }
1705
1706    /// Render a column name inside a table-valued function definition.
1707    ///
1708    /// Standard SQL uses the bare name. T-SQL uses bracket quoting `[name]`.
1709    fn render_function_column_name(&self, name: &str) -> String {
1710        name.to_string()
1711    }
1712
1713    // ========================================================================
1714    // Partition & generated column rendering hooks
1715    // ========================================================================
1716
1717    /// Render a time partition expression for a column and granularity.
1718    ///
1719    /// Granularity values: `"hourly"`, `"daily"`, `"weekly"`, `"monthly"`,
1720    /// `"quarterly"`, `"yearly"`.
1721    ///
1722    /// Default: `date_trunc('unit', column)` (ANSI / `PostgreSQL` style),
1723    /// mapping granularity names to `date_trunc` units.
1724    fn render_time_partition_expr(&self, column: &str, granularity: &str) -> String {
1725        let unit = match granularity {
1726            "hourly" => "hour",
1727            "daily" => "day",
1728            "weekly" => "week",
1729            "monthly" => "month",
1730            "quarterly" => "quarter",
1731            "yearly" => "year",
1732            other => other,
1733        };
1734        format!("date_trunc('{unit}', {column})")
1735    }
1736
1737    /// Render a DDL partition clause wrapping the expression list.
1738    ///
1739    /// Default: `PARTITION BY (expr)`. Snowflake: `CLUSTER BY (expr)`,
1740    /// Databricks: `PARTITIONED BY (expr)`, `ClickHouse`: `PARTITION BY expr` (no parens).
1741    fn render_partition_clause(&self, expr: &str) -> String {
1742        format!("PARTITION BY ({expr})")
1743    }
1744
1745    /// Render a generated column declaration.
1746    ///
1747    /// Default: `name GENERATED ALWAYS AS (expr) STORED` (`PostgreSQL` style).
1748    fn render_generated_column(&self, name: &str, expr: &str) -> String {
1749        format!("{name} GENERATED ALWAYS AS ({expr}) STORED")
1750    }
1751
1752    // ========================================================================
1753    // Window function capability hooks
1754    // ========================================================================
1755
1756    /// Whether the platform supports the EXCLUDE clause in window frame specifications.
1757    ///
1758    /// `PostgreSQL`, Snowflake, Databricks: `true`. Others: `false`.
1759    fn supports_window_frame_exclude(&self) -> bool {
1760        false
1761    }
1762
1763    /// Whether the platform supports the GROUPS frame mode in window functions.
1764    ///
1765    /// `PostgreSQL`, Databricks: `true`. Others: `false`.
1766    fn supports_window_frame_groups(&self) -> bool {
1767        false
1768    }
1769
1770    /// Whether the platform supports named windows (WINDOW clause).
1771    ///
1772    /// Most platforms: `true`. `ClickHouse`, `DuckDB`, `MariaDB`: `false`.
1773    fn supports_named_windows(&self) -> bool {
1774        true
1775    }
1776
1777    // ========================================================================
1778    // DDL table-scope capability hooks
1779    // ========================================================================
1780
1781    /// Whether the platform uses a `#` prefix for session-scoped temporary table names.
1782    ///
1783    /// SQL Server: `true`. Others: `false`.
1784    fn uses_hash_temp_table_prefix(&self) -> bool {
1785        false
1786    }
1787
1788    /// Whether the platform supports `DECLARE @name TABLE (...)` table variables.
1789    ///
1790    /// SQL Server: `true`. Others: `false`.
1791    fn supports_table_variables(&self) -> bool {
1792        false
1793    }
1794
1795    /// The full CREATE keyword for session-scoped temporary tables.
1796    ///
1797    /// Default: `"CREATE TEMPORARY"`.
1798    /// SQL Server: `"CREATE"` (uses `#name` instead of TEMPORARY keyword).
1799    /// Databricks: `"CREATE OR REPLACE TEMPORARY"`.
1800    fn render_create_temp_table_keyword(&self) -> &'static str {
1801        "CREATE TEMPORARY"
1802    }
1803
1804    /// Whether the platform supports transient tables (e.g. `CREATE TRANSIENT TABLE`).
1805    ///
1806    /// Snowflake: `true`. Others: `false`.
1807    fn supports_transient_tables(&self) -> bool {
1808        false
1809    }
1810
1811    /// Whether the platform supports `ON COMMIT DROP` for transaction-scoped temp tables.
1812    ///
1813    /// `PostgreSQL`, `CitusDB`: `true`. Others: `false`.
1814    fn supports_on_commit_drop(&self) -> bool {
1815        false
1816    }
1817
1818    /// **The clause written after a table's column list**, or `None` on an
1819    /// engine whose `CREATE TABLE` is complete at the closing parenthesis.
1820    ///
1821    /// Most engines take `CREATE TABLE t (…)` as it stands. Some refuse it
1822    /// without a clause the column list cannot carry — `ClickHouse` answers
1823    /// `ORDER BY or PRIMARY KEY clause is missing` — so its dialect answers the
1824    /// table's engine and sorting key here: `ENGINE = MergeTree()` and
1825    /// `ORDER BY (…)`, `ReplacingMergeTree(<version column>)` on a versioned
1826    /// table, and `ENGINE = Memory` on a temporary one. An engine with a
1827    /// mandatory clause of its own spells it here too.
1828    ///
1829    /// Razeri writes the answer on its own line, directly after the closing
1830    /// parenthesis of every `CREATE TABLE` it renders, and before the storage
1831    /// plan's clause from
1832    /// [`render_table_storage_clause`](crate::StorageRenderer::render_table_storage_clause).
1833    /// **What you answer here, the storage clause must not repeat**, and the
1834    /// reverse: two engine clauses in one statement is `Code: 62 … Syntax
1835    /// error` on `ClickHouse`. A sorting key belongs here rather than there,
1836    /// because it usually has to begin with the table's primary key, and only
1837    /// this method is handed it.
1838    ///
1839    /// Answer without a terminator; more than one line is fine.
1840    ///
1841    /// ```
1842    /// use razeri_plugin_contract::{SqlRenderer, TableSuffixRequest};
1843    ///
1844    /// struct Columnar;
1845    ///
1846    /// impl SqlRenderer for Columnar {
1847    ///     # fn render_data_type(&self, _: &razeri_plugin_contract::DataType) -> String { "TEXT".into() }
1848    ///     # fn quote_identifier(&self, name: &str) -> String { format!("\"{name}\"") }
1849    ///     # fn qualify_name(&self, _: Option<&str>, name: &str) -> String { self.quote_identifier(name) }
1850    ///     # fn render_current_timestamp(&self) -> String { "CURRENT_TIMESTAMP".into() }
1851    ///     fn render_table_suffix(&self, table: &TableSuffixRequest<'_>) -> Option<String> {
1852    ///         if table.temporary {
1853    ///             return Some("USING MEMORY".to_owned());
1854    ///         }
1855    ///         let key: Vec<String> =
1856    ///             table.primary_key.iter().map(|c| self.quote_identifier(c)).collect();
1857    ///         Some(format!("USING COLUMNSTORE\nSORT KEY ({})", key.join(", ")))
1858    ///     }
1859    /// }
1860    ///
1861    /// let key = ["id".to_owned()];
1862    /// let table = TableSuffixRequest::new().with_primary_key(&key);
1863    /// assert_eq!(
1864    ///     Columnar.render_table_suffix(&table).as_deref(),
1865    ///     Some("USING COLUMNSTORE\nSORT KEY (\"id\")"),
1866    /// );
1867    /// ```
1868    fn render_table_suffix(&self, table: &TableSuffixRequest<'_>) -> Option<String> {
1869        let _ = table;
1870        None
1871    }
1872
1873    /// Whether this platform's table sorting key refuses nullable columns.
1874    ///
1875    /// `ClickHouse`: `true` — a `MergeTree` sorting key containing a
1876    /// `Nullable(…)` column answers `Code: 44 … Sorting key contains nullable
1877    /// columns, but merge tree setting allow_nullable_key is disabled`, measured
1878    /// on 24.10.2 against the control (the same key over a non-nullable column
1879    /// of the same type is accepted). Others: `false`.
1880    ///
1881    /// The materializer asks this before it lets a design's clustering columns
1882    /// into a sorting key, so the answer is a refusal the author can act on
1883    /// rather than a deployment failure they cannot.
1884    fn sorting_key_refuses_nullable_columns(&self) -> bool {
1885        false
1886    }
1887
1888    /// Whether the platform requires a primary key on every table.
1889    ///
1890    /// `ClickHouse` (MergeTree): `true`. Others: `false`.
1891    fn requires_primary_key_in_create_table(&self) -> bool {
1892        false
1893    }
1894
1895    /// Create a boxed [`FormulaRenderer`](crate::FormulaRenderer) for this dialect.
1896    ///
1897    /// Real dialect implementations override this to return `Box::new(Self)`.
1898    /// The default returns an ANSI dialect with standard function names.
1899    fn to_formula_renderer(&self) -> Box<dyn crate::FormulaRenderer> {
1900        Box::new(crate::AnsiDialect)
1901    }
1902}
1903
1904/// Level 2 — Expression rendering with ANSI SQL defaults.
1905pub trait ExpressionRenderer: SqlRenderer {
1906    /// **Render a conversion of `expr` to `target`** — the expression the
1907    /// generated load's typed projection writes for every column.
1908    ///
1909    /// The default is `CAST(expr AS …)` over
1910    /// [`render_cast_type`](SqlRenderer::render_cast_type), the cast vocabulary
1911    /// rather than the column vocabulary: a column may be declared `STRING(36)`
1912    /// where a cast may name only `STRING`, and `TINYINT` where a cast must say
1913    /// `SMALLINT`. Override it where a conversion is not a `CAST` at all —
1914    /// `GoogleSQL` has no cast from `STRING` to `JSON` ("Invalid cast from
1915    /// STRING to JSON") and converts with `PARSE_JSON(expr)` instead.
1916    fn render_cast(&self, expr: &str, target: &DataType) -> String {
1917        format!("CAST({} AS {})", expr, self.render_cast_type(target))
1918    }
1919
1920    /// **This engine's canonical text for a value**: the same characters on
1921    /// every engine, whatever the session is set to.
1922    ///
1923    /// `expr` holds a value of `value_type`. The answer is its text as
1924    /// [`CanonicalText::of`](crate::CanonicalText::of) defines it for that
1925    /// type — `2026-01-05` for a date, `2026-01-05 12:34:56.12` for a
1926    /// timestamp, `2026-01-05 12:34:56.12+00:00` for a zoned moment, `10.00`
1927    /// for a `DECIMAL(12, 2)` ten, `10` for a floating ten, `true` for a truth
1928    /// value — and `NULL` where `expr` is `NULL`. `text` is the text type the value would otherwise be cast
1929    /// to, and every type written by the engine's own cast is cast to it.
1930    ///
1931    /// A load that refuses its rows names their values in this text, and a
1932    /// reconciliation writes a decimal measurement in it before comparing it
1933    /// with the source's by string equality. So an engine that writes a date
1934    /// by a format its session sets, drops a decimal's trailing zeros, writes
1935    /// every digit of a timestamp's precision, writes a zoned moment in the
1936    /// session's zone, writes a floating number to more or fewer digits, or
1937    /// holds a truth value as a number answers here with an explicit format. The default is the plain cast to `text`, which is the
1938    /// canonical text on an engine that does none of those things.
1939    fn render_value_text(&self, expr: &str, value_type: &DataType, text: &DataType) -> String {
1940        let _ = value_type;
1941        self.render_cast(expr, text)
1942    }
1943
1944    /// **The functions [`Self::render_value_text`] calls, and the statements
1945    /// that create them**, or `None` where it calls none.
1946    ///
1947    /// An engine with no `printf` computes a floating number's text from its
1948    /// own arithmetic ([`computed_float_text`](crate::computed_float_text)),
1949    /// and that arithmetic is several thousand characters long. Written into
1950    /// every place a value is named, it makes a statement hard to read and a
1951    /// line some clients misread. So such an engine writes it once, as
1952    /// [`FLOAT_TEXT_FUNCTION`](crate::FLOAT_TEXT_FUNCTION) and
1953    /// [`SINGLE_FLOAT_TEXT_FUNCTION`](crate::SINGLE_FLOAT_TEXT_FUNCTION), and
1954    /// its value text calls them by those names.
1955    ///
1956    /// A generated script creates them before anything that calls them, and
1957    /// only where something does. The default is `None`: the engine writes
1958    /// every value's text inline.
1959    fn value_text_functions(&self) -> Option<crate::ValueTextFunctions> {
1960        None
1961    }
1962
1963    /// **An expression that stops the statement with `sentence`**, where
1964    /// `sentence` is not `NULL`, and is `NULL` where it is.
1965    ///
1966    /// A generated load refuses rows it cannot write honestly by building a
1967    /// sentence about them and asking the engine for this expression. The
1968    /// engine's error then carries the sentence to whoever reads it. The
1969    /// default converts the sentence to a whole number, which most engines
1970    /// refuse and quote back: *Could not convert string '…' to INT64*. An
1971    /// engine that answers that conversion without an error spells a refusal
1972    /// of its own here, or says through
1973    /// [`DialectCapabilities::refusal_stops_the_statement`](crate::DialectCapabilities::refusal_stops_the_statement)
1974    /// that its refusal only warns.
1975    fn render_refusal(&self, sentence: &str) -> String {
1976        self.render_cast(sentence, &DataType::bigint())
1977    }
1978
1979    /// **A statement that reads `relation`, changes nothing, and stops with
1980    /// `sentence` where a row meets `condition`** — or `None`, the default,
1981    /// where the engine has none, and a load's refusal stays a statement that
1982    /// would write and stops on [`Self::render_refusal`].
1983    ///
1984    /// `condition` is true of a row holding a value the load refuses, and
1985    /// `sentence` is a text expression over that row: the words the engine's
1986    /// error should carry, and nothing around them. A statement that only
1987    /// reads is one an engine may run on several workers, which a statement
1988    /// that writes is not; the refusal ahead of a load reads every staged row,
1989    /// so on a large file that is most of what it costs. And an error raised
1990    /// with its own message carries the sentence alone, where a failed
1991    /// conversion wraps it in the engine's words about a type the column is
1992    /// not.
1993    fn render_read_only_refusal(
1994        &self,
1995        relation: &str,
1996        condition: &str,
1997        sentence: &str,
1998    ) -> Option<String> {
1999        let _ = (relation, condition, sentence);
2000        None
2001    }
2002
2003    /// **How many characters `expr` holds**, counted the way a person counts
2004    /// them: one for each character, whatever the bytes that store it, and
2005    /// every space inside or at either end of the value included.
2006    ///
2007    /// A reconciliation totals this over a text column and compares the total
2008    /// with the one razeri counted in the source file, so a value that gained
2009    /// a carriage return, lost its last letters or was padded on the way in
2010    /// shows up as a different number. `expr` is a text value; `NULL` counts
2011    /// as `NULL`, which a total leaves out.
2012    ///
2013    /// The default is the standard `CHAR_LENGTH`. An engine without it
2014    /// answers with its own function, and an engine whose usual length leaves
2015    /// out trailing spaces, counts bytes, or counts a character outside the
2016    /// basic plane as two answers with one that does none of those.
2017    fn render_text_length(&self, expr: &str) -> String {
2018        format!("CHAR_LENGTH({expr})")
2019    }
2020
2021    /// **The unit [`render_text_length`](Self::render_text_length) counts a
2022    /// value of this type in**, held where `held` says.
2023    ///
2024    /// A reconciliation compares the total with one counted from the source
2025    /// file, and counts the file in the same unit, so the two agree and the
2026    /// engine is asked for nothing it does not do quickly. The default is
2027    /// [`TextLengthUnit::Characters`]. An engine whose length of a large
2028    /// object counts UTF-16 code units — a character outside the basic plane
2029    /// counting two — answers [`TextLengthUnit::Utf16CodeUnits`] for the types
2030    /// it holds as one.
2031    fn text_length_unit(&self, data_type: &DataType, held: HeldAs) -> TextLengthUnit {
2032        let _ = (data_type, held);
2033        TextLengthUnit::Characters
2034    }
2035
2036    /// **The expression a load compares in place of `value`**, a column of
2037    /// this type held where `held` says, or `None` where this engine cannot
2038    /// compare such a value at all.
2039    ///
2040    /// A load looks for a row that arrives twice: it collapses the rows it
2041    /// reads with `SELECT DISTINCT` and counts each column's distinct values,
2042    /// so every column it reads is compared. The default answers `value`
2043    /// itself, which is right wherever the engine compares the type
2044    /// directly. An engine that holds the type as a large object it cannot
2045    /// group or compare answers `None`, and Razeri refuses such a column by
2046    /// name when it writes the load, saying to give it a length; or it answers
2047    /// an expression that stands for the value wherever two values are
2048    /// compared — the same text for the same value, and different text for
2049    /// different values.
2050    fn render_comparable(&self, value: &str, data_type: &DataType, held: HeldAs) -> Option<String> {
2051        let _ = (data_type, held);
2052        Some(value.to_owned())
2053    }
2054
2055    /// **A test that is true where a value of a date or timestamp column is
2056    /// not written as an ISO 8601 date**, on an engine that holds such a
2057    /// column as text it does not check — or `None`, the default, where the
2058    /// engine's date type checks every value written into it.
2059    ///
2060    /// On such an engine a fill written by hand can put `17/09/2026` in a
2061    /// load's day column and nothing refuses it: the load matches no row, the
2062    /// day loads empty, and every count agrees with itself. The reconciliation
2063    /// counts the values this test finds, and expects none. `value` is the
2064    /// column as the statement names it; `data_type` is the type the design
2065    /// declares, a date or a timestamp.
2066    fn render_not_an_iso_day(&self, value: &str, data_type: &DataType) -> Option<String> {
2067        let _ = (value, data_type);
2068        None
2069    }
2070
2071    /// **`text`, or a missing value where it is exactly one of `words`**: how
2072    /// a load reads a source field that holds a word meaning "no value", such
2073    /// as `NULL`, `n/a` or `?`.
2074    ///
2075    /// A source file names its own words for a missing value, and one design
2076    /// has to load the same warehouse on every engine, so a word matches
2077    /// letter for letter and in the same case, whatever the engine's usual
2078    /// comparison. `text` is a text value, already trimmed; `words` are the
2079    /// words as the file writes them, and the empty word is among them.
2080    ///
2081    /// The default writes each word as a standard string literal, its quotes
2082    /// doubled, and compares with the standard `IN`. An engine whose usual
2083    /// comparison ignores case or accents answers with a comparison that does
2084    /// not, and one whose string literals treat a backslash as an escape
2085    /// writes each word so that a backslash in it stays a backslash.
2086    fn render_missing_value(&self, text: &str, words: &[&str]) -> String {
2087        let listed = words
2088            .iter()
2089            .map(|word| format!("'{}'", word.replace('\'', "''")))
2090            .collect::<Vec<_>>()
2091            .join(", ");
2092        format!("CASE WHEN {text} IN ({listed}) THEN NULL ELSE {text} END")
2093    }
2094
2095    /// **Read a value the source spells its own way** into `target`, which is
2096    /// a date or a timestamp.
2097    ///
2098    /// An import arrives with `1/1/2015` in one column and
2099    /// `2015-01-01T00:00:00Z` in the next, and no engine reads either of them
2100    /// the same way as the engine beside it. So the design states the
2101    /// spelling, razeri reads it once into a [`DateFormat`], and this
2102    /// translates that list into whatever this engine's own pattern language
2103    /// is — `MM/DD/YYYY`, `MM/dd/yyyy`, a style number, or a format string
2104    /// this engine calls its own.
2105    ///
2106    /// **A value that is not spelled that way has to come back as NULL.**
2107    /// razeri reads the NULL as *this row did not parse* and writes the row
2108    /// into the table it sets rows aside in, naming the column and the format
2109    /// it was told to expect. An expression that stops the load instead leaves
2110    /// a warehouse half-loaded over one bad row, and one that answers a
2111    /// different day than the value says is worse than either. An engine whose
2112    /// parse reads a value it cannot read — rolling the forty-fifth of a month
2113    /// forward rather than answering nothing — writes its own guard beside the
2114    /// parse; [`DateFormat::shape_pattern`] and
2115    /// [`DateFormat::capture_pattern`] come from the same directive list, so
2116    /// the guard and the parse cannot hold two opinions about one spelling.
2117    ///
2118    /// **A directive this engine cannot say is a refusal, not a guess.**
2119    /// [`DateFormat::translate`] hands back the first directive this engine
2120    /// answered nothing for, and answering `None` here is how that reaches
2121    /// the author — by name, at the door, rather than as a wrong day in the
2122    /// warehouse.
2123    ///
2124    /// `None` — the default — is this engine stating nothing about how it
2125    /// reads a spelled value. razeri then refuses the column and names the
2126    /// engine.
2127    fn render_parse_datetime(
2128        &self,
2129        _expr: &str,
2130        _format: &DateFormat,
2131        _target: &DataType,
2132    ) -> Option<String> {
2133        None
2134    }
2135
2136    /// **Read a value the source writes its own way** into `target`, which is
2137    /// a number.
2138    ///
2139    /// A file writes a price `$2,899.99 `. The symbol, the thousands mark and
2140    /// the space around it are not part of the number, and no two engines
2141    /// agree about what to do with them — one converts the text to nothing,
2142    /// one to `2`, one raises. So the design states the picture, razeri reads
2143    /// it once into a [`NumberFormat`], and this renders the reading in
2144    /// whatever this engine's own words for *take this out* and *convert this*
2145    /// are.
2146    ///
2147    /// **A value that is not written that way has to come back as NULL.**
2148    /// razeri reads the NULL as *this row did not parse* and writes the row
2149    /// into the table it sets rows aside in, naming the column and the picture
2150    /// it was told to expect. An expression that stops the load instead leaves
2151    /// a warehouse half-loaded over one bad row, and one that answers `0` for
2152    /// text that was never a number is worse than either. An engine whose
2153    /// conversion raises, or answers a number for text that is not one, writes
2154    /// a guard in front of it; [`NumberFormat::shape_pattern`] is built from
2155    /// the same picture the marks come from, so the guard and the reading
2156    /// cannot hold two opinions about one spelling.
2157    ///
2158    /// `None` — the default — is this engine stating nothing about how it
2159    /// reads a written amount. razeri then refuses the column and names the
2160    /// engine. `None` is also the right answer for a target that is not a
2161    /// number: a picture says how an amount is written, and there is nothing
2162    /// else for it to be about.
2163    ///
2164    /// [`NumberFormat`]: crate::number_format::NumberFormat
2165    /// [`NumberFormat::shape_pattern`]: crate::number_format::NumberFormat::shape_pattern
2166    fn render_parse_number(
2167        &self,
2168        _expr: &str,
2169        _format: &NumberFormat,
2170        _target: &DataType,
2171    ) -> Option<String> {
2172        None
2173    }
2174
2175    /// Render string concatenation.
2176    fn render_concat(&self, parts: &[String]) -> String {
2177        format!("CONCAT({})", parts.join(", "))
2178    }
2179
2180    /// Render a COALESCE expression.
2181    fn render_coalesce(&self, parts: &[String]) -> String {
2182        format!("COALESCE({})", parts.join(", "))
2183    }
2184
2185    /// Render a string aggregation function.
2186    fn render_string_agg(&self, expr: &str, separator: &str) -> String {
2187        format!("STRING_AGG({expr}, '{separator}')")
2188    }
2189}
2190
2191/// A foreign key's referential action — SQL's own five words, no synonyms.
2192///
2193/// Lives here (rather than in the table model) so a dialect can answer
2194/// [`DdlRenderer::supports_foreign_key_action`] about a specific action
2195/// without depending on the modeling crate.
2196#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
2197pub enum ReferentialAction {
2198    /// Also delete or update the referencing rows
2199    Cascade,
2200    /// Set the referencing columns to NULL
2201    SetNull,
2202    /// Reject the delete or update; some engines defer the check
2203    Restrict,
2204    /// Reject the delete or update (the SQL default)
2205    NoAction,
2206    /// Set the referencing columns to their default values
2207    SetDefault,
2208}
2209
2210impl ReferentialAction {
2211    /// Parses the SQL words, case-insensitively; underscores read as spaces
2212    /// so `set_null` and `SET NULL` both land.
2213    ///
2214    /// # Errors
2215    /// Returns a plain message listing the valid words when the input is not
2216    /// one of them.
2217    pub fn from_sql_word(word: &str) -> Result<Self, String> {
2218        match word.trim().replace('_', " ").to_uppercase().as_str() {
2219            "CASCADE" => Ok(Self::Cascade),
2220            "SET NULL" => Ok(Self::SetNull),
2221            "RESTRICT" => Ok(Self::Restrict),
2222            "NO ACTION" => Ok(Self::NoAction),
2223            "SET DEFAULT" => Ok(Self::SetDefault),
2224            _ => Err(format!(
2225                "'{word}' is not a referential action. Use one of: CASCADE, SET NULL, \
2226                 RESTRICT, NO ACTION, SET DEFAULT."
2227            )),
2228        }
2229    }
2230}
2231
2232impl std::fmt::Display for ReferentialAction {
2233    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
2234        match self {
2235            Self::Cascade => write!(f, "CASCADE"),
2236            Self::SetNull => write!(f, "SET NULL"),
2237            Self::Restrict => write!(f, "RESTRICT"),
2238            Self::NoAction => write!(f, "NO ACTION"),
2239            Self::SetDefault => write!(f, "SET DEFAULT"),
2240        }
2241    }
2242}
2243
2244/// Level 3 — DDL rendering with reasonable defaults.
2245pub trait DdlRenderer: SqlRenderer {
2246    /// Prefix for CREATE TABLE statements.
2247    fn render_create_table_prefix(&self) -> &'static str {
2248        "CREATE TABLE"
2249    }
2250
2251    /// **Whether a column's `DEFAULT` is written before its `NOT NULL`.**
2252    ///
2253    /// The default, `false`, writes `NOT NULL DEFAULT …`, which is what every
2254    /// engine that does not override this has always been given. An engine
2255    /// that refuses that order and takes only `DEFAULT … NOT NULL` answers
2256    /// `true`.
2257    fn column_default_precedes_not_null(&self) -> bool {
2258        false
2259    }
2260
2261    /// Whether this engine carries the given foreign-key referential action
2262    /// (`ON DELETE …` / `ON UPDATE …`) in `CREATE TABLE` DDL.
2263    ///
2264    /// The ANSI default says yes to all five words. A dialect answers `false`
2265    /// for an action its engine rejects or silently never enforces; the
2266    /// renderer then keeps the author's declaration visible as a plain
2267    /// comment instead of emitting a clause the engine would refuse — a
2268    /// declared action never vanishes without a trace.
2269    fn supports_foreign_key_action(&self, action: ReferentialAction) -> bool {
2270        let _ = action;
2271        true
2272    }
2273
2274    /// Prefix for CREATE VIEW statements.
2275    fn render_create_view_prefix(&self) -> &'static str {
2276        "CREATE OR REPLACE VIEW"
2277    }
2278
2279    /// Render a column definition (name, type, nullability).
2280    fn render_column_definition(&self, name: &str, dt: &DataType, nullable: bool) -> String {
2281        let quoted = self.quote_identifier(name);
2282        let (type_str, null_str) = if nullable {
2283            (self.render_nullable_data_type(dt), "")
2284        } else {
2285            (self.render_data_type(dt), " NOT NULL")
2286        };
2287        format!("{quoted} {type_str}{null_str}")
2288    }
2289
2290    /// **The statement this engine takes for one change to a table that
2291    /// already holds rows**, or `None` where it has none.
2292    ///
2293    /// Razeri classifies every difference between two deploys and hands the
2294    /// ones that lose nothing to this method, one at a time. What comes back
2295    /// goes into the file an operator runs; `None` means the change is named in
2296    /// the plan with this engine's name beside it, so the reader learns which
2297    /// engine has no statement rather than that no engine does.
2298    ///
2299    /// # The default, and why it is the only one
2300    ///
2301    /// `ALTER TABLE … ADD COLUMN … <type>` is the spelling most engines take,
2302    /// so a dialect that answers nothing still writes the commonest evolution
2303    /// there is: a column that was not there yesterday. Some engines write
2304    /// `ADD` without `COLUMN` and refuse this one — Firebird answers *"Token
2305    /// unknown"* at `COLUMN` — so run it on yours, and override this method
2306    /// where it is refused.
2307    /// The column is declared without `NOT NULL`, which is the only form that
2308    /// can be added to a table that already holds rows.
2309    ///
2310    /// Every other change answers `None` by default. A widening and a record
2311    /// that gained a field are each spelled three or four ways across the
2312    /// engines Razeri reaches, and a default picked from one of them writes SQL
2313    /// the others reject — a statement handed to an operator who then finds out
2314    /// their engine will not take it. A dialect that has measured its engine
2315    /// says so here, and the ones that have not say nothing.
2316    ///
2317    /// The statement comes back without a terminator; the caller separates
2318    /// statements the way this engine separates them.
2319    fn render_column_evolution(&self, change: &ColumnEvolution<'_>) -> Option<String> {
2320        match change {
2321            ColumnEvolution::AddColumn { schema, table, column, data_type } => {
2322                let target = self.qualify_name(*schema, table);
2323                let name = self.quote_identifier(column);
2324                let rendered = self.render_nullable_data_type(data_type);
2325                Some(format!("ALTER TABLE {target} ADD COLUMN {name} {rendered}"))
2326            }
2327            ColumnEvolution::WidenColumn { .. } | ColumnEvolution::AddNestedField { .. } => None,
2328        }
2329    }
2330
2331    /// Render the surrogate identity column — a plain, ungenerated `NOT NULL`
2332    /// column of the type it is given, on **every** dialect.
2333    ///
2334    /// **razeri's surrogate keys are logical and DETERMINATE, never
2335    /// engine-generated.** The identity value is a hash
2336    /// derived from the entity's identity grain, WRITTEN BY THE LOAD
2337    /// (`materialize_core`, `sid_expression`), so the same design loaded with the
2338    /// same data converges to the same key on `Postgres`, `Snowflake`, `MariaDB`,
2339    /// `ClickHouse` — every target. A database-assigned `AUTO_INCREMENT` /
2340    /// `IDENTITY(1,1)` / `GENERATED ALWAYS AS IDENTITY` would be sequential,
2341    /// load-order-dependent and **non-portable** — a different key per database
2342    /// for the same row — which is the one thing a portable key may not be. So no
2343    /// dialect emits a generator here; every one renders a plain column and the
2344    /// load fills it.
2345    ///
2346    /// `SQLite` narrows the type to `INTEGER` and `ClickHouse`/`DuckDB` carry
2347    /// their own overrides only to document the same plain shape; no override
2348    /// re-introduces a generator.
2349    ///
2350    /// # The type is the engine's own word for it
2351    ///
2352    /// The word `BIGINT` is not written here, because not every engine has it:
2353    /// Oracle refuses it outright — `ORA-00902: invalid datatype` — so the
2354    /// table holding the column could not be created and nothing downstream
2355    /// of it could load. The shape is the decision here, a plain ungenerated
2356    /// column; how this engine spells a 64-bit integer is the dialect's answer
2357    /// to [`render_data_type`](SqlRenderer::render_data_type), asked for
2358    /// rather than assumed. An engine whose word for it *is* `BIGINT` gets a
2359    /// `BIGINT` column.
2360    fn render_identity_column(&self, name: &str, data_type: &DataType) -> String {
2361        let quoted = self.quote_identifier(name);
2362        let rendered = self.render_data_type(data_type);
2363        format!("{quoted} {rendered} NOT NULL")
2364    }
2365
2366    /// Whether this platform's engine actually **assigns** an identity column's
2367    /// value, rather than merely tolerating the declaration.
2368    ///
2369    /// **This is a dialect fact, not a field fact, and the difference is a
2370    /// deploy-time failure.** `Field::identity` says razeri intends the engine
2371    /// to generate the value; whether it does depends entirely on the target.
2372    /// `render_identity_column`'s own table above records that `DuckDB`,
2373    /// `SQLite` and `ClickHouse` have **no native identity** — there the column
2374    /// comes out as a plain `NOT NULL` and **the writer must supply it.**
2375    ///
2376    /// The default is `false` because the default `render_identity_column`
2377    /// emits no generation clause: a dialect that overrides the rendering to
2378    /// generate values must override this too, and
2379    /// `the_identity_capability_matches_what_each_dialect_renders` holds the two
2380    /// in step so they cannot drift.
2381    ///
2382    /// Read by the deploy bundle's write contract: a column
2383    /// the engine does not generate is a column a hand-written `INSERT` owes.
2384    fn generates_identity_values(&self) -> bool {
2385        false
2386    }
2387
2388    /// **The latest instant this platform can hold EXACTLY in the column it
2389    /// renders for `data_type`** — the ceiling the refuse-by-name rule compares
2390    /// against, or `None` for *no declared ceiling*.
2391    ///
2392    /// Spelled `YYYY-MM-DD HH:MM:SS`, which is how razeri writes a timestamp
2393    /// literal (`razeri_materialize::convert::OPEN_INTERVAL_SENTINEL`), so the
2394    /// comparison is a string comparison over a format that sorts
2395    /// chronologically. A ceiling and a value in the same spelling cannot be
2396    /// compared wrongly by a parser neither of them goes through.
2397    ///
2398    /// # What the refusal contract asks of this
2399    ///
2400    /// > A value razeri cannot store exactly on a target platform is a
2401    /// > deploy-time refusal naming the value, the column, the platform and the
2402    /// > ceiling — never a truncation, never a saturated write.
2403    ///
2404    /// The *refusal* lives in `razeri_materialize::bundle`, which is the one
2405    /// place it is built — the function is `refuse_unrepresentable_values`, and
2406    /// it is reached from `generate_bundle` before a single file is rendered.
2407    /// This is only where a platform declares the number.
2408    ///
2409    /// # A stated variance from the decision's original wording
2410    ///
2411    /// The refusal was decided as a *deploy-time* act, homed in the
2412    /// prerequisites step. Razeri refuses **earlier**: at bundle
2413    /// generation, by declining to emit the bundle at all. That is strictly
2414    /// ahead of deploy time and cannot be skipped by an operator who never runs
2415    /// the script — a deliberate strengthening, recorded rather
2416    /// than glossed. The consequence worth knowing: a *hand-assembled* bundle,
2417    /// or one from an older razeri, is not re-checked at deploy time by
2418    /// anything. **The default is `None`, which
2419    /// means "razeri has no measured ceiling for this platform and type" and not
2420    /// "this platform holds everything"** — an unmeasured platform gets no
2421    /// refusal rather than an invented one.
2422    ///
2423    /// # The ceiling is a property of the TYPE, not of the platform
2424    ///
2425    /// That is the half a platform-flat capability would get wrong, and it is
2426    /// measured rather than assumed (reading the renderers):
2427    ///
2428    /// | platform | `Timestamp(p)` renders | ceiling | `TimestampTz(p)` renders | ceiling |
2429    /// |---|---|---|---|---|
2430    /// | `MySQL`, `MariaDB` | `DATETIME(p)` | `None` — the end of the four-digit calendar is not a constraint | `TIMESTAMP(p)` | **`2038-01-19 03:14:07`** |
2431    /// | `ClickHouse` | `DateTime64(p)` | `2299-12-31 23:59:59` at p ≤ 6, **`2262-04-11 23:47:16`** at p ≥ 7 | `DateTime64(p,'UTC')` | the same |
2432    /// | everything else | — | not measured → `None` | — | `None` |
2433    ///
2434    /// On `MySQL` the two razeri type variants land on *different engine types*
2435    /// and get different answers: `Timestamp` renders as the
2436    /// non-converting `DATETIME` — the type whose semantics match the declared
2437    /// non-tz timestamp — which runs to the end of the calendar and so
2438    /// declares no ceiling at all, while `TimestampTz` keeps `TIMESTAMP` and its
2439    /// 32-bit epoch boundary. A check keyed on the platform alone would either
2440    /// miss the real ceiling or refuse a column that is fine. On `ClickHouse`
2441    /// the ceiling moves with the **declared precision** of one type:
2442    /// `DateTime64(9)` is int64 nanoseconds from the epoch and stops at
2443    /// 2262-04-11, while `DateTime64(6)` reaches 2299.
2444    ///
2445    /// Parameters:
2446    /// - `data_type`: the column's declared type, as the design carries it —
2447    ///   not the rendered string, because the precision is what moves the answer
2448    ///   and re-parsing it back out of `DateTime64(9)` would be a second reading
2449    ///   of what the dialect already knows.
2450    fn representable_timestamp_ceiling(&self, data_type: &DataType) -> Option<&'static str> {
2451        let _ = data_type;
2452        None
2453    }
2454
2455    /// Whether the platform requires every auto-increment / identity column to
2456    /// be the leading column of an index (PK or KEY).
2457    ///
2458    /// `MySQL` / `MariaDB`: `true` (otherwise error 1075). Others: `false`.
2459    ///
2460    /// When this is `true` and an identity column is not already the leading
2461    /// column of the primary key, the DDL renderer must emit an additional
2462    /// `KEY (identity_col)` clause on the table.
2463    fn auto_increment_requires_key(&self) -> bool {
2464        false
2465    }
2466
2467    /// Prefix for DROP TABLE statements.
2468    fn render_drop_table_prefix(&self, if_exists: bool) -> String {
2469        if if_exists { "DROP TABLE IF EXISTS".to_string() } else { "DROP TABLE".to_string() }
2470    }
2471
2472    /// Prefix for DROP VIEW statements.
2473    fn render_drop_view_prefix(&self, if_exists: bool) -> String {
2474        if if_exists { "DROP VIEW IF EXISTS".to_string() } else { "DROP VIEW".to_string() }
2475    }
2476
2477    /// Prefix for CREATE SCHEMA / CREATE NAMESPACE.
2478    fn render_create_schema_prefix(&self, if_not_exists: bool) -> String {
2479        if if_not_exists {
2480            "CREATE SCHEMA IF NOT EXISTS".to_string()
2481        } else {
2482            "CREATE SCHEMA".to_string()
2483        }
2484    }
2485
2486    /// Render a complete CREATE SCHEMA statement.
2487    ///
2488    /// Dialects that need fundamentally different syntax (e.g. T-SQL wrapping
2489    /// in `IF SCHEMA_ID(...) IS NULL EXEC(...)`) should override this method.
2490    /// The default concatenates [`Self::render_create_schema_prefix`] with the name.
2491    fn render_create_schema_statement(&self, name: &str, if_not_exists: bool) -> String {
2492        let prefix = self.render_create_schema_prefix(if_not_exists);
2493        format!("{prefix} {name}")
2494    }
2495
2496    /// Render a table comment (platform-specific syntax).
2497    ///
2498    /// Like [`Self::render_column_comment`], nothing in the tree calls this and
2499    /// no dialect overrides it — **and unlike its sibling it has not been
2500    /// measured.** Read that method's table before wiring either: the ANSI
2501    /// spelling is a hard syntax error on five of the ten platforms it was
2502    /// measured on at the column level, and this form was not probed.
2503    fn render_table_comment(&self, table: &str, comment: &str) -> Option<String> {
2504        Some(format!("COMMENT ON TABLE {table} IS '{comment}'"))
2505    }
2506
2507    /// Render a column comment — **not wired to anything, and measured**.
2508    ///
2509    /// # Nothing calls this
2510    ///
2511    /// No caller in the tree, no dialect override, and, until measured, no
2512    /// execution against a live engine — the exact state the column `DEFAULT`
2513    /// clause was in when it turned out to be refused outright on four platforms
2514    /// of ten and silently wrong on a fifth. Moving the open
2515    /// interval's meaning here was proposed **on the condition that it be measured first**,
2516    /// because *"a comment that fails to compile is a worse outcome than the
2517    /// clause it replaced"*.
2518    ///
2519    /// # It was measured, on ten platforms, and this ANSI form is right on five
2520    ///
2521    /// Executed against six containers, in-process `DuckDB` and
2522    /// `SQLite`, and both live cloud accounts:
2523    ///
2524    /// | platform | this `COMMENT ON COLUMN` | inline `COMMENT` in `CREATE TABLE` | what it does take |
2525    /// |---|---|---|---|
2526    /// | `PostgreSQL`, Citus | ✅ | ❌ syntax error | this, as a separate statement |
2527    /// | `DuckDB` | ✅ | ❌ syntax error | this, as a separate statement |
2528    /// | Snowflake, Databricks | ✅ | ✅ | either |
2529    /// | `ClickHouse` | ❌ `SYNTAX_ERROR` (code 62) | ✅ | inline, or `ALTER TABLE … COMMENT COLUMN c '…'` |
2530    /// | `MySQL`, `MariaDB` | ❌ `ERROR 1064` | ✅ | inline, or `ALTER TABLE … MODIFY <whole column definition> COMMENT '…'` |
2531    /// | SQL Server | ❌ `Msg 156, Incorrect syntax near the keyword 'ON'` | ❌ `Msg 102` | `EXEC sp_addextendedproperty …` — a stored procedure, not DDL |
2532    /// | `SQLite` | ❌ syntax error | ❌ syntax error | **nothing at all** |
2533    ///
2534    /// Four mechanisms across the ten platforms measured, and two of them carry
2535    /// a hazard the `DEFAULT` clause never had:
2536    ///
2537    /// - `MySQL`'s and `MariaDB`'s only `ALTER` route **redefines the column**.
2538    ///   Measured: a `MODIFY` that did not restate `NOT NULL` left
2539    ///   `is_nullable = YES` behind it. A documentation mechanism that can
2540    ///   silently de-constrain a column is strictly worse than the clause it
2541    ///   replaces.
2542    /// - SQL Server's `sp_addextendedproperty` **is not idempotent**:
2543    ///   `Msg 15233 — Property 'MS_Description' already exists`. Razeri emits
2544    ///   `CREATE TABLE IF NOT EXISTS` so a deploy bundle can be re-applied; a
2545    ///   comment mechanism that fails on the second run takes that away.
2546    ///
2547    /// # So the escape hatch was taken
2548    ///
2549    /// Q1's answer included its own fall-back — *"if the measurement comes back
2550    /// ugly, take the simpler answer: no comment, and the bundle README carries
2551    /// the contract"* — and the measurement is ugly. **Razeri emits no column
2552    /// comment.** The open interval's meaning ships instead in the deploy
2553    /// bundle's generated *write contract*
2554    /// (`razeri_materialize::bundle::write_contract`, Q5), which is prose an
2555    /// operator reads, is right on every platform because no engine parses
2556    /// it, and is owed regardless of how this measurement came out.
2557    ///
2558    /// The hook stays, unchanged and unwired, for the same reason
2559    /// `render_column_default` stays: a dialect that genuinely wants to emit one
2560    /// has somewhere to say so. **A dialect that overrides this owes a
2561    /// measurement first** — the ANSI spelling below is a hard syntax error on
2562    /// five of the ten platforms it was measured on.
2563    fn render_column_comment(&self, table: &str, column: &str, comment: &str) -> Option<String> {
2564        Some(format!("COMMENT ON COLUMN {table}.{column} IS '{comment}'"))
2565    }
2566
2567    /// Whether to render IF NOT EXISTS on CREATE TABLE.
2568    fn supports_if_not_exists(&self) -> bool {
2569        true
2570    }
2571
2572    /// Render a column's `DEFAULT` clause — **keyword included** — or decline it.
2573    ///
2574    /// `None` means *this dialect renders no default clause for this value*,
2575    /// and Razeri's column renderer omits the whole clause. The ANSI default
2576    /// returns `None` for every value, which
2577    /// is the write-contract rule expressed where the renderer can see it:
2578    /// **razeri emits no
2579    /// column `DEFAULT` clause in rendered DDL, on any platform, for any value —
2580    /// a value the generated load owns is written by the load, never by the
2581    /// schema.**
2582    ///
2583    /// *Read that qualifier deliberately.* The sentence is about the DDL an engine
2584    /// executes. The `.raz.sql` **agnostic interchange** form —
2585    /// `Table::to_ddl()`, which `Table::from_ddl()` parses back — still
2586    /// serializes `Field::default_value` as `DEFAULT <value>`, because that is
2587    /// where a design's declared default survives a save and a load.
2588    ///
2589    /// **That exception is a serializer, not a platform, and the word "agnostic"
2590    /// names both.** `to_ddl()` renders through a free function and consults no
2591    /// dialect; you reach it by calling it, never by choosing a target.
2592    /// `SchemaObject::render_ddl` short-circuits to it only for a renderer whose
2593    /// [`SqlRenderer::is_agnostic_dialect`] answers `true` — which defaults to
2594    /// `false` here and is overridden by exactly one type in the tree,
2595    /// `AnsiDialect`, which no registration hands out. The platform the registry
2596    /// publishes under the name `agnostic` is `AgnosticDialect`; it does **not**
2597    /// override the method, so it declines the clause like every other target.
2598    /// The rule therefore holds on every registered platform, `agnostic`
2599    /// included, on every rendering path, and a regression test pins it.
2600    ///
2601    /// # Why the keyword lives in here
2602    ///
2603    /// The answer carries the keyword so that the hook can **decline a clause**
2604    /// as well as rewrite a value. Were the caller to add `DEFAULT` in front of
2605    /// whatever came back, a dialect returning `""` would emit a bare dangling
2606    /// `DEFAULT`. The same holds for statement boundaries: *the emission
2607    /// declares the boundary; a consumer never infers it.*
2608    ///
2609    /// # Why the hook survives the decision
2610    ///
2611    /// `Field::default_value` stays on the struct, so the design
2612    /// still knows what a column's intended default is, and a future dialect that
2613    /// genuinely wants to emit one has somewhere to say so. The distinction being
2614    /// bought is between **what razeri knows** and **what razeri emits**.
2615    ///
2616    /// A dialect that overrides this owes the whole clause, keyword and all —
2617    /// `Some(format!("DEFAULT {value}"))` — because there is nowhere else it is
2618    /// added.
2619    fn render_column_default(&self, value: &str) -> Option<String> {
2620        let _ = value;
2621        None
2622    }
2623
2624    /// **Spell an author's OWN declared default in this engine's words.**
2625    ///
2626    /// This is the other sense of "default", and the seam where a dialect
2627    /// translates it. [`render_column_default`](Self::render_column_default)
2628    /// governs the tables Razeri's generated load owns, and it declines
2629    /// everywhere by design. A `tile: base` table is the exception: the author
2630    /// owns every column, no generated statement writes into it, and the
2631    /// declared default is an engine constraint they asked for — so it is
2632    /// rendered, on every platform.
2633    ///
2634    /// Rendered **verbatim**, it is a portability hole with the design's own
2635    /// words in it. A design declaring `default: CURRENT_TIMESTAMP` is asking
2636    /// for *the current moment*, not for those eighteen characters; on `BigQuery` those
2637    /// characters name the zone-aware type, the column Razeri builds is
2638    /// `DATETIME`, and the `CREATE TABLE` is refused outright — *"Column
2639    /// default expression has type TIMESTAMP which cannot be assigned to column
2640    /// type DATETIME"*. The design is right, and this is where it is
2641    /// translated.
2642    ///
2643    /// The default returns the value unchanged, so a dialect that says nothing
2644    /// renders the author's words as written.
2645    ///
2646    /// # A dialect overriding this owes a measurement, and owes restraint
2647    ///
2648    /// Translate an expression whose *meaning* your engine spells differently.
2649    /// Do not rewrite an author's literal, do not widen a type, and do not
2650    /// silently drop a default you cannot express — an author who declared one
2651    /// and got none has a column that fills with NULL and a document that says
2652    /// otherwise. Where the engine has no equivalent at all, leave the value
2653    /// alone and let the engine refuse it: a loud failure belongs to the value
2654    /// the author wrote, and a quiet substitution belongs to nobody.
2655    ///
2656    /// Parameters:
2657    /// - `value`: the default exactly as the design declares it.
2658    /// - `column_type`: the type of the column it is being assigned to —
2659    ///   load-bearing, because the same expression is legal against one type
2660    ///   and refused against another on the same engine.
2661    fn render_declared_column_default(&self, value: &str, column_type: &DataType) -> String {
2662        let _ = column_type;
2663        value.to_owned()
2664    }
2665
2666    // ═══════════════════════════════════════════════════════════════
2667    // DDL Extensions
2668    // ═══════════════════════════════════════════════════════════════
2669
2670    /// Render a BEGIN transaction statement.
2671    ///
2672    /// `PostgreSQL`: `BEGIN`, `SqlServer`: `BEGIN TRANSACTION`, `ClickHouse`: empty (no DDL transactions)
2673    fn render_begin_transaction(&self) -> &'static str {
2674        "BEGIN"
2675    }
2676
2677    /// Render a COMMIT transaction statement.
2678    fn render_commit(&self) -> &'static str {
2679        "COMMIT"
2680    }
2681
2682    /// Whether the platform supports DDL transactions.
2683    fn supports_ddl_transactions(&self) -> bool {
2684        true
2685    }
2686
2687    /// Render a DROP FUNCTION statement with optional parameter types for overloaded functions.
2688    fn render_drop_function(&self, name: &str, param_types: &[&str], if_exists: bool) -> String {
2689        let exists = if if_exists { " IF EXISTS" } else { "" };
2690        if param_types.is_empty() {
2691            format!("DROP FUNCTION{exists} {name}")
2692        } else {
2693            format!("DROP FUNCTION{exists} {name}({})", param_types.join(", "))
2694        }
2695    }
2696
2697    /// Render the drop keyword for a SQL object type.
2698    fn render_drop_object_keyword(&self, obj_type: &str) -> String {
2699        obj_type.to_uppercase()
2700    }
2701
2702    /// Render the CREATE VIEW keyword/prefix with `secure/global_temp` modifiers.
2703    fn render_create_view_keyword(&self, secure: bool, global_temp: bool) -> String {
2704        let mut parts = vec!["CREATE OR REPLACE"];
2705        if secure {
2706            parts.push("SECURE");
2707        }
2708        if global_temp {
2709            parts.push("GLOBAL TEMPORARY");
2710        }
2711        parts.push("VIEW");
2712        parts.join(" ")
2713    }
2714
2715    /// Whether `ALTER TABLE … DROP COLUMN IF EXISTS` is part of the grammar.
2716    ///
2717    /// The default [`render_drop_column`](Self::render_drop_column) reads it:
2718    /// where it is `false`, a drop asked to run only where the column stands
2719    /// is one the default cannot write.
2720    fn supports_alter_drop_if_exists(&self) -> bool {
2721        true
2722    }
2723
2724    /// **The statement that drops one column of a table that holds rows.**
2725    ///
2726    /// `table` is spelled as the statement writes it, and `column` too.
2727    /// `only_where_it_stands` asks for a drop that does nothing where the
2728    /// column is already gone, so a file of drops that stopped part way runs
2729    /// again whole: Razeri asks for it on an engine that commits each
2730    /// statement on its own. Answer `None` where this engine cannot say
2731    /// that, and Razeri writes the plain drop and tells the operator to run
2732    /// the file again from the statement that stopped.
2733    ///
2734    /// The default writes `ALTER TABLE … DROP COLUMN …`, with `IF EXISTS`
2735    /// where [`supports_alter_drop_if_exists`](Self::supports_alter_drop_if_exists)
2736    /// says the engine takes it. An engine with neither `IF EXISTS` nor the
2737    /// `COLUMN` word answers in its own words: a guard over its catalogue, a
2738    /// prepared statement chosen by `information_schema`, or a block.
2739    fn render_drop_column(
2740        &self,
2741        table: WrittenObject<'_>,
2742        column: &str,
2743        only_where_it_stands: bool,
2744    ) -> Option<Written> {
2745        let relation = table.relation;
2746        if !only_where_it_stands {
2747            return Some(Written::Statement(format!(
2748                "ALTER TABLE {relation} DROP COLUMN {column}"
2749            )));
2750        }
2751        self.supports_alter_drop_if_exists().then(|| {
2752            Written::Statement(format!("ALTER TABLE {relation} DROP COLUMN IF EXISTS {column}"))
2753        })
2754    }
2755
2756    /// **The statement that drops one of Razeri's views**, spelled as the
2757    /// statement writes it.
2758    ///
2759    /// `only_where_it_stands` asks for a drop that does nothing where the
2760    /// view is already gone. The default is
2761    /// [`render_drop_view_prefix`](Self::render_drop_view_prefix) and the
2762    /// view; an engine with no `DROP VIEW IF EXISTS` answers with a guard of
2763    /// its own, or `None` where it has none.
2764    fn render_drop_view(
2765        &self,
2766        view: WrittenObject<'_>,
2767        only_where_it_stands: bool,
2768    ) -> Option<Written> {
2769        Some(Written::Statement(format!(
2770            "{} {}",
2771            self.render_drop_view_prefix(only_where_it_stands),
2772            view.relation
2773        )))
2774    }
2775
2776    /// **A `CREATE TABLE` that runs only where the table is not there yet**,
2777    /// on an engine whose grammar has no `CREATE TABLE IF NOT EXISTS`.
2778    ///
2779    /// A deploy runs again over the warehouse it made, so each table it makes
2780    /// has to pass over one that already stands. Where
2781    /// [`supports_if_not_exists`](Self::supports_if_not_exists) is `true` the
2782    /// statement says so itself and this is never asked. Where it is `false`,
2783    /// `create` is the whole plain statement and `table` its name as written:
2784    /// answer with the statement behind a question of your engine's
2785    /// catalogue, or `None` — the default — where you have none, and a second
2786    /// deploy is refused at the first table by the engine, with nothing
2787    /// changed.
2788    fn render_create_table_if_absent(
2789        &self,
2790        table: WrittenObject<'_>,
2791        create: &str,
2792    ) -> Option<Written> {
2793        let _ = (table, create);
2794        None
2795    }
2796
2797    /// **The clause a table's storage settings are written in**, after its
2798    /// column list — SQL Server's `WITH (DATA_COMPRESSION = PAGE)` — or `None`
2799    /// where none of `settings` is this engine's, which is the default.
2800    ///
2801    /// See [`EngineSetting`]: spell the settings written for your engine and
2802    /// pass over the rest.
2803    fn render_table_storage_options(&self, settings: &[EngineSetting<'_>]) -> Option<String> {
2804        let _ = settings;
2805        None
2806    }
2807
2808    // ═══════════════════════════════════════════════════════════════
2809    // Drop Behavior Hooks
2810    // ═══════════════════════════════════════════════════════════════
2811
2812    /// Whether DROP INDEX uses ON syntax: `DROP INDEX idx ON table` (`SqlServer`).
2813    fn drop_index_uses_on_syntax(&self) -> bool {
2814        false
2815    }
2816
2817    /// **Whether this engine checks the constraints it accepts.**
2818    ///
2819    /// The default is [`ConstraintEnforcement::Enforced`] — eight of Razeri's
2820    /// registered platforms, and the safe assumption for a dialect that has not
2821    /// said, because *"this engine guarantees nothing"* is the unusual claim.
2822    ///
2823    /// Answer [`ConstraintEnforcement::DeclaredOnly`] where a `PRIMARY KEY`,
2824    /// `FOREIGN KEY` or `UNIQUE` is accepted and never checked. Two engines are
2825    /// that, in two different ways, and the type carries both:
2826    ///
2827    /// - `BigQuery` has the words. `NOT ENFORCED` is the only legal spelling,
2828    ///   so the keyword goes on the constraint line and the engine makes the
2829    ///   declaration honest by itself.
2830    /// - `Redshift` has no words. It takes all three constraints, checks none,
2831    ///   and renders DDL a reader cannot tell from `PostgreSQL`'s — so the note
2832    ///   is written above the constraint list instead, and it is the only place
2833    ///   that DDL says what it means.
2834    ///
2835    /// [`key_constraint_suffix`](Self::key_constraint_suffix) reads this, so
2836    /// stating enforcement states the suffix too.
2837    fn constraint_enforcement(&self) -> ConstraintEnforcement {
2838        ConstraintEnforcement::Enforced
2839    }
2840
2841    /// **The words a key constraint must carry to be accepted at all.**
2842    ///
2843    /// Appended to a table-level `PRIMARY KEY (…)` and to a
2844    /// `FOREIGN KEY (…) REFERENCES …`. Empty on every engine that enforces its
2845    /// keys, and empty on a declaring engine that has no keyword for it.
2846    ///
2847    /// The default reads
2848    /// [`constraint_enforcement`](Self::constraint_enforcement), so the two are
2849    /// one fact rather than two that must be kept in step. Override this only
2850    /// for a keyword a key constraint needs for some *other* reason than
2851    /// enforcement — there is no such engine in Razeri's set today.
2852    fn key_constraint_suffix(&self) -> &'static str {
2853        self.constraint_enforcement().keyword()
2854    }
2855
2856    /// **Whether a `PRIMARY KEY` is part of the grammar.**
2857    ///
2858    /// `false` on an engine that has no primary key at all — one that refuses
2859    /// the declaration, not one that accepts it and checks nothing (that is
2860    /// [`Self::constraint_enforcement`]). The key is left out of
2861    /// the `CREATE TABLE` and named in a comment beside it, and the loads still
2862    /// match rows on its columns, as they do everywhere. Default `true`.
2863    fn supports_primary_key_constraint(&self) -> bool {
2864        true
2865    }
2866
2867    /// **Whether a `FOREIGN KEY … REFERENCES` is part of the grammar.**
2868    ///
2869    /// `false` on an engine that refuses the declaration; it is left out and
2870    /// named in a comment beside the table. Default `true`.
2871    fn supports_foreign_key_constraint(&self) -> bool {
2872        true
2873    }
2874
2875    /// Whether a table-level `UNIQUE (…)` constraint is part of the grammar.
2876    ///
2877    /// `false` on an engine that has no such constraint at all, where the
2878    /// declaration is left out of the `CREATE TABLE` and named in a comment
2879    /// beside it rather than emitted and refused.
2880    fn supports_unique_constraint(&self) -> bool {
2881        true
2882    }
2883
2884    /// Whether a table-level `CHECK (…)` constraint is part of the grammar.
2885    ///
2886    /// `false` on an engine that has none, with the same treatment as
2887    /// [`supports_unique_constraint`](Self::supports_unique_constraint): named
2888    /// in a comment, never written as SQL the engine will reject.
2889    fn supports_check_constraint(&self) -> bool {
2890        true
2891    }
2892
2893    /// Whether the platform supports CASCADE/RESTRICT in DROP statements.
2894    ///
2895    /// True for `PostgreSQL`, `CitusDB`, `DuckDB`, Snowflake, Agnostic.
2896    /// False for `SqlServer`, `ClickHouse`, Databricks, `MySQL`, `MariaDB`.
2897    fn supports_cascade_drop(&self) -> bool {
2898        true
2899    }
2900
2901    /// Whether `COMMENT ON PROCEDURE` is part of the grammar.
2902    ///
2903    /// `true` almost everywhere, and **false on `Redshift`**, where
2904    /// `COMMENT ON FUNCTION`, `COMMENT ON VIEW` and `COMMENT ON COLUMN` all
2905    /// exist and the procedure form alone does not: *"syntax error at or near
2906    /// `PROCEDURE`"*, measured on a live workgroup. `CockroachDB` 24.3 refuses
2907    /// it the same way. A description is worth having and is not worth a
2908    /// statement that stops the script, so a declining engine simply carries
2909    /// no description on its procedures. Pass this answer to
2910    /// [`render_procedure_as`](crate::routine_render::render_procedure_as)
2911    /// when you spell a procedure through it, and it leaves the description
2912    /// out too.
2913    fn supports_procedure_comment(&self) -> bool {
2914        true
2915    }
2916
2917    /// Whether the platform supports function argument types in DROP FUNCTION.
2918    ///
2919    /// False for `ClickHouse` (no overloaded functions).
2920    fn supports_drop_function_arg_types(&self) -> bool {
2921        true
2922    }
2923
2924    /// Whether the platform supports SYNC option for DROP (`ClickHouse`).
2925    fn supports_sync_drop(&self) -> bool {
2926        false
2927    }
2928
2929    /// Whether the platform supports FORCE option for DROP (Databricks).
2930    fn supports_force_drop(&self) -> bool {
2931        false
2932    }
2933
2934    /// Whether the platform supports CASCADE in ALTER TABLE DROP.
2935    ///
2936    /// Only `PostgreSQL`, `CitusDB`, `DuckDB` support CASCADE in ALTER TABLE DROP.
2937    fn supports_alter_drop_cascade(&self) -> bool {
2938        false
2939    }
2940}
2941
2942/// **One row, written once** — the pieces of an INSERT that must do nothing
2943/// when a row already carries the same key.
2944///
2945/// The key value is not a field of its own: it is whichever `values` entry sits
2946/// at the same index as the key column in `columns`. Two fields that must agree
2947/// are two fields free to disagree, and the one they would disagree about here
2948/// is the one that decides whether a second write is a duplicate.
2949#[derive(Debug, Clone, Copy)]
2950pub struct InsertIfAbsent<'a> {
2951    /// The target table, already spelled the way the caller wants it written —
2952    /// schema-qualified or bare.
2953    pub table: &'a str,
2954    /// The columns being written, in the order `values` supplies them.
2955    pub columns: &'a [&'a str],
2956    /// One value expression per column.
2957    pub values: &'a [String],
2958    /// The columns whose equality means *this row is already here*.
2959    pub key_columns: &'a [&'a str],
2960}
2961
2962impl InsertIfAbsent<'_> {
2963    /// The value expression written into `column`, if it is being written.
2964    #[must_use]
2965    pub fn value_of(&self, column: &str) -> Option<&str> {
2966        let at = self.columns.iter().position(|name| *name == column)?;
2967        self.values.get(at).map(String::as_str)
2968    }
2969
2970    /// `key = value AND …` — the test for *already here*, in terms of the
2971    /// values this INSERT would have written.
2972    ///
2973    /// `qualifier` prefixes the column side, which the guarded form needs so
2974    /// the probe's own row is named unambiguously.
2975    ///
2976    /// # Panics
2977    ///
2978    /// When a key column is not among the columns being written. A key the
2979    /// INSERT does not supply cannot be tested for, and silently dropping it
2980    /// from the predicate would make the guard match everything.
2981    #[must_use]
2982    pub fn key_predicate(&self, qualifier: &str) -> String {
2983        self.key_columns
2984            .iter()
2985            .map(|column| {
2986                let value = self
2987                    .value_of(column)
2988                    .unwrap_or_else(|| panic!("{column} is a key column and must be written"));
2989                format!("{qualifier}{column} = {value}")
2990            })
2991            .collect::<Vec<_>>()
2992            .join(" AND ")
2993    }
2994
2995    /// **`ON CONFLICT (key) DO NOTHING`** — for the platforms that let the
2996    /// engine decide, which is every platform that has the clause.
2997    ///
2998    /// The engine tests the key as it writes, so two sessions registering one
2999    /// file at the same moment settle on one row rather than on whichever of
3000    /// them looked first.
3001    #[must_use]
3002    pub fn on_conflict_do_nothing(&self) -> String {
3003        format!(
3004            "{}\n{}\nON CONFLICT ({}) DO NOTHING;",
3005            self.target(),
3006            self.values_clause(),
3007            self.key_columns.join(", ")
3008        )
3009    }
3010
3011    /// **`ON DUPLICATE KEY UPDATE key = key`** — the `MySQL` family's no-op.
3012    ///
3013    /// `INSERT IGNORE` would be shorter and it is not the same request: it
3014    /// downgrades *every* error on the statement to a warning — a truncated
3015    /// value, a bad date — so a row that should have been refused arrives
3016    /// mangled and silent. Assigning the key to itself touches only the
3017    /// collision this is about, and leaves everything else to complain
3018    /// normally.
3019    #[must_use]
3020    pub fn on_duplicate_key_no_op(&self) -> String {
3021        let held = self
3022            .key_columns
3023            .iter()
3024            .map(|column| format!("{column} = {column}"))
3025            .collect::<Vec<_>>()
3026            .join(", ");
3027        format!("{}\n{}\nON DUPLICATE KEY UPDATE {held};", self.target(), self.values_clause())
3028    }
3029
3030    /// **The guarded form over an explicit one-row source** — the same
3031    /// `INSERT … SELECT … WHERE NOT EXISTS` the portable default writes, with a
3032    /// `FROM` clause under it.
3033    ///
3034    /// `GoogleSQL` refuses a `WHERE` on a query that selects from nothing —
3035    /// *"Query without FROM clause cannot have a WHERE clause"*, measured on the
3036    /// live service on every generated `register_file` procedure — while
3037    /// `PostgreSQL` and its cousins accept the bare form. `(SELECT 1)` is a
3038    /// one-row table that changes no result: the guard still decides whether
3039    /// the row is written, and the values are still the caller's.
3040    ///
3041    /// It stays a separate form rather than becoming the default because the
3042    /// engines that need no `FROM` read better without one, and a clause every
3043    /// platform carries for one platform's sake is how a rendering stops saying
3044    /// what it means.
3045    #[must_use]
3046    pub fn guarded_over_one_row(&self) -> String {
3047        format!(
3048            "{}\nSELECT {}\nFROM (SELECT 1)\nWHERE NOT EXISTS (SELECT 1 FROM {} WHERE {});",
3049            self.target(),
3050            self.values.join(", "),
3051            self.table,
3052            self.key_predicate("")
3053        )
3054    }
3055
3056    /// `INSERT INTO t (a, b)` — shared by every form below.
3057    fn target(&self) -> String {
3058        format!("INSERT INTO {} ({})", self.table, self.columns.join(", "))
3059    }
3060
3061    /// `VALUES (…)`.
3062    fn values_clause(&self) -> String {
3063        format!("VALUES ({})", self.values.join(", "))
3064    }
3065}
3066
3067/// Level 4 — DML rendering with reasonable defaults.
3068pub trait DmlRenderer: SqlRenderer {
3069    /// Prefix for INSERT statements.
3070    fn render_insert_prefix(&self) -> &'static str {
3071        "INSERT INTO"
3072    }
3073
3074    /// **Write one row, and do nothing if its key is already there.**
3075    ///
3076    /// Every platform can express this and they do not agree on how, so the
3077    /// spelling belongs to the dialect and not to the caller. The default is
3078    /// the portable one — an `INSERT … SELECT` guarded by `WHERE NOT EXISTS` —
3079    /// which needs no upsert syntax at all and is therefore the right answer
3080    /// for a platform nobody has written an arm for. Platforms with a native
3081    /// idiom override it: `ON CONFLICT … DO NOTHING` where that exists,
3082    /// `ON DUPLICATE KEY UPDATE` on the `MySQL` family.
3083    ///
3084    /// **No dialect is left without an idempotent form.** The guarded form is
3085    /// weaker than the native ones under concurrency — two sessions can both
3086    /// find the row absent and both insert — which is why a platform that can
3087    /// let the engine decide should, and why this one leans on the key's own
3088    /// uniqueness constraint to catch the race rather than pretending it cannot
3089    /// happen. On an engine that enforces no key at all the guard is the whole
3090    /// of what there is, and it is worth saying so rather than implying more.
3091    fn render_insert_if_absent(&self, insert: &InsertIfAbsent<'_>) -> String {
3092        format!(
3093            "{}\nSELECT {}\nWHERE NOT EXISTS (SELECT 1 FROM {} WHERE {});",
3094            insert.target(),
3095            insert.values.join(", "),
3096            insert.table,
3097            insert.key_predicate("")
3098        )
3099    }
3100
3101    /// Prefix for MERGE statements.
3102    fn render_merge_prefix(&self) -> &'static str {
3103        "MERGE INTO"
3104    }
3105
3106    /// Render the platform's upsert strategy keyword.
3107    ///
3108    /// Returns `None` if platform doesn't support upsert natively.
3109    fn render_upsert_strategy(&self) -> Option<&str> {
3110        None
3111    }
3112
3113    /// Render RETURNING / OUTPUT clause for INSERT/UPDATE.
3114    fn render_returning_clause(&self, _columns: &[&str]) -> Option<String> {
3115        None
3116    }
3117
3118    /// Render a LIMIT clause.
3119    fn render_limit_clause(&self, n: usize) -> String {
3120        format!("LIMIT {n}")
3121    }
3122
3123    /// Render a temporary table prefix or keyword.
3124    fn render_temp_table_prefix(&self) -> &'static str {
3125        "TEMPORARY"
3126    }
3127
3128    /// **One row holding an array becomes one row per element.**
3129    ///
3130    /// Razeri hands over the relation it is reading, the array column, and the
3131    /// element fields the statement needs; the engine answers with the `FROM`
3132    /// item that produces the rows and the expressions that read them. The
3133    /// three parts come back together because on at least one engine they
3134    /// depend on each other: its field expressions name an alias its own
3135    /// `FROM` item introduced.
3136    ///
3137    /// **The ordinal is one for the first element, on every engine.** Engines
3138    /// disagree about where an array starts — some count from one, some from
3139    /// zero, one answers in text — and the element's surrogate key is derived
3140    /// from its position. An engine answering in its own base would give one
3141    /// element two different keys depending on where it was stored, so the
3142    /// normalization belongs here, where the engine that counts from zero pays
3143    /// for it with one addition.
3144    ///
3145    /// `None` — the default — is this engine stating no flatten. Razeri then
3146    /// refuses the declaration and names the engine, rather than guessing an
3147    /// idiom or emitting a statement that does not run.
3148    fn render_flatten(&self, _request: &FlattenRequest<'_>) -> Option<Flatten> {
3149        None
3150    }
3151}
3152
3153/// Level 5 — Stored procedure / function rendering with defaults.
3154pub trait ProcedureRenderer: SqlRenderer {
3155    /// Prefix for CREATE PROCEDURE.
3156    fn render_create_procedure_prefix(&self) -> &'static str {
3157        "CREATE OR REPLACE PROCEDURE"
3158    }
3159
3160    /// Prefix for CREATE FUNCTION.
3161    fn render_create_function_prefix(&self) -> &'static str {
3162        "CREATE OR REPLACE FUNCTION"
3163    }
3164
3165    /// Render a variable declaration inside a procedure body.
3166    fn render_variable_declaration(&self, name: &str, dt: &DataType) -> String {
3167        format!("DECLARE {} {};", name, self.render_data_type(dt))
3168    }
3169
3170    /// Render BEGIN/END block delimiters.
3171    fn render_begin_end(&self, body: &str) -> String {
3172        format!("BEGIN\n{body}\nEND;")
3173    }
3174
3175    /// Whether procedures use a language clause (e.g., Snowflake JavaScript).
3176    fn requires_language_clause(&self) -> bool {
3177        false
3178    }
3179
3180    /// **A statement that calls each of `calls` once for every row of
3181    /// `query`**, or `None` where this engine has none to write, the default.
3182    ///
3183    /// `query` answers one column; each row's value is held in `variable`, of
3184    /// the type `variable_type` spells in this engine, and each call names
3185    /// `variable` where it takes the value. A generated fill asks this to make
3186    /// a staging partition for every day a file holds before it stages a row
3187    /// of any, on an engine that keeps nowhere for a row whose day has no
3188    /// partition. Where the answer is `None`, the fill is refused by name,
3189    /// with the calls to make by hand.
3190    fn render_call_for_each_row(
3191        &self,
3192        variable: &str,
3193        variable_type: &str,
3194        query: &str,
3195        calls: &[String],
3196    ) -> Option<String> {
3197        let _ = (variable, variable_type, query, calls);
3198        None
3199    }
3200}
3201
3202#[cfg(test)]
3203mod tests {
3204    use super::*;
3205    use crate::DialectCapabilities;
3206    use crate::sid_spelling::SidHelperLanguage;
3207
3208    /// A test dialect that implements only the required `SqlRenderer` methods.
3209    /// All other traits use defaults.
3210    struct TestDialect;
3211
3212    impl SqlRenderer for TestDialect {
3213        fn render_data_type(&self, dt: &DataType) -> String {
3214            match dt {
3215                DataType::Boolean(_) => "BOOLEAN".to_string(),
3216                DataType::Integer(_) => "INTEGER".to_string(),
3217                _ => "TEXT".to_string(),
3218            }
3219        }
3220        fn quote_identifier(&self, name: &str) -> String {
3221            format!("\"{name}\"")
3222        }
3223        fn qualify_name(&self, schema: Option<&str>, name: &str) -> String {
3224            match schema {
3225                Some(s) => format!("{}.{}", self.quote_identifier(s), self.quote_identifier(name)),
3226                None => self.quote_identifier(name),
3227            }
3228        }
3229        fn render_current_timestamp(&self) -> String {
3230            "CURRENT_TIMESTAMP".to_string()
3231        }
3232    }
3233
3234    impl ExpressionRenderer for TestDialect {}
3235    impl DdlRenderer for TestDialect {}
3236    impl DmlRenderer for TestDialect {}
3237    impl ProcedureRenderer for TestDialect {}
3238    impl DialectCapabilities for TestDialect {}
3239
3240    /// The same dialect with the two file-and-key facts stated. It exists to
3241    /// prove that a dialect Razeri never compiled against reaches the behaviour
3242    /// by *declaring* it — which is the whole property, and is not visible from
3243    /// a dialect that ships in this tree.
3244    struct StatedDialect;
3245
3246    impl SqlRenderer for StatedDialect {
3247        fn render_data_type(&self, _dt: &DataType) -> String {
3248            "TEXT".to_string()
3249        }
3250        fn quote_identifier(&self, name: &str) -> String {
3251            format!("\"{name}\"")
3252        }
3253        fn qualify_name(&self, _schema: Option<&str>, name: &str) -> String {
3254            self.quote_identifier(name)
3255        }
3256        fn render_current_timestamp(&self) -> String {
3257            "CURRENT_TIMESTAMP".to_string()
3258        }
3259        fn sid_helper_body(&self) -> Option<SidHelperBody> {
3260            Some(SidHelperBody {
3261                language: SidHelperLanguage::Named("ecmascript"),
3262                body: "return 1;",
3263            })
3264        }
3265        fn delimited_file_relation(&self, path_literal: &str) -> Option<String> {
3266            Some(format!("SCAN({path_literal})"))
3267        }
3268    }
3269
3270    /// **A dialect that says nothing writes a value by its plain cast**, and
3271    /// renders exactly what it rendered before the question existed.
3272    #[test]
3273    fn a_dialect_that_states_no_value_text_casts_the_value() {
3274        let d: &dyn ExpressionRenderer = &TestDialect;
3275        for value_type in [DataType::date(), DataType::timestamp(), DataType::decimal(12, 2)] {
3276            assert_eq!(
3277                d.render_value_text("x", &value_type, &DataType::varchar(64)),
3278                d.render_cast("x", &DataType::varchar(64)),
3279            );
3280        }
3281        assert!(d.value_text_functions().is_none(), "a plain cast calls no function");
3282    }
3283
3284    /// **A dialect that says nothing refuses by converting the sentence to a
3285    /// number**, which is what every engine that refuses the conversion
3286    /// needs.
3287    #[test]
3288    fn a_dialect_that_states_no_refusal_converts_the_sentence() {
3289        let d: &dyn ExpressionRenderer = &TestDialect;
3290        assert_eq!(d.render_refusal("s"), d.render_cast("s", &DataType::bigint()));
3291    }
3292
3293    /// **A dialect that says nothing counts characters with the standard
3294    /// function.**
3295    #[test]
3296    fn a_dialect_that_states_no_text_length_counts_with_char_length() {
3297        let d: &dyn ExpressionRenderer = &TestDialect;
3298        assert_eq!(d.render_text_length("name"), "CHAR_LENGTH(name)");
3299    }
3300
3301    /// **A dialect that says nothing reads a missing word with the standard
3302    /// `IN`**, each word a standard literal with its quotes doubled.
3303    #[test]
3304    fn a_dialect_that_states_no_missing_value_reading_compares_with_in() {
3305        let d: &dyn ExpressionRenderer = &TestDialect;
3306        assert_eq!(
3307            d.render_missing_value("TRIM(x)", &["", "NULL", "it's"]),
3308            "CASE WHEN TRIM(x) IN ('', 'NULL', 'it''s') THEN NULL ELSE TRIM(x) END"
3309        );
3310    }
3311
3312    /// **Silence is the default, and silence means Razeri composes.**
3313    ///
3314    /// Both facts are opt-in: a dialect that says nothing gets the SQL
3315    /// derivation for its key and no generated staging fill at all. Neither
3316    /// default may become a guess — a composed body on an engine with no hash
3317    /// keys a warehouse to a number nothing reproduces, and an invented file
3318    /// relation fails the load on its first statement.
3319    #[test]
3320    fn a_dialect_that_states_neither_fact_is_asked_for_nothing() {
3321        let d: &dyn SqlRenderer = &TestDialect;
3322        assert_eq!(d.sid_helper_body(), None);
3323        assert_eq!(d.delimited_file_relation("'/data/x.csv'"), None);
3324    }
3325
3326    /// **A dialect that says nothing adds nothing after the column list.** The
3327    /// default is what every engine that takes a bare `CREATE TABLE t (…)`
3328    /// needs, whatever the table is.
3329    #[test]
3330    fn a_dialect_that_spells_no_table_suffix_is_asked_for_none() {
3331        let d: &dyn SqlRenderer = &TestDialect;
3332        let key = ["id".to_owned()];
3333        let cluster = ["region".to_owned()];
3334        for table in [
3335            TableSuffixRequest::new(),
3336            TableSuffixRequest::new().with_temporary(true),
3337            TableSuffixRequest::new()
3338                .with_primary_key(&key)
3339                .with_clustering_columns(&cluster)
3340                .with_version_column(Some("_loaded_at")),
3341        ] {
3342            assert_eq!(d.render_table_suffix(&table), None, "{table:?}");
3343        }
3344    }
3345
3346    /// And a dialect that states them is heard through the trait object, which
3347    /// is the only way Razeri ever holds one.
3348    #[test]
3349    fn a_dialect_that_states_them_is_heard_through_the_trait_object() {
3350        let d: &dyn SqlRenderer = &StatedDialect;
3351        assert_eq!(
3352            d.sid_helper_body(),
3353            Some(SidHelperBody {
3354                language: SidHelperLanguage::Named("ecmascript"),
3355                body: "return 1;",
3356            })
3357        );
3358        assert_eq!(
3359            d.delimited_file_relation("'/data/x.csv'").as_deref(),
3360            Some("SCAN('/data/x.csv')")
3361        );
3362    }
3363
3364    #[test]
3365    fn sql_renderer_core_methods_work() {
3366        let d = TestDialect;
3367        assert_eq!(d.render_data_type(&DataType::boolean()), "BOOLEAN");
3368        assert_eq!(d.quote_identifier("users"), "\"users\"");
3369        assert_eq!(d.qualify_name(Some("public"), "users"), "\"public\".\"users\"");
3370        assert_eq!(d.render_current_timestamp(), "CURRENT_TIMESTAMP");
3371    }
3372
3373    #[test]
3374    fn dialect_name_returns_ansi_by_default() {
3375        let d = TestDialect;
3376        assert_eq!(d.dialect_name(), "ansi");
3377    }
3378
3379    #[test]
3380    fn expression_renderer_defaults_produce_ansi_sql() {
3381        let d = TestDialect;
3382        assert_eq!(d.render_cast("age", &DataType::int()), "CAST(age AS INTEGER)");
3383        assert_eq!(d.render_concat(&["a".into(), "b".into()]), "CONCAT(a, b)");
3384        assert_eq!(d.render_coalesce(&["x".into(), "y".into()]), "COALESCE(x, y)");
3385    }
3386
3387    #[test]
3388    fn ddl_renderer_defaults_produce_ansi_sql() {
3389        let d = TestDialect;
3390        assert_eq!(d.render_create_table_prefix(), "CREATE TABLE");
3391        assert_eq!(d.render_create_view_prefix(), "CREATE OR REPLACE VIEW");
3392        assert_eq!(
3393            d.render_column_definition("name", &DataType::boolean(), false),
3394            "\"name\" BOOLEAN NOT NULL"
3395        );
3396        assert_eq!(d.render_column_definition("age", &DataType::int(), true), "\"age\" INTEGER");
3397    }
3398
3399    #[test]
3400    fn dml_renderer_defaults_produce_ansi_sql() {
3401        let d = TestDialect;
3402        assert_eq!(d.render_insert_prefix(), "INSERT INTO");
3403        assert_eq!(d.render_merge_prefix(), "MERGE INTO");
3404        assert_eq!(d.render_limit_clause(10), "LIMIT 10");
3405        assert!(d.render_upsert_strategy().is_none());
3406        assert!(d.render_returning_clause(&["id"]).is_none());
3407    }
3408
3409    #[test]
3410    fn procedure_renderer_defaults_produce_ansi_sql() {
3411        let d = TestDialect;
3412        assert_eq!(d.render_create_procedure_prefix(), "CREATE OR REPLACE PROCEDURE");
3413        assert_eq!(
3414            d.render_variable_declaration("v_count", &DataType::int()),
3415            "DECLARE v_count INTEGER;"
3416        );
3417        assert!(!d.requires_language_clause());
3418    }
3419
3420    #[test]
3421    fn trait_hierarchy_allows_selective_override() {
3422        struct MySqlLike;
3423        impl SqlRenderer for MySqlLike {
3424            fn render_data_type(&self, _: &DataType) -> String {
3425                "INT".into()
3426            }
3427            fn quote_identifier(&self, name: &str) -> String {
3428                format!("`{name}`")
3429            }
3430            fn qualify_name(&self, schema: Option<&str>, name: &str) -> String {
3431                match schema {
3432                    Some(s) => format!("`{s}`.`{name}`"),
3433                    None => format!("`{name}`"),
3434                }
3435            }
3436            fn render_current_timestamp(&self) -> String {
3437                "NOW()".into()
3438            }
3439        }
3440        impl ExpressionRenderer for MySqlLike {
3441            fn render_concat(&self, parts: &[String]) -> String {
3442                format!("CONCAT({})", parts.join(", "))
3443            }
3444        }
3445        impl DdlRenderer for MySqlLike {}
3446        impl DmlRenderer for MySqlLike {}
3447        impl ProcedureRenderer for MySqlLike {}
3448        impl DialectCapabilities for MySqlLike {}
3449
3450        let d = MySqlLike;
3451        assert_eq!(d.quote_identifier("users"), "`users`");
3452        assert_eq!(d.render_current_timestamp(), "NOW()");
3453        // DDL defaults still work even though we only overrode expressions
3454        assert_eq!(d.render_create_table_prefix(), "CREATE TABLE");
3455    }
3456
3457    #[test]
3458    fn sql_renderer_expression_extensions_produce_ansi_defaults() {
3459        let d = TestDialect;
3460        assert_sql_renderer_literal_defaults(&d);
3461        assert_sql_renderer_parameter_and_merge_defaults(&d);
3462        assert_sql_renderer_update_insert_defaults(&d);
3463        assert_sql_renderer_partition_and_window_defaults(&d);
3464    }
3465
3466    /// Assert ANSI defaults for literal, date/time, and basic expression hooks.
3467    /// Parameters:
3468    /// - dialect: Renderer under test.
3469    fn assert_sql_renderer_literal_defaults(dialect: &TestDialect) {
3470        assert_eq!(dialect.render_boolean_literal(true), "TRUE");
3471        assert_eq!(dialect.render_boolean_literal(false), "FALSE");
3472        assert_eq!(dialect.render_date_literal("2024-01-15"), "DATE '2024-01-15'");
3473        assert_eq!(
3474            dialect.render_timestamp_literal("2024-01-15 12:00:00"),
3475            "TIMESTAMP '2024-01-15 12:00:00'"
3476        );
3477        assert_eq!(dialect.render_interval_literal("1 year"), "INTERVAL '1 year'");
3478        assert_eq!(dialect.render_current_date(), "CURRENT_DATE");
3479        assert_eq!(dialect.concat_operator(), "||");
3480        assert_eq!(dialect.render_nan_literal(&DataType::float()), "NULL");
3481        assert_eq!(dialect.render_infinity_literal(true, &DataType::float()), "NULL");
3482        assert_eq!(dialect.ifnull_function_name(), "COALESCE");
3483        assert_eq!(
3484            dialect.render_iif("x > 0", "pos", "neg"),
3485            "CASE WHEN x > 0 THEN pos ELSE neg END"
3486        );
3487        assert_eq!(
3488            dialect.render_date_trunc("month", "created_at"),
3489            "DATE_TRUNC('month', created_at)"
3490        );
3491        assert_eq!(
3492            dialect.render_date_extract("year", "created_at"),
3493            "EXTRACT(year FROM created_at)"
3494        );
3495        assert_eq!(dialect.render_try_cast("age", &DataType::int()), "TRY_CAST(age AS INTEGER)");
3496    }
3497
3498    /// Assert ANSI defaults for parameter and merge-related extension hooks.
3499    /// Parameters:
3500    /// - dialect: Renderer under test.
3501    fn assert_sql_renderer_parameter_and_merge_defaults(dialect: &TestDialect) {
3502        assert_eq!(dialect.render_named_parameter("user_id"), ":user_id");
3503        assert_eq!(SqlRenderer::merge_not_matched_keyword(dialect), "WHEN NOT MATCHED");
3504        assert!(!SqlRenderer::supports_merge_not_matched_by_source(dialect));
3505        assert!(SqlRenderer::merge_supported(dialect));
3506        assert!(!SqlRenderer::merge_supports_schema_evolution_keyword(dialect));
3507        assert!(!SqlRenderer::merge_supports_table_hints(dialect));
3508        assert!(!SqlRenderer::merge_uses_output_clause(dialect));
3509        assert!(!SqlRenderer::merge_supports_returning_clause(dialect));
3510        assert_eq!(dialect.schema_separator(), ".");
3511        assert_eq!(dialect.statement_separator(), ";\n");
3512    }
3513
3514    /// Assert ANSI defaults for update and insert behavior extension hooks.
3515    /// Parameters:
3516    /// - dialect: Renderer under test.
3517    fn assert_sql_renderer_update_insert_defaults(dialect: &TestDialect) {
3518        assert!(!SqlRenderer::supports_compound_assignment(dialect));
3519        assert!(!SqlRenderer::supports_update_from(dialect));
3520        assert!(!SqlRenderer::uses_alter_table_update(dialect));
3521        // How a mutation is SPELLED and what it may LOOK AT are
3522        // two facts, and the ANSI default is that a mutation may correlate.
3523        assert!(!SqlRenderer::mutation_reads_only_its_own_rows(dialect));
3524        assert!(!dialect.update_supports_top_clause());
3525        assert!(!dialect.update_supports_table_hints());
3526        assert!(!dialect.update_uses_output_clause());
3527        assert!(!dialect.update_supports_returning());
3528        assert!(!dialect.insert_uses_output_clause());
3529        assert!(!dialect.insert_supports_returning());
3530        assert!(!dialect.insert_supports_table_hints());
3531        assert!(!SqlRenderer::supports_insert_overwrite(dialect));
3532        assert!(!SqlRenderer::supports_on_conflict(dialect));
3533        assert!(!dialect.supports_overriding_clause());
3534        assert_eq!(dialect.returning_clause_keyword(), "RETURNING");
3535        assert!(!SqlRenderer::supports_returning_into(dialect));
3536        assert!(!SqlRenderer::supports_multi_table_insert(dialect));
3537    }
3538
3539    /// Assert ANSI defaults for partitioning, generated columns, and window hooks.
3540    /// Parameters:
3541    /// - dialect: Renderer under test.
3542    fn assert_sql_renderer_partition_and_window_defaults(dialect: &TestDialect) {
3543        assert_eq!(
3544            dialect.render_time_partition_expr("event_ts", "monthly"),
3545            "date_trunc('month', event_ts)"
3546        );
3547        assert_eq!(dialect.render_partition_clause("event_ts"), "PARTITION BY (event_ts)");
3548        assert_eq!(
3549            dialect.render_generated_column("event_month", "DATE_TRUNC('month', event_ts)"),
3550            "event_month GENERATED ALWAYS AS (DATE_TRUNC('month', event_ts)) STORED"
3551        );
3552        assert!(!SqlRenderer::supports_window_frame_exclude(dialect));
3553        assert!(!SqlRenderer::supports_window_frame_groups(dialect));
3554        assert!(SqlRenderer::supports_named_windows(dialect));
3555    }
3556
3557    #[test]
3558    fn ddl_renderer_extensions_produce_ansi_defaults() {
3559        let d = TestDialect;
3560        assert_ddl_renderer_transaction_and_view_defaults(&d);
3561        assert_ddl_renderer_drop_defaults(&d);
3562    }
3563
3564    /// Assert ANSI DDL defaults for transaction and view keyword hooks.
3565    /// Parameters:
3566    /// - dialect: Renderer under test.
3567    fn assert_ddl_renderer_transaction_and_view_defaults(dialect: &TestDialect) {
3568        assert_eq!(dialect.render_begin_transaction(), "BEGIN");
3569        assert_eq!(dialect.render_commit(), "COMMIT");
3570        assert!(dialect.supports_ddl_transactions());
3571        assert_eq!(dialect.render_create_view_keyword(false, false), "CREATE OR REPLACE VIEW");
3572        assert_eq!(
3573            dialect.render_create_view_keyword(true, false),
3574            "CREATE OR REPLACE SECURE VIEW"
3575        );
3576        assert!(dialect.supports_alter_drop_if_exists());
3577    }
3578
3579    /// Assert ANSI DDL defaults for drop-related behavior hooks.
3580    /// Parameters:
3581    /// - dialect: Renderer under test.
3582    fn assert_ddl_renderer_drop_defaults(dialect: &TestDialect) {
3583        assert_eq!(dialect.render_drop_object_keyword("table"), "TABLE");
3584        assert_eq!(
3585            dialect.render_drop_function("my_func", &[], true),
3586            "DROP FUNCTION IF EXISTS my_func"
3587        );
3588        assert!(!dialect.drop_index_uses_on_syntax());
3589        assert!(DdlRenderer::supports_cascade_drop(dialect));
3590        assert!(dialect.supports_drop_function_arg_types());
3591        assert!(!DdlRenderer::supports_sync_drop(dialect));
3592        assert!(!DdlRenderer::supports_force_drop(dialect));
3593        assert!(!dialect.supports_alter_drop_cascade());
3594    }
3595
3596    /// **The one statement every engine takes**, written for a dialect that
3597    /// answered nothing. A plugin author gets the commonest evolution there is
3598    /// without writing a line for it.
3599    #[test]
3600    fn a_dialect_that_answers_nothing_still_adds_a_column() {
3601        let data_type = DataType::boolean();
3602        let written = TestDialect.render_column_evolution(&ColumnEvolution::AddColumn {
3603            schema: Some("telemetry_core"),
3604            table: "clients_daily",
3605            column: "is_default_browser",
3606            data_type: &data_type,
3607        });
3608        assert_eq!(
3609            written.as_deref(),
3610            Some(
3611                r#"ALTER TABLE "telemetry_core"."clients_daily" ADD COLUMN "is_default_browser" BOOLEAN"#
3612            )
3613        );
3614    }
3615
3616    /// Everything else waits to be spelled. A default picked from one engine's
3617    /// grammar is a statement the others reject, handed to an operator who
3618    /// finds that out against their own warehouse.
3619    #[test]
3620    fn nothing_else_is_spelled_until_a_dialect_spells_it() {
3621        let data_type = DataType::text();
3622        assert!(
3623            TestDialect
3624                .render_column_evolution(&ColumnEvolution::WidenColumn {
3625                    schema: None,
3626                    table: "t",
3627                    column: "c",
3628                    data_type: &data_type,
3629                })
3630                .is_none()
3631        );
3632        assert!(
3633            TestDialect
3634                .render_column_evolution(&ColumnEvolution::AddNestedField {
3635                    schema: None,
3636                    table: "t",
3637                    column: "c",
3638                    field: "f",
3639                    data_type: &data_type,
3640                })
3641                .is_none()
3642        );
3643    }
3644
3645    /// **The first line break decides how a file's lines end.** A header that
3646    /// ends `\r\n` is a Windows file; one that ends `\n`, or a file with no
3647    /// line break at all, reads with a newline alone.
3648    #[test]
3649    fn the_first_line_break_decides_how_the_lines_end() {
3650        assert_eq!(LineEnding::of(b"id,name\r\n1,Alpha\r\n"), LineEnding::CrLf);
3651        assert_eq!(LineEnding::of(b"id,name\r\n1,Alpha"), LineEnding::CrLf);
3652        assert_eq!(LineEnding::of(b"id,name\n1,Alpha\n"), LineEnding::Lf);
3653        assert_eq!(LineEnding::of(b"id,name"), LineEnding::Lf);
3654        assert_eq!(LineEnding::of(b""), LineEnding::Lf);
3655        assert_eq!(LineEnding::of(b"\n"), LineEnding::Lf);
3656        assert_eq!(LineEnding::default(), LineEnding::Lf);
3657        assert_eq!(LineEnding::CrLf.as_str(), "\r\n");
3658        assert_eq!(LineEnding::CrLf.escaped(), "\\r\\n");
3659        assert_eq!(LineEnding::Lf.escaped(), "\\n");
3660    }
3661}