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