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}