Skip to main content

squonk_ast/dialect/
ansi.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Moderately AI Inc.
3
4//! The always-compiled ANSI/standard dialect preset.
5//!
6//! Other presets derive from this data without making the shared
7//! [`FeatureSet`]/[`KeywordSet`]/`ByteClasses` machinery dialect-specific.
8
9use super::keyword::{
10    POSTGRES_AS_LABEL_KEYWORDS, POSTGRES_COL_NAME_KEYWORDS, POSTGRES_RESERVED_KEYWORDS,
11    POSTGRES_TYPE_FUNC_NAME_KEYWORDS,
12};
13use super::{
14    AccessControlSyntax, AggregateCallSyntax, CallSyntax, CaretOperator, Casing,
15    ColumnDefinitionSyntax, CommentSyntax, ConstraintSyntax, CreateTableClauseSyntax,
16    DoubleAmpersand, ExistenceGuards, ExpressionSyntax, FeatureSet, GroupingSyntax,
17    IdentifierQuote, IdentifierSyntax, IndexAlterSyntax, JoinSyntax, Keyword, KeywordOperators,
18    KeywordSet, MaintenanceSyntax, MutationSyntax, NullOrdering, NumericLiteralSyntax,
19    OperatorSyntax, ParameterSyntax, PipeOperator, PredicateSyntax, QueryTailSyntax,
20    STANDARD_BYTE_CLASSES, SelectSyntax, SessionVariableSyntax, ShowSyntax, StatementDdlGates,
21    StringFuncForms, StringLiteralSyntax, TableExpressionSyntax, TableFactorSyntax, TargetSpelling,
22    TypeNameSyntax, UtilitySyntax,
23};
24use crate::precedence::{STANDARD_BINDING_POWERS, STANDARD_SET_OPERATION_BINDING_POWERS};
25
26/// Standard `"`-only identifier quoting shared by the ANSI and PostgreSQL presets.
27pub const STANDARD_IDENTIFIER_QUOTES: &[IdentifierQuote] = &[IdentifierQuote::Symmetric('"')];
28
29// --- Per-position reject sets (prod-keyword-position-reserved-sets) -----------
30//
31// PostgreSQL reserves keywords per grammatical position via the four `kwlist.h`
32// classes plus the `BARE_LABEL`/`AS_LABEL` axis; these compose the generated
33// category bitsets into the reject set each parser gate consults. They are sourced
34// from the PostgreSQL category model — the practical, libpg-query-validated one —
35// for *both* the ANSI/generic and PostgreSQL presets: the strict SQL:2016 reserved
36// list reserves `COUNT`/`COALESCE`/… as identifiers, which would reject the common
37// `count(*)` / `coalesce(a, b)` SQL every working dialect accepts, so the generic
38// dialect adopts PostgreSQL's position-aware categories (the differential oracle
39// confirms the agreement). A dialect wanting strict ANSI reservation overrides
40// these fields via a `FeatureDelta`. They live in the always-compiled ANSI module
41// because PostgreSQL reuses them verbatim, so they are baseline data, not a
42// PostgreSQL-gated cost.
43//
44// Source of truth (the `dialect-ref` citation convention — see
45// docs/dialect-references/manifest.toml): `postgres/kwlist` @
46// REL_18_BETA1-3053-g4b0bf0788b0, the four `kwlist.h` categories vendored at
47// docs/dialect-references/corpora/postgres/kwlist.h (pin matches
48// conformance/corpus/postgres). Cite the manifest id + pin, not a bare URL that
49// rots; a version bump there is a deliberate commit that re-cites here.
50
51/// `ColId` reject set (column/table name, FROM/correlation alias, qualifier): a
52/// keyword usable as a bare ColId is `unreserved ∪ col_name`, so this rejects
53/// `type_func_name ∪ reserved` (e.g. `JOIN`/`LEFT` cannot be a table alias).
54pub const RESERVED_COLUMN_NAME: KeywordSet =
55    POSTGRES_TYPE_FUNC_NAME_KEYWORDS.union(POSTGRES_RESERVED_KEYWORDS);
56
57/// Keywords PostgreSQL never admits as a generic `func_application` name, on top
58/// of the reserved set.
59///
60/// PostgreSQL's generic call name is `type_function_name` (`unreserved ∪
61/// type_func_name`); the `col_name` keywords are admitted as functions *only*
62/// through dedicated productions. Those productions split two ways:
63///
64/// - Some take an ordinary argument list (`coalesce(a, b)`, `greatest`, `least`,
65///   `xmlconcat`, `grouping`, `json`, `substring`, `overlay`, `trim`,
66///   `normalize`, the `json_object`/`json_array`/`json_scalar`/`json_serialize`
67///   builders, `xmlforest`), so parsing them as ordinary calls happens to agree
68///   with PostgreSQL — we keep admitting those.
69/// - The rest have non-generic argument syntax (`position(x IN y)`,
70///   `xmlelement(...)`, the `json_query`/`json_value`/… builders, `treat(x AS
71///   ty)`), are the bare type spellings used only in a type position (`int`,
72///   `bit`, `numeric`, `interval`, …), or are keywords with no call form at all
73///   (`values`, `between`, `setof`, `inout`/`out`, `merge_action`, `operator`,
74///   `none`). PostgreSQL rejects `kw(1)` for every one of these; admitting them
75///   as ordinary calls is exactly the divergence the keyword-position oracle
76///   pinned, so they are reserved here. `nullif` is *not* listed: its dedicated
77///   `NULLIF(a, b)` production is enforced by a parser arity check instead, so
78///   the valid two-argument form keeps parsing.
79pub const POSTGRES_NON_GENERIC_FUNCTION_KEYWORDS: KeywordSet = KeywordSet::from_keywords(&[
80    Keyword::Between,
81    Keyword::Bigint,
82    Keyword::Bit,
83    Keyword::Boolean,
84    Keyword::Char,
85    Keyword::Character,
86    Keyword::Dec,
87    Keyword::Decimal,
88    Keyword::Float,
89    Keyword::Inout,
90    Keyword::Int,
91    Keyword::Integer,
92    Keyword::Interval,
93    Keyword::JsonExists,
94    Keyword::JsonObjectagg,
95    Keyword::JsonQuery,
96    Keyword::JsonTable,
97    Keyword::JsonValue,
98    Keyword::MergeAction,
99    Keyword::National,
100    Keyword::Nchar,
101    Keyword::None,
102    Keyword::Numeric,
103    Keyword::Operator,
104    Keyword::Out,
105    Keyword::Position,
106    Keyword::Precision,
107    Keyword::Real,
108    Keyword::Setof,
109    Keyword::Smallint,
110    Keyword::Time,
111    Keyword::Timestamp,
112    Keyword::Treat,
113    Keyword::Values,
114    Keyword::Varchar,
115    Keyword::Xmlattributes,
116    Keyword::Xmlelement,
117    Keyword::Xmlexists,
118    Keyword::Xmlnamespaces,
119    Keyword::Xmlparse,
120    Keyword::Xmlpi,
121    Keyword::Xmlroot,
122    Keyword::Xmlserialize,
123    Keyword::Xmltable,
124]);
125
126/// Function-name reject set: `reserved ∪` the `col_name`/special keywords that
127/// have no generic call form (see `POSTGRES_NON_GENERIC_FUNCTION_KEYWORDS`).
128/// PostgreSQL's generic `func_application` admits only `type_function_name`; the
129/// `col_name` functions with an ordinary argument list (`coalesce`, …) still parse
130/// because they are *not* in the reject set, while the bare type spellings and
131/// non-generic-syntax builders are rejected here to match PostgreSQL.
132pub const RESERVED_FUNCTION_NAME: KeywordSet =
133    POSTGRES_RESERVED_KEYWORDS.union(POSTGRES_NON_GENERIC_FUNCTION_KEYWORDS);
134
135/// Type-name reject set (`type_function_name`): a keyword usable as a type name is
136/// `unreserved ∪ type_func_name`, so this rejects `col_name ∪ reserved` — matching
137/// PostgreSQL, which rejects `CAST(x AS coalesce)` (`coalesce` is `col_name`).
138/// Built-in type spellings are matched contextually before any gate, so this only
139/// governs user-defined type names.
140pub const RESERVED_TYPE_NAME: KeywordSet =
141    POSTGRES_COL_NAME_KEYWORDS.union(POSTGRES_RESERVED_KEYWORDS);
142
143/// Bare-label (`BareColLabel`) reject set: the `AS_LABEL` keywords, which cannot be
144/// a column alias without `AS`. This is the axis behind `SELECT a over` /
145/// `SELECT a filter` rejecting while `SELECT a select` accepts.
146pub const RESERVED_BARE_ALIAS: KeywordSet = POSTGRES_AS_LABEL_KEYWORDS;
147
148impl CommentSyntax {
149    /// The `ANSI` predefined value.
150    pub const ANSI: Self = Self {
151        line_comment_hash: false,
152        // `\n`-only line-comment termination is the strict SQL-standard baseline and the
153        // pre-existing behaviour of every preset; PostgreSQL/DuckDB widen it to `\r` too
154        // (`CommentSyntax::POSTGRES`), SQLite/MySQL keep it off.
155        line_comment_ends_at_carriage_return: false,
156        nested_block_comments: true,
157        versioned_comments: None,
158        // The strict baseline: an unterminated `/* …` running to EOF is a hard error
159        // everywhere but SQLite (`CommentSyntax::SQLITE`).
160        unterminated_block_comment_at_eof: false,
161    };
162}
163
164impl StringLiteralSyntax {
165    /// The `ANSI` predefined value.
166    pub const ANSI: Self = Self {
167        escape_strings: false,
168        dollar_quoted_strings: false,
169        national_strings: false,
170        double_quoted_strings: false,
171        backslash_escapes: false,
172        unicode_strings: false,
173        bit_string_literals: false,
174        blob_literals: false,
175        charset_introducers: false,
176        // The standard requires a newline in the separator between adjacent literals.
177        same_line_adjacent_concat: false,
178    };
179}
180
181impl NumericLiteralSyntax {
182    /// The `ANSI` predefined value.
183    pub const ANSI: Self = Self {
184        hex_integers: false,
185        octal_integers: false,
186        binary_integers: false,
187        underscore_separators: false,
188        radix_leading_underscore: false,
189        money_literals: false,
190        reject_trailing_junk: false,
191    };
192}
193
194impl ParameterSyntax {
195    /// The `ANSI` predefined value.
196    pub const ANSI: Self = Self {
197        positional_dollar: false,
198        anonymous_question: false,
199        named_colon: false,
200        named_at: false,
201        named_dollar: false,
202        numbered_question: false,
203    };
204}
205
206impl SessionVariableSyntax {
207    /// The `ANSI` predefined value.
208    pub const ANSI: Self = Self {
209        user_variables: false,
210        system_variables: false,
211        variable_assignment: false,
212    };
213}
214
215impl IdentifierSyntax {
216    /// The `ANSI` predefined value.
217    pub const ANSI: Self = Self {
218        dollar_in_identifiers: false,
219        string_literal_identifiers: false,
220        empty_quoted_identifiers: false,
221    };
222}
223
224impl TableExpressionSyntax {
225    /// The `ANSI` predefined value.
226    pub const ANSI: Self = Self {
227        only: false,
228        table_sample: false,
229        parenthesized_joins: true,
230        table_alias_column_lists: true,
231        join_using_alias: false,
232        // MySQL-only table-factor tails.
233        index_hints: false,
234        // MSSQL-only `WITH (...)` table hints.
235        table_hints: false,
236        partition_selection: false,
237        base_table_alias_column_lists: true,
238        // DuckDB-only string-literal table alias (`FROM t AS 't'('k')`).
239        string_literal_aliases: false,
240        aliased_parenthesized_join: true,
241        // The standard bare table alias is a `ColId`, same as the table name (SQLite is the
242        // outlier whose bare alias is the narrower `ids` class that reserves JOIN keywords).
243        bare_table_alias_is_bare_label: false,
244        // Version / time-travel modifiers are BigQuery/MSSQL/Databricks extensions.
245        table_version: false,
246        // PartiQL / SUPER table-position JSON paths are Redshift/Snowflake extensions.
247        table_json_path: false,
248        // SQLite's `INDEXED BY` / `NOT INDEXED` index directive is a SQLite extension.
249        indexed_by: false,
250    };
251}
252
253impl JoinSyntax {
254    /// The `ANSI` predefined value.
255    pub const ANSI: Self = Self {
256        // Standard SQL right-nests stacked join qualifiers (`a JOIN b JOIN c ON p ON q`).
257        stacked_join_qualifiers: true,
258        full_outer_join: true,
259        // SQLite-only `NATURAL CROSS JOIN` (PostgreSQL/DuckDB parse-reject it).
260        natural_cross_join: false,
261        straight_join: false,
262        // DuckDB-only nonstandard joins.
263        asof_join: false,
264        positional_join: false,
265        semi_anti_join: false,
266        sided_semi_anti_join: false,
267        apply_join: false,
268        // The SQL:2023 recursive-query SEARCH/CYCLE clauses stay off in this conservative
269        // ANSI-ish baseline (like `unnest`); PostgreSQL/Lenient enable them.
270        recursive_search_cycle: false,
271        // DuckDB-only parse restriction on a `UNION`-bodied recursive CTE's ORDER BY/LIMIT.
272        recursive_union_rejects_order_limit: false,
273        // DuckDB-only `USING KEY` recursive-CTE key clause; off in this baseline.
274        recursive_using_key: false,
275    };
276}
277
278impl TableFactorSyntax {
279    /// The `ANSI` predefined value.
280    pub const ANSI: Self = Self {
281        lateral: false,
282        table_functions: false,
283        rows_from: false,
284        // UNNEST is SQL-standard, but this ANSI-ish baseline keeps the table-function
285        // surface off (like `table_functions`), so `FROM unnest(…)` is a clean reject.
286        unnest: false,
287        unnest_with_offset: false,
288        table_function_ordinality: false,
289        // The standard admits a special value function as a `FROM` source and an alias on a
290        // parenthesized join (MySQL is the outlier that rejects both).
291        special_function_table_source: true,
292        // DuckDB-only PIVOT/UNPIVOT operators.
293        pivot: false,
294        unpivot: false,
295        // DuckDB-only DESCRIBE/SHOW/SUMMARIZE table source.
296        show_ref: false,
297        // DuckDB-only bare `FROM VALUES (…) AS t` row-list table factor.
298        from_values: false,
299        // JSON_TABLE / XMLTABLE table factors are PostgreSQL/Lenient-only; off here.
300        json_table: false,
301        xml_table: false,
302        // `TABLE(<expr>)` is a Lenient-only factor (no oracle-backed preset ships it);
303        // off in this conservative baseline.
304        table_expr_factor: false,
305        // The standard PIVOT's extended value sources / `DEFAULT ON NULL` are a
306        // BigQuery/Snowflake/Lenient form; off in this conservative baseline.
307        pivot_value_sources: false,
308        // MATCH_RECOGNIZE is a Snowflake/Oracle form; off in this conservative baseline.
309        match_recognize: false,
310        // OPENJSON is a SQL Server form; off in this conservative baseline.
311        open_json: false,
312    };
313}
314
315impl MutationSyntax {
316    /// The `ANSI` predefined value.
317    pub const ANSI: Self = Self {
318        returning: false,
319        on_conflict: false,
320        on_duplicate_key_update: false,
321        multi_column_assignment: false,
322        update_tuple_value_row_arity: false,
323        where_current_of: false,
324        merge: true,
325        // The MySQL `REPLACE` statement and `INSERT ... SET` source are dialect
326        // extensions, not standard surface, so the ANSI baseline rejects both.
327        replace_into: false,
328        insert_set: false,
329        // The MySQL `UPDATE`/`DELETE ... ORDER BY ... LIMIT` tails are dialect
330        // extensions; standard SQL row-limits neither statement.
331        update_delete_tails: false,
332        // The SQLite `INSERT OR`/`UPDATE OR <action>` conflict prefix is a SQLite
333        // extension; standard SQL has no such verb-level conflict resolution.
334        or_conflict_action: false,
335        insert_column_matching: false,
336        delete_using: true,
337        update_from: true,
338        // The standard admits an alias on a `DELETE … USING` target and a leading `WITH`
339        // before `INSERT` (MySQL rejects both).
340        delete_using_target_alias: true,
341        cte_before_insert: true,
342        // SQL:2016's `<merge statement>` takes no `<with clause>` (unlike `INSERT`,
343        // whose source query carries one), so a leading `WITH` before `MERGE` is a
344        // PostgreSQL/DuckDB extension the ANSI baseline rejects.
345        cte_before_merge: false,
346        // The standard's `<with clause>` bodies are query expressions only; the
347        // data-modifying CTE is a PostgreSQL extension.
348        data_modifying_ctes: false,
349        // SQL:2016's `<merge when clause>` is only `MATCHED`/`NOT MATCHED` and its
350        // `<merge insert specification>` has no `DEFAULT VALUES` alternative, so both
351        // are PostgreSQL/DuckDB extensions the ANSI baseline rejects.
352        merge_when_not_matched_by: false,
353        merge_insert_default_values: false,
354        // The `<override clause>` on a merge insert *is* SQL:2016 standard surface, so
355        // the ANSI baseline accepts it (DuckDB is the outlier that rejects it).
356        merge_insert_overriding: true,
357        merge_update_set_star: false,
358        merge_insert_star_by_name: false,
359        merge_error_action: false,
360        update_set_qualified_column: true,
361    };
362}
363
364impl StatementDdlGates {
365    /// The `ANSI` predefined value.
366    pub const ANSI: Self = Self {
367        // `CREATE TRIGGER`'s only modelled body form is SQLite's, so the standard
368        // baseline does not dispatch it.
369        create_trigger: false,
370        // The macro DDL is DuckDB-only; the standard baseline does not dispatch it.
371        create_macro: false,
372        create_secret: false,
373        // The user-defined-type DDL (`CREATE TYPE`/`DROP TYPE`) is a DuckDB extension here.
374        create_type: false,
375        // Virtual tables are a SQLite-only concept; the standard baseline does not dispatch
376        // `CREATE VIRTUAL TABLE`.
377        create_virtual_table: false,
378        // T176 sequence generators are an *optional* standard feature modelled via the
379        // PostgreSQL/DuckDB presets; the bare-standard baseline does not dispatch `SEQUENCE`.
380        create_sequence: false,
381        extension_ddl: false,
382        transform_ddl: false,
383        alter_system: false,
384        // MySQL's tablespace / logfile-group storage DDL has no ANSI equivalent (MySQL turns
385        // both on; MySQL derives from this preset).
386        tablespace_ddl: false,
387        logfile_group_ddl: false,
388        schemas: true,
389        // ANSI accepts the `CREATE SCHEMA` head but not the embedded-element form here:
390        // the standard embedding is validated only against the PostgreSQL oracle, so it
391        // is gated to PostgreSQL/Lenient rather than widening the reference dialect's
392        // accept surface without a differential; a trailing `CREATE`/`GRANT` stays a
393        // separate top-level statement under ANSI.
394        schema_elements: false,
395        databases: true,
396        // ANSI has no MySQL `DROP DATABASE`/`DROP SCHEMA` single-name synonym drop.
397        drop_database: false,
398        materialized_views: true,
399        temporary_views: true,
400        routines: true,
401        or_replace: true,
402        // `CREATE RECURSIVE VIEW` is gated to DuckDB/Lenient; the standard baseline
403        // leaves `RECURSIVE` unconsumed before the expected `VIEW`.
404        recursive_views: false,
405        // The standard baseline has no MySQL-style compound-statement routine body.
406        compound_statements: false,
407        alter_database: false,
408        alter_database_options: false,
409        server_definition: false,
410        alter_instance: false,
411        spatial_reference_system: false,
412        resource_group: false,
413        alter_sequence: false,
414        alter_object_set_schema: false,
415        view_definition_options: false,
416    };
417}
418
419impl CreateTableClauseSyntax {
420    /// The `ANSI` predefined value.
421    pub const ANSI: Self = Self {
422        table_options: false,
423        // The SQLite trailing `WITHOUT ROWID` table option is a dialect extension, not
424        // standard surface, so the baseline rejects it.
425        without_rowid_table_option: false,
426        // The SQLite trailing `STRICT` table option is a dialect extension, not standard
427        // surface, so the baseline rejects it.
428        strict_table_option: false,
429        // `OR REPLACE TABLE` and `CREATE SECRET` are DuckDB-only extensions.
430        create_or_replace_table: false,
431        storage_parameters: true,
432        on_commit: true,
433        create_table_as_with_data: true,
434        create_table_as_execute: false,
435        // Declarative partitioning is a PostgreSQL extension, not standard SQL.
436        declarative_partitioning: false,
437        // Table inheritance and the LIKE source-table element are PostgreSQL extensions;
438        // the statement-level `CREATE TABLE t LIKE src` is a MySQL extension.
439        table_inheritance: false,
440        like_source_table: false,
441        statement_level_table_like: false,
442        unlogged_tables: false,
443        table_access_method: false,
444        without_oids: false,
445        typed_tables: false,
446    };
447}
448
449impl ColumnDefinitionSyntax {
450    /// The `ANSI` predefined value.
451    pub const ANSI: Self = Self {
452        // The keywordless generated-column `AS (…)` shorthand and the SQLite `CREATE
453        // TABLE` decorations are dialect extensions, not standard surface.
454        generated_column_shorthand: false,
455        // The SQLite column-level `ON CONFLICT <resolution>` clause is a dialect
456        // extension, not standard surface, so the baseline rejects it.
457        column_conflict_resolution_clause: false,
458        // A typeless column is a SQLite extension; the standard baseline requires a type,
459        // so a column with no type is a clean parse error.
460        typeless_column_definitions: false,
461        // DuckDB's type-optional-for-generated-columns narrowing is a dialect extension; the
462        // standard baseline requires a type on every column, generated or not.
463        typeless_generated_columns: false,
464        // The SQLite joined `AUTOINCREMENT` attribute is a dialect extension, not standard
465        // surface, so the baseline rejects it.
466        joined_autoincrement_attribute: false,
467        // An `ASC`/`DESC` order on an inline `PRIMARY KEY` is a SQLite extension; the standard
468        // baseline leaves the trailing keyword unconsumed and rejects it.
469        inline_primary_key_ordering: false,
470        // A `CONSTRAINT <name>` prefix on a column `COLLATE` is a SQLite extension; the standard
471        // baseline has no column COLLATE at all, so the named wrapper is off.
472        named_column_collate_constraint: false,
473        identity_columns: true,
474        // The standard accepts a bare (unparenthesized) expression default and a
475        // `CONSTRAINT <name>` prefix on any inline column constraint (MySQL restricts both).
476        default_expression_requires_parens: false,
477        column_default_requires_b_expr: false,
478        // Column COLLATE, UNLOGGED, column STORAGE/COMPRESSION, the table USING access method,
479        // legacy WITHOUT OIDS, and typed `OF <type>` tables are all dialect extensions absent
480        // from standard SQL.
481        column_collation: false,
482        column_storage: false,
483    };
484}
485
486impl ConstraintSyntax {
487    /// The `ANSI` predefined value.
488    pub const ANSI: Self = Self {
489        deferrable_constraints: true,
490        named_inline_non_check_constraints: true,
491        // The standard requires a constraint element after `CONSTRAINT <name>` (SQLite only
492        // makes it optional).
493        bare_constraint_name: false,
494        exclusion_constraints: false,
495        constraint_no_inherit_not_valid: false,
496        index_constraint_parameters: false,
497        constraint_column_collate_order: false,
498        referential_action_cascade_set: true,
499        check_constraint_subqueries: true,
500    };
501}
502
503impl IndexAlterSyntax {
504    /// The `ANSI` predefined value.
505    pub const ANSI: Self = Self {
506        drop_behavior: true,
507        // ANSI has no MySQL `DROP INDEX … ON <table>` form.
508        index_drop_on_table: false,
509        index_concurrently: false,
510        index_using_method: false,
511        partial_index: false,
512        index_if_not_exists: true,
513        index_nulls_order: true,
514        alter_table_extended: true,
515        alter_nested_column_paths: false,
516        alter_existence_guards: true,
517        alter_column_set_data_type: true,
518        routine_arg_types: true,
519        routine_arg_defaults: true,
520        routine_arg_modes: true,
521        // The SQL-standard `<language name>` is a bare identifier, not a string constant.
522        routine_language_string: false,
523        alter_table_multiple_actions: true,
524    };
525}
526
527impl ExistenceGuards {
528    /// The `ANSI` predefined value.
529    pub const ANSI: Self = Self {
530        if_exists: false,
531        view_if_not_exists: false,
532        create_database_if_not_exists: false,
533    };
534}
535
536impl SelectSyntax {
537    /// The `ANSI` predefined value.
538    pub const ANSI: Self = Self {
539        distinct_on: false,
540        select_into: false,
541        // Standard SQL requires at least one select item, so a bare `SELECT` is rejected.
542        empty_target_list: false,
543        // `QUALIFY` is a DuckDB (Teradata-origin) extension, not standard SQL.
544        qualify: false,
545        // A string literal as a column alias is a MySQL extension; the standard requires
546        // an identifier.
547        alias_string_literals: false,
548        bare_alias_string_literals: false,
549        // `UNION [ALL] BY NAME` is a DuckDB name-matched set operation, not standard
550        // SQL; `BY` after a set operator is a syntax error here.
551        union_by_name: false,
552        wildcard_modifiers: false,
553        // The standard's qualified asterisk is a non-aliasable `<all fields reference>`; a
554        // trailing alias after `t.*` rejects (the ANSI-derived presets inherit this).
555        qualified_wildcard_alias: false,
556        // FROM-first SELECT (`FROM t SELECT x`, bare `FROM t`) is a DuckDB extension; a
557        // leading `FROM` is never a statement start in standard SQL.
558        from_first: false,
559        // Standard SQL admits a parenthesized query as a compound operand
560        // (`(SELECT …) UNION (SELECT …)`, PostgreSQL `select_with_parens`).
561        parenthesized_query_operands: true,
562        // A ragged VALUES constructor is a DuckDB parse-time reject; the ANSI baseline
563        // leaves the arity check to bind, so it accepts one at parse (no oracle forces the
564        // strict-baseline reject, and keeping it off makes this a clean DuckDB-only delta).
565        values_rows_require_equal_arity: false,
566        // The standard query-position VALUES constructor is bare-parenthesized rows.
567        values_row_constructor: true,
568        // Standard SQL's `AS` projection alias is a `ColLabel` admitting reserved words;
569        // only MySQL rejects them there.
570        as_alias_rejects_reserved: false,
571        // A trailing comma in a list is a DuckDB tolerance; standard SQL rejects the
572        // dangling comma.
573        trailing_comma: false,
574        // DuckDB's prefix colon alias (`SELECT j : 42`, `FROM b : a`) is not standard SQL;
575        // a `:` at a select-item / table-factor head is a parse error here.
576        prefix_colon_alias: false,
577        // Hive/Spark `LATERAL VIEW` is not standard SQL; a post-FROM `LATERAL` is a
578        // parse error here.
579        lateral_view_clause: false,
580        // The Oracle-style `START WITH`/`CONNECT BY` hierarchical query clause is not
581        // standard SQL; a post-WHERE `CONNECT BY`/`START WITH` is a parse error here.
582        connect_by_clause: false,
583    };
584}
585
586impl QueryTailSyntax {
587    /// The `ANSI` predefined value.
588    pub const ANSI: Self = Self {
589        fetch_first: true,
590        limit_offset_comma: false,
591        // A query-tail row-locking clause (`FOR UPDATE`/`FOR SHARE`) is a
592        // PostgreSQL/MySQL extension, not a standard SQL query clause.
593        locking_clauses: false,
594        // The PostgreSQL-only strength refinements and stacked clauses are likewise
595        // non-standard; with no base locking clause they never lead here.
596        key_lock_strengths: false,
597        stacked_locking_clauses: false,
598        using_sample: false,
599        leading_offset: true,
600        limit_expressions: true,
601        limit_percent: false,
602        with_ties_requires_order_by: false,
603        // BigQuery/ZetaSQL `|>` pipe syntax is not standard SQL; a `|>` after a query is a
604        // parse error here (and the `|>` token never lexes with the gate off).
605        pipe_syntax: false,
606        // ClickHouse `LIMIT n BY …` is not standard SQL; a `BY` after `LIMIT` is a
607        // parse error here.
608        limit_by_clause: false,
609        // ClickHouse `SETTINGS …` is not standard SQL; a trailing `SETTINGS` is a parse
610        // error here.
611        settings_clause: false,
612        // ClickHouse `FORMAT …` is not standard SQL; a trailing `FORMAT` is a parse error
613        // here.
614        format_clause: false,
615        // MSSQL `FOR XML`/`FOR JSON` is not standard SQL; a trailing `FOR XML`/`FOR JSON`
616        // is a parse error here.
617        for_xml_json_clause: false,
618    };
619}
620
621impl GroupingSyntax {
622    /// The `ANSI` predefined value.
623    pub const ANSI: Self = Self {
624        grouping_sets: true,
625        // The trailing `WITH ROLLUP` is a MySQL-only spelling; standard SQL writes the
626        // super-aggregate as the `ROLLUP (…)` grouping set above.
627        with_rollup: false,
628        // Standard SQL sorts only by `ASC`/`DESC`; the operator-driven `USING` sort is
629        // a PostgreSQL extension.
630        order_by_using: false,
631        // `GROUP BY ALL` / `ORDER BY ALL` are DuckDB clause modes, not standard SQL;
632        // `ALL` after either keyword pair is a parse error here (the word is reserved).
633        group_by_all: false,
634        group_by_set_quantifier: false,
635        order_by_all: false,
636    };
637}
638
639impl UtilitySyntax {
640    /// The `ANSI` predefined value.
641    pub const ANSI: Self = Self {
642        copy: false,
643        // `COPY INTO` is Snowflake-specific bulk load/unload — not standard SQL, so the
644        // ANSI baseline leaves the surface off (MySQL and the other ANSI-derived presets
645        // inherit it off).
646        copy_into: false,
647        stage_references: false,
648        comment_on: false,
649        pragma: false,
650        attach: false,
651        kill: false,
652        // MySQL's `HANDLER` low-level cursor family: no ANSI equivalent, so off at the
653        // baseline (MySQL turns it on; MySQL derives from this preset).
654        handler_statements: false,
655        // MySQL's `INSTALL`/`UNINSTALL` `PLUGIN`/`COMPONENT` family: no ANSI equivalent, so off
656        // at the baseline (MySQL turns it on; MySQL derives from this preset).
657        plugin_component_statements: false,
658        // MySQL's server-administration families (SHUTDOWN/RESTART/CLONE/IMPORT TABLE/HELP/
659        // BINLOG): no ANSI equivalent, so off at the baseline (MySQL turns each on).
660        shutdown: false,
661        restart: false,
662        clone: false,
663        import_table: false,
664        help_statement: false,
665        binlog: false,
666        // MySQL's `CACHE INDEX` / `LOAD INDEX INTO CACHE` MyISAM key-cache pair: no ANSI
667        // equivalent, so off at the baseline (MySQL turns it on; MySQL derives from here).
668        key_cache_statements: false,
669        use_statement: false,
670        use_qualified_name: false,
671        // DuckDB's `PREPARE`/`EXECUTE`/`DEALLOCATE` and `CALL` statements are not standard
672        // SQL, so the ANSI baseline dispatches neither (and MySQL inherits both off).
673        prepared_statements: false,
674        // The PostgreSQL typed parameter list is a widening of `PREPARE`, which is itself
675        // off here; the ANSI baseline leaves it off too (and MySQL inherits it off).
676        prepare_typed_parameters: false,
677        // MySQL's `PREPARE ... FROM` / `EXECUTE ... USING` / `{DEALLOCATE | DROP} PREPARE`
678        // lifecycle: no ANSI equivalent, so off at the baseline (MySQL turns it on).
679        prepared_statements_from: false,
680        call: false,
681        // The `CALL` statement itself is off at the baseline, so its MySQL bare-name widening
682        // is off too (and every ANSI-derived preset inherits it off).
683        call_bare_name: false,
684        load_extension: false,
685        load_bare_name: false,
686        load_data: false,
687        reset_scope: false,
688        detach_if_exists: false,
689        // `DO` is the PostgreSQL anonymous code block — not standard SQL, so the ANSI
690        // baseline leaves the leading keyword undispatched.
691        do_statement: false,
692        // MySQL's `DO <expr-list>` evaluate-and-discard statement — MySQL-only, so the ANSI
693        // baseline leaves it off (and every ANSI-derived preset inherits it off).
694        do_expression_list: false,
695        // MySQL's `LOCK/UNLOCK {TABLES|TABLE}` per-table locking and its
696        // `LOCK INSTANCE FOR BACKUP`/`UNLOCK INSTANCE` backup-lock pair — MySQL-only, so
697        // the ANSI baseline leaves both leading keywords undispatched.
698        lock_tables: false,
699        lock_instance: false,
700        // SQLite's `BEGIN {DEFERRED|IMMEDIATE|EXCLUSIVE}` modifier is not standard SQL
701        // (the standard/PostgreSQL `BEGIN` takes its own `TransactionMode` list instead),
702        // so the ANSI baseline leaves the modifier keyword unrecognized and it falls
703        // through to the existing `BEGIN`-body error.
704        begin_transaction_mode: false,
705        // MySQL's `XA` distributed-transaction family is MySQL-only, so the ANSI baseline
706        // leaves the leading `XA` keyword undispatched (every ANSI-derived preset inherits
707        // it off unless it opts back in).
708        xa_transactions: false,
709        // The standalone `RENAME TABLE`/`RENAME USER` statements are MySQL-only, so the
710        // ANSI baseline does not dispatch the leading `RENAME` keyword.
711        rename_statement: false,
712        signal_diagnostics: false,
713        // `EXPORT`/`IMPORT DATABASE` are DuckDB catalogue-dump statements, not standard
714        // SQL, so the ANSI baseline leaves both leading keywords undispatched (MySQL,
715        // Snowflake, and Databricks inherit the pair off).
716        export_import_database: false,
717        // `UPDATE EXTENSIONS` is DuckDB extension management, not standard SQL, so the ANSI
718        // baseline never takes the `EXTENSIONS` lookahead and every `UPDATE` reaches the DML
719        // parser (MySQL, Snowflake, and Databricks inherit it off).
720        update_extensions: false,
721        // MySQL's `FLUSH` / `PURGE BINARY LOGS` server-administration statements — leading
722        // keyword gates off in the ANSI baseline (only MySQL/Lenient arm them).
723        flush: false,
724        purge_binary_logs: false,
725        replication_statements: false,
726    };
727}
728
729impl ShowSyntax {
730    /// The `ANSI` predefined value.
731    pub const ANSI: Self = Self {
732        describe: false,
733        describe_summarize: false,
734        session_statements: true,
735        show_tables: false,
736        show_columns: false,
737        show_create_table: false,
738        show_functions: false,
739        show_routine_status: false,
740        show_verbose: false,
741        show_admin: false,
742    };
743}
744
745impl MaintenanceSyntax {
746    /// The `ANSI` predefined value.
747    pub const ANSI: Self = Self {
748        vacuum: false,
749        vacuum_analyze: false,
750        reindex: false,
751        analyze: false,
752        analyze_columns: false,
753        // `CHECKPOINT`/`LOAD` are PostgreSQL/DuckDB utility statements and the DuckDB
754        // `RESET`-scope / `DETACH … IF EXISTS` extensions are DuckDB-only — none is
755        // standard SQL, so the ANSI baseline dispatches/accepts none.
756        checkpoint: false,
757        checkpoint_database: false,
758        // The MySQL admin-table verbs (`ANALYZE/CHECK/CHECKSUM/OPTIMIZE/REPAIR TABLE`) are
759        // MySQL-only, so the ANSI baseline dispatches none.
760        table_maintenance: false,
761    };
762}
763
764impl AccessControlSyntax {
765    /// The `ANSI` predefined value.
766    pub const ANSI: Self = Self {
767        access_control: true,
768        // The standard admits the schema-scoped grant objects and the `OPTION FOR` prefix.
769        access_control_extended_objects: true,
770        // The standard has no MySQL account-management DDL (`CREATE USER`, `CREATE ROLE`, …).
771        user_role_management: false,
772        // The standard uses the typed-object/role-spec grant grammar, not MySQL's account-based one.
773        access_control_account_grants: false,
774    };
775}
776
777impl TypeNameSyntax {
778    /// The `ANSI` predefined value.
779    pub const ANSI: Self = Self {
780        extended_scalar_type_names: false,
781        enum_type: false,
782        set_type: false,
783        numeric_modifiers: false,
784        integer_display_width: false,
785        composite_types: false,
786        // The standard accepts a length-less `VARCHAR` and the zoned temporal types
787        // (`TIMESTAMPTZ`, `WITH TIME ZONE`); MySQL requires the length and has no zoned type.
788        varchar_requires_length: false,
789        zoned_temporal_types: true,
790        // Empty `DECIMAL()` parens are a DuckDB spelling; the standard rejects them
791        // (`pg_query` rejects `DECIMAL()`).
792        empty_type_parens: false,
793        // MySQL's `CHARACTER SET`/`ASCII`/`UNICODE`/`BYTE`/`BINARY` type annotation; the
794        // standard/PostgreSQL reject it (`pg_query` syntax-errors `CHAR(5) CHARACTER SET x`).
795        character_set_annotation: false,
796        // A signed `numeric`/`decimal` precision/scale is a PostgreSQL raw-parse laxity; the
797        // standard requires an unsigned modifier.
798        signed_type_modifier: false,
799        // ClickHouse's `Nullable(T)` combinator has no differential oracle; the standard
800        // has no such type (its head resolves to a user-defined name here).
801        nullable_type: false,
802        // ClickHouse's `LowCardinality(T)` combinator likewise has no differential
803        // oracle; the standard has no such type.
804        low_cardinality_type: false,
805        // ClickHouse's `FixedString(N)` constructor likewise has no differential oracle;
806        // the standard has no such type.
807        fixed_string_type: false,
808        // ClickHouse's `DateTime64(P[, 'tz'])` constructor likewise has no differential
809        // oracle; the standard has no such type.
810        datetime64_type: false,
811        // ClickHouse's `Nested(name Type, ...)` composite likewise has no differential
812        // oracle; the standard has no such type.
813        nested_type: false,
814        // ClickHouse's `Int8`…`Int256`/`UInt*` fixed-bit-width integer names likewise have no
815        // differential oracle; the standard reads them as user-defined type names.
816        bit_width_integer_names: false,
817        // SQLite's liberal multi-word / two-argument affinity type name; the standard has a
818        // closed type vocabulary, so `LONG INTEGER` / `VARCHAR(123,456)` are parse errors
819        // (`pg_query` rejects both).
820        liberal_type_names: false,
821        string_type_modifiers: false,
822        angle_bracket_types: false,
823    };
824}
825
826impl ExpressionSyntax {
827    /// The `ANSI` predefined value.
828    pub const ANSI: Self = Self {
829        typecast_operator: false,
830        subscript: false,
831        // DuckDB's three-bound `[lower:upper:step]` slice is a dialect extension.
832        slice_step: false,
833        collate: false,
834        at_time_zone: false,
835        semi_structured_access: false,
836        array_constructor: false,
837        multidim_array_literals: false,
838        // The DuckDB `[…]`/`{…}`/`MAP` collection literals are a dialect extension.
839        collection_literals: false,
840        row_constructor: false,
841        // BigQuery's `STRUCT(...)` value constructor is a dialect extension.
842        struct_constructor: false,
843        field_selection: false,
844        field_wildcard: false,
845        typed_string_literals: true,
846        // The ANSI prefix-typed interval literal (`INTERVAL '1' HOUR TO SECOND`).
847        typed_interval_literal: true,
848        // DuckDB's relaxed interval spellings are a dialect extension.
849        relaxed_interval_syntax: false,
850        mysql_interval_operator: false,
851        // DuckDB's `#n` positional column reference is a dialect extension.
852        positional_column: false,
853        lambda_keyword: false,
854    };
855}
856
857impl OperatorSyntax {
858    /// The `ANSI` predefined value.
859    pub const ANSI: Self = Self {
860        operator_construct: false,
861        containment_operators: false,
862        json_arrow_operators: false,
863        jsonb_operators: false,
864        double_equals: false,
865        // DuckDB-only `//` spelling.
866        integer_divide_slash: false,
867        starts_with_operator: false,
868        is_general_equality: false,
869        // Truth-value tests `IS [NOT] {TRUE|FALSE|UNKNOWN}` are standard SQL (F571).
870        truth_value_tests: true,
871        // `<=>` is MySQL-only.
872        null_safe_equals: false,
873        // The single-arrow lambda is DuckDB-only (and `->` does not even lex here).
874        lambda_expressions: false,
875        // Bitwise `| & ~ << >>` are a shared PostgreSQL/MySQL/SQLite/DuckDB extension, not
876        // standard SQL, so the ANSI baseline rejects them.
877        bitwise_operators: false,
878        quantified_comparisons: true,
879        quantified_comparison_lists: false,
880        // The standard quantifier admits only the comparison operators; the any-operator
881        // extension is PostgreSQL's. MySQL/SQLite inherit this `false`.
882        quantified_arbitrary_operator: false,
883        // The general PostgreSQL `Op`-class operator surface is a PostgreSQL extension, not
884        // standard SQL, so the ANSI baseline rejects it (`^` exponentiation is likewise off,
885        // via `caret_operator` on the preset below).
886        custom_operators: false,
887        null_test_postfix: false,
888        // Postfix symbolic operators are a non-standard extension; the ANSI baseline rejects a
889        // trailing operator.
890        postfix_operators: false,
891    };
892}
893
894impl CallSyntax {
895    /// The `ANSI` predefined value.
896    pub const ANSI: Self = Self {
897        named_argument: false,
898        utc_special_functions: false,
899        columns_expression: false,
900        extract_from_syntax: true,
901        try_cast: false,
902        // ANSI/standard `CAST` admits any type name as its target.
903        restricted_cast_targets: false,
904        // The DuckDB-specific call tails — a quoted `EXTRACT` field, dot-method chaining,
905        // and in-parenthesis null-treatment — are dialect extensions, off in the baseline.
906        extract_string_field: false,
907        method_chaining: false,
908        sqljson_constructors_require_argument: false,
909        // The SQL/JSON expression functions are modelled against PostgreSQL's raw-parse
910        // surface, not verified against the bare ISO grammar, so they stay off for ANSI.
911        sqljson_expression_functions: false,
912        // The SQL/XML expression functions are likewise modelled against PostgreSQL's
913        // raw-parse surface, not the bare ISO grammar, so they stay off for ANSI.
914        xml_expression_functions: false,
915        variadic_argument: false,
916        // `merge_action()` is a PostgreSQL-only support function.
917        merge_action_function: false,
918        convert_function: false,
919    };
920}
921
922impl StringFuncForms {
923    /// The `ANSI` predefined value.
924    pub const ANSI: Self = Self {
925        // The four standard string special forms are core SQL-92/SQL:1999 grammar
926        // (E021-06/-09/-11, T312), so ANSI takes their *standard* shapes — like
927        // `extract_from_syntax` above: SUBSTRING is FROM-first only (no FOR-leading
928        // order), POSITION operands are the symmetric restricted level, OVERLAY is
929        // the PLACING form only (the standard defines no plain `overlay` call), and
930        // TRIM is the single-source `[side] [chars] FROM src` operand (no
931        // PostgreSQL trim_list tails). The PostgreSQL-only SIMILAR/ESCAPE regex
932        // substring stays off.
933        substring_from_for: true,
934        substring_leading_for: false,
935        substring_similar: false,
936        substring_plain_call_requires_2_or_3_args: false,
937        substr_from_for: false,
938        position_in: true,
939        position_asymmetric_operands: false,
940        overlay_placing: true,
941        overlay_requires_placing: true,
942        trim_from: true,
943        trim_list_syntax: false,
944        // `COLLATION FOR (<expr>)` is a PostgreSQL-only common-subexpr.
945        collation_for_expression: false,
946        // The `CEIL TO <field>` keyword form is sqlparser-rs-parity surface only —
947        // no probed oracle engine's grammar admits it.
948        ceil_to_field: false,
949        // The `FLOOR TO <field>` keyword form is sqlparser-rs-parity surface only —
950        // no probed oracle engine's grammar admits it.
951        floor_to_field: false,
952        match_against: false,
953    };
954}
955
956impl AggregateCallSyntax {
957    /// The `ANSI` predefined value.
958    pub const ANSI: Self = Self {
959        // The MySQL `GROUP_CONCAT(... SEPARATOR …)` delimiter and `UTC_*` niladic
960        // functions are dialect extensions.
961        group_concat_separator: false,
962        within_group: true,
963        aggregate_filter: true,
964        // Standard SQL requires the `WHERE` keyword inside `FILTER (…)`; only DuckDB drops it.
965        filter_optional_where: false,
966        // Standard SQL admits an aggregate's argument forms regardless of a space before the
967        // `(`; only MySQL's `IGNORE_SPACE`-off tokenizer makes the space significant.
968        aggregate_args_require_adjacent_paren: false,
969        null_treatment: false,
970        // The MySQL built-in aggregate/window arity restrictions; ANSI admits an empty
971        // call and `OVER` on any function.
972        aggregate_calls_reject_empty_arguments: false,
973        over_requires_windowable_function: false,
974        window_function_tail: false,
975        standalone_argument_order_by: false,
976    };
977}
978
979impl PredicateSyntax {
980    /// The `ANSI` predefined value.
981    pub const ANSI: Self = Self {
982        like: true,
983        ilike: false,
984        similar_to: false,
985        // The standard `OVERLAPS` period predicate stays off in this conservative base
986        // (like `similar_to`/`ilike` above); PostgreSQL and Lenient enable it. MySQL and
987        // SQLite inherit this `false` (both reject the predicate, engine-probed).
988        overlaps_period_predicate: false,
989        // The unparenthesized `IN <value>` operator is a DuckDB extension; the standard
990        // requires the parentheses.
991        unparenthesized_in_list: false,
992        // The pattern-match quantifier `LIKE/ILIKE ANY|ALL (array)` is PostgreSQL's.
993        // MySQL/SQLite inherit this `false`.
994        pattern_match_quantifier: false,
995        between_symmetric: false,
996        is_normalized: false,
997        // The standard `IN` predicate requires at least one list element; an empty `IN ()`
998        // is a syntax error. SQLite overrides this to accept it.
999        empty_in_list: false,
1000        // The two-word `<expr> NOT NULL` postfix null test is a SQLite/DuckDB extension; the
1001        // standard has only `IS NOT NULL`. SQLite and Lenient override this to accept it.
1002        null_test_two_word_postfix: false,
1003    };
1004}
1005
1006impl FeatureSet {
1007    /// The generic/standard dialect data.
1008    pub const ANSI: Self = Self {
1009        identifier_casing: Casing::Upper,
1010        identifier_quotes: STANDARD_IDENTIFIER_QUOTES,
1011        default_null_ordering: NullOrdering::NullsLast,
1012        reserved_column_name: RESERVED_COLUMN_NAME,
1013        reserved_function_name: RESERVED_FUNCTION_NAME,
1014        reserved_type_name: RESERVED_TYPE_NAME,
1015        reserved_bare_alias: RESERVED_BARE_ALIAS,
1016        // Standard/PostgreSQL admit every keyword as a `ColLabel` (`SELECT a AS select`).
1017        reserved_as_label: KeywordSet::EMPTY,
1018        // Standard relation names are catalog-qualified (`catalog.schema.table`).
1019        catalog_qualified_names: true,
1020        byte_classes: STANDARD_BYTE_CLASSES,
1021        binding_powers: STANDARD_BINDING_POWERS,
1022        set_operation_powers: STANDARD_SET_OPERATION_BINDING_POWERS,
1023        string_literals: StringLiteralSyntax::ANSI,
1024        numeric_literals: NumericLiteralSyntax::ANSI,
1025        parameters: ParameterSyntax::ANSI,
1026        session_variables: SessionVariableSyntax::ANSI,
1027        identifier_syntax: IdentifierSyntax::ANSI,
1028        table_expressions: TableExpressionSyntax::ANSI,
1029        join_syntax: JoinSyntax::ANSI,
1030        table_factor_syntax: TableFactorSyntax::ANSI,
1031        expression_syntax: ExpressionSyntax::ANSI,
1032        operator_syntax: OperatorSyntax::ANSI,
1033        call_syntax: CallSyntax::ANSI,
1034        string_func_forms: StringFuncForms::ANSI,
1035        aggregate_call_syntax: AggregateCallSyntax::ANSI,
1036        predicate_syntax: PredicateSyntax::ANSI,
1037        pipe_operator: PipeOperator::StringConcat,
1038        double_ampersand: DoubleAmpersand::Unsupported,
1039        keyword_operators: KeywordOperators::Unsupported,
1040        // `^` has no infix meaning and `#` is not the XOR operator: bitwise XOR (`#`/`^`)
1041        // and `^` exponentiation are PostgreSQL/MySQL operators, not standard SQL.
1042        caret_operator: CaretOperator::Unsupported,
1043        hash_bitwise_xor: false,
1044        comment_syntax: CommentSyntax::ANSI,
1045        mutation_syntax: MutationSyntax::ANSI,
1046        statement_ddl_gates: StatementDdlGates::ANSI,
1047        create_table_clause_syntax: CreateTableClauseSyntax::ANSI,
1048        column_definition_syntax: ColumnDefinitionSyntax::ANSI,
1049        constraint_syntax: ConstraintSyntax::ANSI,
1050        index_alter_syntax: IndexAlterSyntax::ANSI,
1051        existence_guards: ExistenceGuards::ANSI,
1052        select_syntax: SelectSyntax::ANSI,
1053        query_tail_syntax: QueryTailSyntax::ANSI,
1054        grouping_syntax: GroupingSyntax::ANSI,
1055        utility_syntax: UtilitySyntax::ANSI,
1056        show_syntax: ShowSyntax::ANSI,
1057        maintenance_syntax: MaintenanceSyntax::ANSI,
1058        access_control_syntax: AccessControlSyntax::ANSI,
1059        type_name_syntax: TypeNameSyntax::ANSI,
1060        // The generic baseline renders its own canonical type spellings (ADR-0011).
1061        target_spelling: TargetSpelling::Ansi,
1062    };
1063}
1064
1065/// Prefer [`FeatureSet::ANSI`] for struct update.
1066pub const ANSI: FeatureSet = FeatureSet::ANSI;
1067
1068// Compile-time proof the ANSI baseline claims no shared tokenizer trigger twice. The
1069// ratchet must sit where a preset grows: a future edit that adds a contending feature
1070// fails the build here rather than silently shadowing one meaning (the discipline
1071// `LENIENT` already carries, applied uniformly).
1072const _: () = assert!(FeatureSet::ANSI.is_lexically_consistent());
1073// The two sibling self-consistency registries are ratcheted the same way, so the
1074// parse-entry `debug_assert!` folds all three to dead code for this preset: no refinement
1075// flag rides an unset base, and no two features contend for one parser-position head.
1076const _: () = assert!(FeatureSet::ANSI.has_satisfied_feature_dependencies());
1077const _: () = assert!(FeatureSet::ANSI.has_no_grammar_conflict());
1078
1079#[cfg(test)]
1080mod tests {
1081    use super::Keyword;
1082    use super::*;
1083
1084    #[test]
1085    fn per_position_sets_are_data_driven_and_position_specific() {
1086        // `JOIN` is type_func_name: a function/type name but not a bare ColId.
1087        assert!(RESERVED_COLUMN_NAME.contains(Keyword::Join));
1088        assert!(!RESERVED_FUNCTION_NAME.contains(Keyword::Join));
1089        assert!(!RESERVED_TYPE_NAME.contains(Keyword::Join));
1090
1091        // `COALESCE` is col_name: a column name and (for us) a function, but not a
1092        // type name.
1093        assert!(!RESERVED_COLUMN_NAME.contains(Keyword::Coalesce));
1094        assert!(!RESERVED_FUNCTION_NAME.contains(Keyword::Coalesce));
1095        assert!(RESERVED_TYPE_NAME.contains(Keyword::Coalesce));
1096
1097        // `SELECT` is reserved: rejected as every kind of name, yet still a bare
1098        // label (it is BARE_LABEL, not AS_LABEL).
1099        assert!(RESERVED_COLUMN_NAME.contains(Keyword::Select));
1100        assert!(RESERVED_FUNCTION_NAME.contains(Keyword::Select));
1101        assert!(RESERVED_TYPE_NAME.contains(Keyword::Select));
1102        assert!(!RESERVED_BARE_ALIAS.contains(Keyword::Select));
1103
1104        // `OVER`/`FILTER` are unreserved (usable as any name) yet AS_LABEL, so they
1105        // are the bare-label divergence: not a bare alias, but everything else.
1106        for keyword in [Keyword::Over, Keyword::Filter] {
1107            assert!(!RESERVED_COLUMN_NAME.contains(keyword));
1108            assert!(!RESERVED_FUNCTION_NAME.contains(keyword));
1109            assert!(!RESERVED_TYPE_NAME.contains(keyword));
1110            assert!(RESERVED_BARE_ALIAS.contains(keyword));
1111        }
1112    }
1113}