Skip to main content

squonk_ast/dialect/
databricks.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Moderately AI Inc.
3
4//! The Databricks dialect preset (ANSI-derived, deliberately conservative).
5//!
6//! Databricks SQL (the Spark-derived engine) diverges widely across its type, function,
7//! and statement surface, and — unlike the five shipped oracle-compared presets — this
8//! workspace has **no Databricks oracle**, so over-acceptance cannot be measured.
9//! Conservatism is therefore the honesty bar: this preset derives from
10//! [`FeatureSet::ANSI`], the strict standard baseline, and enables only the Databricks
11//! surface that already has a modelled, tested parser gate and clear documentary evidence.
12//! Every other axis keeps its ANSI value; a reader can predict from this module exactly
13//! what Databricks accepts beyond the standard, and unsupported Databricks syntax is a
14//! clean reject routed to a focused follow-up ticket, never a silent over-accept.
15//!
16//! # What this preset adds over ANSI
17//!
18//! Six grammar gates, each documented as Databricks SQL surface:
19//!
20//! - [`sided_semi_anti_join`](JoinSyntax::sided_semi_anti_join) — the
21//!   `{LEFT|RIGHT} {SEMI|ANTI} JOIN` sided semi-/anti-join spelling, Spark/Hive/Databricks'
22//!   signature join family. This is the flag this preset exists to make real: it shipped
23//!   staged (Lenient-only) with no engine preset home until now. The leading `LEFT`/`RIGHT`
24//!   is already a reserved join side, so no reserved-word interplay is needed (the
25//!   preceding factor's alias can never swallow it). Databricks documents the `LEFT`-sided
26//!   spelling; the atomic flag also admits the `RIGHT`-sided spelling, a known
27//!   conservative-direction over-acceptance a future side-refinement (or a Databricks
28//!   oracle) would tighten — captured as a deferral on the owning ticket.
29//! - [`semi_structured_access`](ExpressionSyntax::semi_structured_access) — the
30//!   `base:key[0].field` colon path over `VARIANT`/JSON-string columns, Databricks' JSON
31//!   path accessor. Its `:` trigger contends with
32//!   [`ParameterSyntax::named_colon`](super::ParameterSyntax::named_colon), which stays off
33//!   (ANSI's value) so the `:` trigger has a single claimant — the lexical-consistency
34//!   `const` assert below enforces it.
35//! - [`qualify`](SelectSyntax::qualify) — the `QUALIFY <predicate>` post-window filter
36//!   (Databricks Runtime 10.4 LTS and above). `QUALIFY` is reserved in every identifier
37//!   position (see [`DATABRICKS_QUALIFY_RESERVATION`]); the reservation is what lets
38//!   `FROM t QUALIFY …` read the clause rather than a table alias named `qualify`, matching
39//!   Spark's ANSI reserved-keyword parser. (Default Databricks does not enforce reserved
40//!   keywords, so `SELECT qualify FROM t` becomes a conservative reject here rather than an
41//!   ordinary column — the honest cost of shipping the clause whole instead of half.)
42//! - [`group_by_all`](GroupingSyntax::group_by_all) — the `GROUP BY ALL` clause mode.
43//! - [`order_by_all`](GroupingSyntax::order_by_all) — the `ORDER BY ALL` clause mode. Unlike
44//!   the Snowflake preset (Snowflake ships `GROUP BY ALL` *without* `ORDER BY ALL`),
45//!   Databricks documents **both**, so both flags are on here.
46//! - [`lateral_view_clause`](SelectSyntax::lateral_view_clause) — the Spark-inherited
47//!   `LATERAL VIEW [OUTER] generator(args) tblName [AS cols]` table-generating clause
48//!   (Spark `SqlBaseParser.g4` `lateralView`, documented Databricks SQL surface). The
49//!   derived-table `LATERAL` factor stays off, so `LATERAL` leads only this clause under
50//!   the preset; the flag doc records the acceptance bound the Spark grammar evidences.
51//!
52//! It also takes one utility-statement delta: [`show_functions`](ShowSyntax::show_functions),
53//! the typed `SHOW FUNCTIONS` function-listing statement. This is the first typed-`SHOW`
54//! gate on under Databricks — the MySQL-shaped `SHOW TABLES`/`COLUMNS`/`CREATE TABLE`
55//! siblings stay off, but a bare `SHOW FUNCTIONS` listing is documented Spark/Databricks
56//! surface with a modelled, tested parser gate, so it clears the conservatism bar.
57//!
58//! Databricks' identifier lexis takes one delta over ANSI: it quotes identifiers with the
59//! MySQL-style backtick `` `…` `` **and** the standard `"…"`, so
60//! [`DATABRICKS_IDENTIFIER_QUOTES`] lists both. `double_quoted_strings` stays off (ANSI's
61//! value) so `"x"` reads as an identifier, keeping the preset lexically consistent. Spark
62//! preserves the exact case of an unquoted identifier at parse time (its case-insensitive
63//! *resolution* is an analysis-time concern past this parse-level contract), so
64//! [`identifier_casing`](FeatureSet::identifier_casing) is [`Casing::Preserve`] rather than
65//! ANSI's upper-fold — an identity-only delta that never affects acceptance.
66
67use super::{
68    AccessControlSyntax, AggregateCallSyntax, CallSyntax, CaretOperator, Casing,
69    ColumnDefinitionSyntax, CommentSyntax, ConstraintSyntax, CreateTableClauseSyntax,
70    DoubleAmpersand, ExistenceGuards, ExpressionSyntax, FeatureSet, GroupingSyntax,
71    IdentifierQuote, IdentifierSyntax, IndexAlterSyntax, JoinSyntax, Keyword, KeywordOperators,
72    KeywordSet, MaintenanceSyntax, MutationSyntax, NullOrdering, NumericLiteralSyntax,
73    OperatorSyntax, ParameterSyntax, PipeOperator, PredicateSyntax, QueryTailSyntax,
74    RESERVED_BARE_ALIAS, RESERVED_COLUMN_NAME, RESERVED_FUNCTION_NAME, RESERVED_TYPE_NAME,
75    STANDARD_BYTE_CLASSES, SelectSyntax, SessionVariableSyntax, ShowSyntax, StatementDdlGates,
76    StringFuncForms, StringLiteralSyntax, TableExpressionSyntax, TableFactorSyntax, TargetSpelling,
77    TransactionSyntax, TypeNameSyntax, UtilitySyntax, ViewSequenceClauseSyntax,
78};
79use crate::precedence::{STANDARD_BINDING_POWERS, STANDARD_SET_OPERATION_BINDING_POWERS};
80
81/// Databricks identifier quoting: the MySQL-style backtick `` `…` `` **and** the SQL
82/// standard `"…"`, both at once. The two openers are distinct bytes, so their order is
83/// immaterial. `"` stays a quote here (and `double_quoted_strings` is correspondingly off
84/// in [`StringLiteralSyntax::ANSI`], which this preset keeps), so `"x"` is an identifier,
85/// never a string.
86pub const DATABRICKS_IDENTIFIER_QUOTES: &[IdentifierQuote] = &[
87    IdentifierQuote::Symmetric('"'),
88    IdentifierQuote::Symmetric('`'),
89];
90
91/// `QUALIFY`, reserved by Databricks' ANSI-strict Spark parser (its reserved-keyword list
92/// rejects the word as an unquoted identifier). Unioned into all four per-position reject
93/// sets below, mirroring the Snowflake preset: the bare-alias reservation is load-bearing
94/// for the grammar — it is what lets `FROM t QUALIFY …` read the clause instead of a table
95/// alias named `qualify`, and the column/function/type reservations match the "reserved
96/// everywhere" status. `AS`-label position stays open (`SELECT 1 AS qualify`), keeping
97/// `reserved_as_label` empty like every ANSI-derived preset.
98pub const DATABRICKS_QUALIFY_RESERVATION: KeywordSet =
99    KeywordSet::from_keywords(&[Keyword::Qualify]);
100
101/// The ANSI column-name reject set plus [`DATABRICKS_QUALIFY_RESERVATION`].
102pub const DATABRICKS_RESERVED_COLUMN_NAME: KeywordSet =
103    RESERVED_COLUMN_NAME.union(DATABRICKS_QUALIFY_RESERVATION);
104
105/// The ANSI function-name reject set plus [`DATABRICKS_QUALIFY_RESERVATION`].
106pub const DATABRICKS_RESERVED_FUNCTION_NAME: KeywordSet =
107    RESERVED_FUNCTION_NAME.union(DATABRICKS_QUALIFY_RESERVATION);
108
109/// The ANSI type-name reject set plus [`DATABRICKS_QUALIFY_RESERVATION`].
110pub const DATABRICKS_RESERVED_TYPE_NAME: KeywordSet =
111    RESERVED_TYPE_NAME.union(DATABRICKS_QUALIFY_RESERVATION);
112
113/// The ANSI bare-alias reject set plus [`DATABRICKS_QUALIFY_RESERVATION`].
114pub const DATABRICKS_RESERVED_BARE_ALIAS: KeywordSet =
115    RESERVED_BARE_ALIAS.union(DATABRICKS_QUALIFY_RESERVATION);
116
117impl SelectSyntax {
118    /// Databricks SELECT surface: the ANSI baseline plus the documented Databricks
119    /// clauses — the `QUALIFY <predicate>` post-window filter, the `GROUP BY ALL` clause
120    /// mode, the `ORDER BY ALL` clause mode, and the Spark-inherited `LATERAL VIEW`
121    /// generator clause. Unlike Snowflake, Databricks ships `ORDER BY ALL` alongside
122    /// `GROUP BY ALL`, so both are on. Every other SELECT knob is conservatively ANSI.
123    pub const DATABRICKS: Self = Self {
124        qualify: true,
125        // Spark's `LATERAL VIEW [OUTER] generator(args) tblName [AS cols]` clause
126        // (SqlBaseParser.g4 `lateralView`, documented Databricks SQL surface); the
127        // derived-table `LATERAL` factor stays off, so `LATERAL` leads only this
128        // clause under the preset.
129        lateral_view_clause: true,
130        distinct_on: false,
131        select_into: false,
132        empty_target_list: false,
133        alias_string_literals: false,
134        bare_alias_string_literals: false,
135        union_by_name: false,
136        wildcard_modifiers: false,
137        wildcard_replace: false,
138        intersect_all: true,
139        except_all: true,
140        qualified_wildcard_alias: false,
141        from_first: false,
142        explicit_table: true,
143        parenthesized_query_operands: true,
144        values_rows_require_equal_arity: false,
145        values_row_constructor: true,
146        as_alias_rejects_reserved: false,
147        trailing_comma: false,
148        prefix_colon_alias: false,
149        connect_by_clause: false,
150    };
151}
152
153impl QueryTailSyntax {
154    /// The `DATABRICKS` preset for query tail syntax.
155    pub const DATABRICKS: Self = Self {
156        fetch_first: true,
157        limit_offset_comma: false,
158        locking_clauses: false,
159        key_lock_strengths: false,
160        stacked_locking_clauses: false,
161        using_sample: false,
162        leading_offset: true,
163        limit_expressions: true,
164        limit_percent: false,
165        with_ties_requires_order_by: false,
166        pipe_syntax: false,
167        limit_by_clause: false,
168        settings_clause: false,
169        format_clause: false,
170        for_xml_json_clause: false,
171    };
172}
173
174impl GroupingSyntax {
175    /// The `DATABRICKS` preset for grouping syntax.
176    pub const DATABRICKS: Self = Self {
177        group_by_all: true,
178        group_by_set_quantifier: false,
179        order_by_all: true,
180        grouping_sets: true,
181        with_rollup: false,
182        order_by_using: false,
183    };
184}
185
186impl ExpressionSyntax {
187    /// Databricks expression surface: the ANSI baseline plus semi-structured colon path
188    /// access (`base:key[0].field`), Databricks' `VARIANT`/JSON accessor. Every other
189    /// expression knob is conservatively ANSI.
190    pub const DATABRICKS: Self = Self {
191        semi_structured_access: true,
192        typecast_operator: false,
193        subscript: false,
194        slice_step: false,
195        collate: false,
196        at_time_zone: false,
197        array_constructor: false,
198        multidim_array_literals: false,
199        collection_literals: false,
200        row_constructor: false,
201        struct_constructor: false,
202        field_selection: false,
203        field_wildcard: false,
204        typed_string_literals: true,
205        typed_interval_literal: true,
206        relaxed_interval_syntax: false,
207        mysql_interval_operator: false,
208        positional_column: false,
209        lambda_keyword: false,
210    };
211}
212
213impl TableExpressionSyntax {
214    /// Databricks table-expression surface: the ANSI baseline plus the Delta/Databricks
215    /// `VERSION`/`TIMESTAMP AS OF` time-travel modifiers. The sided `{LEFT|RIGHT}
216    /// {SEMI|ANTI} JOIN` family rides [`JoinSyntax`]; the side-less DuckDB `SEMI JOIN`
217    /// spelling stays off pending `SEMI`/`ANTI` bare-alias reservation modelling. Every
218    /// other table knob is conservatively ANSI.
219    pub const DATABRICKS: Self = Self {
220        // `VERSION AS OF <n>` / `TIMESTAMP AS OF <ts>` — Delta Lake time travel.
221        table_version: true,
222        only: false,
223        table_sample: false,
224        parenthesized_joins: true,
225        table_alias_column_lists: true,
226        join_using_alias: false,
227        index_hints: false,
228        table_hints: false,
229        partition_selection: false,
230        base_table_alias_column_lists: true,
231        string_literal_aliases: false,
232        aliased_parenthesized_join: true,
233        bare_table_alias_is_bare_label: false,
234        table_json_path: false,
235        indexed_by: false,
236        prefix_colon_alias: false,
237    };
238}
239
240impl JoinSyntax {
241    /// The `DATABRICKS` preset for join syntax.
242    pub const DATABRICKS: Self = Self {
243        sided_semi_anti_join: true,
244        stacked_join_qualifiers: true,
245        full_outer_join: true,
246        natural_cross_join: false,
247        straight_join: false,
248        asof_join: false,
249        positional_join: false,
250        semi_anti_join: false,
251        apply_join: false,
252        recursive_search_cycle: false,
253        recursive_union_rejects_order_limit: false,
254        recursive_using_key: false,
255    };
256}
257
258impl TableFactorSyntax {
259    /// The `DATABRICKS` preset for table factor syntax.
260    pub const DATABRICKS: Self = Self {
261        lateral: false,
262        table_functions: false,
263        rows_from: false,
264        unnest: false,
265        unnest_with_offset: false,
266        table_function_ordinality: false,
267        special_function_table_source: true,
268        pivot: false,
269        unpivot: false,
270        show_ref: false,
271        from_values: false,
272        json_table: false,
273        xml_table: false,
274        table_expr_factor: false,
275        pivot_value_sources: false,
276        match_recognize: false,
277        open_json: false,
278    };
279}
280
281impl UtilitySyntax {
282    /// Databricks utility surface: the ANSI baseline plus the typed `SHOW FUNCTIONS`
283    /// listing. This is the first typed-`SHOW` gate on under Databricks — the sibling
284    /// `SHOW TABLES`/`COLUMNS`/`CREATE TABLE` gates are MySQL-shaped and stay off, but a
285    /// bare `SHOW FUNCTIONS` listing is documented Spark/Databricks surface with a modelled,
286    /// tested parser gate, so it clears the preset's conservatism bar. Every other utility
287    /// knob is conservatively ANSI.
288    pub const DATABRICKS: Self = Self {
289        copy: false,
290        copy_into: false,
291        stage_references: false,
292        comment_on: false,
293        comment_if_exists: false,
294        pragma: false,
295        attach: false,
296        kill: false,
297        handler_statements: false,
298        plugin_component_statements: false,
299        shutdown: false,
300        restart: false,
301        clone: false,
302        import_table: false,
303        help_statement: false,
304        binlog: false,
305        key_cache_statements: false,
306        use_statement: false,
307        use_qualified_name: false,
308        use_string_literal_name: false,
309        prepared_statements: false,
310        prepare_typed_parameters: false,
311        prepared_statements_from: false,
312        call: false,
313        call_bare_name: false,
314        load_extension: false,
315        load_bare_name: false,
316        load_data: false,
317        reset_scope: false,
318        detach_if_exists: false,
319        do_statement: false,
320        do_expression_list: false,
321        lock_tables: false,
322        lock_instance: false,
323        rename_statement: false,
324        signal_diagnostics: false,
325        export_import_database: false,
326        update_extensions: false,
327        flush: false,
328        purge_binary_logs: false,
329        replication_statements: false,
330    };
331}
332impl TransactionSyntax {
333    /// Transaction-control surface for the `DATABRICKS` preset (split from UtilitySyntax).
334    pub const DATABRICKS: Self = Self {
335        start_transaction: true,
336        start_transaction_block_optional: false,
337        transaction_work_keyword: true,
338        begin_transaction_keyword: true,
339        commit_transaction_keyword: true,
340        rollback_transaction_keyword: true,
341        transaction_name: false,
342        begin_transaction_modes: true,
343        transaction_savepoints: true,
344        set_transaction: true,
345        transaction_isolation_mode: true,
346        transaction_access_mode: true,
347        transaction_deferrable_mode: true,
348        start_transaction_isolation_mode: true,
349        start_transaction_deferrable_mode: true,
350        start_transaction_consistent_snapshot: false,
351        transaction_multiple_modes: true,
352        transaction_modes_require_commas: false,
353        transaction_modes_reject_duplicates: false,
354        abort_transaction_alias: false,
355        end_transaction_alias: false,
356        transaction_release: false,
357        transaction_chain: true,
358        release_savepoint_keyword_optional: true,
359        begin_transaction_mode: false,
360        xa_transactions: false,
361    };
362}
363
364impl ShowSyntax {
365    /// The `DATABRICKS` preset for show syntax.
366    pub const DATABRICKS: Self = Self {
367        show_functions: true,
368        describe: false,
369        describe_summarize: false,
370        session_statements: true,
371        set_value_reserved_words: super::RESERVED_SET_VALUE_WORDS,
372        set_value_on_keyword: true,
373        set_value_null_keyword: false,
374        show_tables: false,
375        show_columns: false,
376        show_create_table: false,
377        show_routine_status: false,
378        show_verbose: false,
379        show_admin: false,
380    };
381}
382
383impl MaintenanceSyntax {
384    /// The `DATABRICKS` preset for maintenance syntax.
385    pub const DATABRICKS: Self = Self {
386        vacuum: false,
387        vacuum_analyze: false,
388        reindex: false,
389        analyze: false,
390        analyze_columns: false,
391        checkpoint: false,
392        checkpoint_database: false,
393        table_maintenance: false,
394    };
395}
396
397impl AccessControlSyntax {
398    /// The `DATABRICKS` preset for access control syntax.
399    pub const DATABRICKS: Self = Self {
400        alter_role_rename: false,
401        access_control: true,
402        access_control_extended_objects: true,
403        user_role_management: false,
404        access_control_account_grants: false,
405    };
406}
407
408impl FeatureSet {
409    /// Databricks as ANSI-derived dialect data (see the module docs for the full derivation
410    /// rationale and the conservatism bar).
411    pub const DATABRICKS: Self = Self {
412        // Spark preserves the exact case of an unquoted identifier at parse time; its
413        // case-insensitive resolution is an analysis-time concern past this parse contract.
414        // Identity only — never affects acceptance.
415        identifier_casing: Casing::Preserve,
416        // The one lexical delta over ANSI: backtick *and* double-quote identifier quoting.
417        identifier_quotes: DATABRICKS_IDENTIFIER_QUOTES,
418        default_null_ordering: NullOrdering::NullsLast,
419        // The one reserved-set delta over ANSI: `QUALIFY` is reserved in every identifier
420        // position (matching Spark's ANSI reserved-keyword parser), which the `QUALIFY`
421        // clause gate depends on to disambiguate `FROM t QUALIFY …`.
422        reserved_column_name: DATABRICKS_RESERVED_COLUMN_NAME,
423        reserved_function_name: DATABRICKS_RESERVED_FUNCTION_NAME,
424        reserved_type_name: DATABRICKS_RESERVED_TYPE_NAME,
425        reserved_bare_alias: DATABRICKS_RESERVED_BARE_ALIAS,
426        reserved_as_label: KeywordSet::EMPTY,
427        catalog_qualified_names: true,
428        byte_classes: STANDARD_BYTE_CLASSES,
429        binding_powers: STANDARD_BINDING_POWERS,
430        set_operation_powers: STANDARD_SET_OPERATION_BINDING_POWERS,
431        // Conservative ANSI string/number/parameter surface: Databricks' own forms
432        // (backslash escapes, stage/`@`-path references, `$$`-free bind syntax) have no
433        // modelled gate here and are deferred rather than guessed at without an oracle.
434        // Crucially `named_colon` stays off (in `ParameterSyntax::ANSI`) so the `:` trigger
435        // belongs solely to `semi_structured_access` (the lexical assert below enforces it).
436        string_literals: StringLiteralSyntax::ANSI,
437        numeric_literals: NumericLiteralSyntax::ANSI,
438        parameters: ParameterSyntax::ANSI,
439        session_variables: SessionVariableSyntax::ANSI,
440        identifier_syntax: IdentifierSyntax::ANSI,
441        // The sided semi-/anti-join family — one of the capstones this preset exposes.
442        table_expressions: TableExpressionSyntax::DATABRICKS,
443        join_syntax: JoinSyntax::DATABRICKS,
444        table_factor_syntax: TableFactorSyntax::DATABRICKS,
445        // The semi-structured colon path accessor.
446        expression_syntax: ExpressionSyntax::DATABRICKS,
447        operator_syntax: OperatorSyntax::ANSI,
448        call_syntax: CallSyntax::ANSI,
449        string_func_forms: StringFuncForms::ANSI,
450        aggregate_call_syntax: AggregateCallSyntax::ANSI,
451        predicate_syntax: PredicateSyntax::ANSI,
452        pipe_operator: PipeOperator::StringConcat,
453        double_ampersand: DoubleAmpersand::Unsupported,
454        keyword_operators: KeywordOperators::Unsupported,
455        caret_operator: CaretOperator::Unsupported,
456        hash_bitwise_xor: false,
457        comment_syntax: CommentSyntax::ANSI,
458        mutation_syntax: MutationSyntax::ANSI,
459        statement_ddl_gates: StatementDdlGates::ANSI,
460        view_sequence_clause_syntax: ViewSequenceClauseSyntax::ANSI,
461        create_table_clause_syntax: CreateTableClauseSyntax::ANSI,
462        column_definition_syntax: ColumnDefinitionSyntax::ANSI,
463        constraint_syntax: ConstraintSyntax::ANSI,
464        index_alter_syntax: IndexAlterSyntax::ANSI,
465        existence_guards: ExistenceGuards::ANSI,
466        // The `QUALIFY`, `GROUP BY ALL`, and `ORDER BY ALL` clauses.
467        select_syntax: SelectSyntax::DATABRICKS,
468        query_tail_syntax: QueryTailSyntax::DATABRICKS,
469        grouping_syntax: GroupingSyntax::DATABRICKS,
470        // The typed `SHOW FUNCTIONS` listing — the first typed-`SHOW` gate on under
471        // Databricks (see `UtilitySyntax::DATABRICKS`); every other utility knob is ANSI.
472        utility_syntax: UtilitySyntax::DATABRICKS,
473        transaction_syntax: TransactionSyntax::DATABRICKS,
474        show_syntax: ShowSyntax::DATABRICKS,
475        maintenance_syntax: MaintenanceSyntax::DATABRICKS,
476        access_control_syntax: AccessControlSyntax::DATABRICKS,
477        type_name_syntax: TypeNameSyntax::ANSI,
478        // No Databricks-specific Tier-1 output spelling yet; render the portable ANSI
479        // canonical type names (a `TargetSpelling::Databricks` is render work a later
480        // ticket owns).
481        target_spelling: TargetSpelling::Ansi,
482    };
483}
484
485/// Prefer [`FeatureSet::DATABRICKS`] for struct update.
486pub const DATABRICKS: FeatureSet = FeatureSet::DATABRICKS;
487
488// Compile-time proof the Databricks preset claims no shared tokenizer trigger twice. Beyond
489// ANSI it adds two triggers — the backtick identifier quote and the `:` semi-structured
490// accessor — each with a single claimant (no other enabled feature lexes a backtick, and
491// `named_colon` stays off so `:` belongs solely to `semi_structured_access`), and it keeps
492// `double_quoted_strings` off so `"` stays the sole identifier quote it already was. Every
493// other delta is a contextual grammar gate or a keyword reservation with no tokenizer
494// trigger. Kept as a ratchet so a future Databricks delta that *does* add a contending
495// trigger (e.g. enabling colon parameters) fails the build here.
496const _: () = assert!(FeatureSet::DATABRICKS.is_lexically_consistent());
497// The two sibling self-consistency registries are ratcheted the same way, so the
498// parse-entry `debug_assert!` folds all three to dead code for this preset: no refinement
499// flag rides an unset base, and no two features contend for one parser-position head.
500const _: () = assert!(FeatureSet::DATABRICKS.has_satisfied_feature_dependencies());
501const _: () = assert!(FeatureSet::DATABRICKS.has_no_grammar_conflict());
502
503#[cfg(test)]
504mod tests {
505    use super::*;
506
507    #[test]
508    fn databricks_is_ansi_plus_the_six_gates_and_two_lexical_facts() {
509        // The preset is ANSI with a documented, closed set of divergent axes: the two
510        // lexical facts (case-preservation, dual identifier quoting), the `QUALIFY`
511        // reservation, and the three enabled sub-presets. Asserting the whole rest equals
512        // ANSI keeps the "ANSI-derived, every delta documented" claim honest against a
513        // future stray edit. Bind to locals so the const reads are not flagged by clippy's
514        // `assertions_on_constants`.
515        let ansi = FeatureSet::ANSI;
516        let dbx = FeatureSet::DATABRICKS;
517
518        // The two lexical facts.
519        assert_eq!(dbx.identifier_casing, Casing::Preserve);
520        assert_ne!(dbx.identifier_casing, ansi.identifier_casing);
521        assert_eq!(dbx.identifier_quotes, DATABRICKS_IDENTIFIER_QUOTES);
522        assert_ne!(dbx.identifier_quotes, ansi.identifier_quotes);
523        // `"` stays an identifier quote, so Databricks must keep `double_quoted_strings`
524        // off (the interplay the preset depends on for lexical consistency).
525        assert!(!dbx.string_literals.double_quoted_strings);
526        // `named_colon` off is the interplay the semi-structured accessor depends on.
527        assert!(!dbx.parameters.named_colon);
528
529        // The four divergent sub-presets.
530        assert_eq!(dbx.select_syntax, SelectSyntax::DATABRICKS);
531        assert_ne!(dbx.select_syntax, ansi.select_syntax);
532        assert_eq!(dbx.expression_syntax, ExpressionSyntax::DATABRICKS);
533        assert_ne!(dbx.expression_syntax, ansi.expression_syntax);
534        assert_eq!(dbx.table_expressions, TableExpressionSyntax::DATABRICKS);
535        assert_eq!(dbx.join_syntax, JoinSyntax::DATABRICKS);
536        assert_ne!(dbx.join_syntax, ansi.join_syntax);
537        // The utility surface diverges only in the typed `SHOW FUNCTIONS` gate — the first
538        // typed-`SHOW` flag on under Databricks; forcing it off recovers ANSI verbatim.
539        assert_eq!(dbx.utility_syntax, UtilitySyntax::DATABRICKS);
540        assert_eq!(dbx.show_syntax, ShowSyntax::DATABRICKS);
541        assert_ne!(dbx.show_syntax, ansi.show_syntax);
542        assert!(dbx.show_syntax.show_functions);
543        assert_eq!(
544            ShowSyntax {
545                show_functions: false,
546                ..dbx.show_syntax
547            },
548            ansi.show_syntax,
549        );
550
551        // The reserved-set delta: ANSI base plus QUALIFY, in every identifier position.
552        assert_eq!(dbx.reserved_column_name, DATABRICKS_RESERVED_COLUMN_NAME);
553        assert_ne!(dbx.reserved_column_name, ansi.reserved_column_name);
554        assert_eq!(
555            dbx.reserved_function_name,
556            DATABRICKS_RESERVED_FUNCTION_NAME
557        );
558        assert_eq!(dbx.reserved_type_name, DATABRICKS_RESERVED_TYPE_NAME);
559        assert_eq!(dbx.reserved_bare_alias, DATABRICKS_RESERVED_BARE_ALIAS);
560        // `QUALIFY` is the sole addition — dropping it recovers the ANSI sets verbatim.
561        assert_eq!(
562            dbx.reserved_column_name
563                .difference(DATABRICKS_QUALIFY_RESERVATION),
564            ansi.reserved_column_name,
565        );
566        assert!(dbx.reserved_column_name.contains(Keyword::Qualify));
567        assert!(dbx.reserved_bare_alias.contains(Keyword::Qualify));
568        // `AS`-label position stays open (`SELECT 1 AS qualify`).
569        assert_eq!(dbx.reserved_as_label, KeywordSet::EMPTY);
570
571        // Everything else is inherited verbatim from ANSI.
572        assert_eq!(dbx.string_literals, ansi.string_literals);
573        assert_eq!(dbx.numeric_literals, ansi.numeric_literals);
574        assert_eq!(dbx.parameters, ansi.parameters);
575        assert_eq!(dbx.session_variables, ansi.session_variables);
576        assert_eq!(dbx.identifier_syntax, ansi.identifier_syntax);
577        assert_eq!(dbx.operator_syntax, ansi.operator_syntax);
578        assert_eq!(dbx.call_syntax, ansi.call_syntax);
579        assert_eq!(dbx.predicate_syntax, ansi.predicate_syntax);
580        assert_eq!(dbx.mutation_syntax, ansi.mutation_syntax);
581        assert_eq!(dbx.statement_ddl_gates, ansi.statement_ddl_gates);
582        assert_eq!(
583            dbx.create_table_clause_syntax,
584            ansi.create_table_clause_syntax
585        );
586        assert_eq!(dbx.column_definition_syntax, ansi.column_definition_syntax);
587        assert_eq!(dbx.constraint_syntax, ansi.constraint_syntax);
588        assert_eq!(dbx.index_alter_syntax, ansi.index_alter_syntax);
589        assert_eq!(dbx.existence_guards, ansi.existence_guards);
590        assert_eq!(dbx.type_name_syntax, ansi.type_name_syntax);
591        assert_eq!(dbx.byte_classes, ansi.byte_classes);
592        assert_eq!(dbx.binding_powers, ansi.binding_powers);
593        assert_eq!(dbx.target_spelling, ansi.target_spelling);
594        assert_eq!(dbx.default_null_ordering, ansi.default_null_ordering);
595    }
596
597    #[test]
598    fn databricks_enables_exactly_the_six_staged_gates() {
599        // The capstone: sided semi-/anti-joins, semi-structured access, QUALIFY,
600        // GROUP BY ALL, ORDER BY ALL, and LATERAL VIEW are on, and each is off in the
601        // ANSI base it derives from. Forcing the gates back off recovers the ANSI
602        // sub-presets verbatim.
603        let ansi = FeatureSet::ANSI;
604        let dbx = FeatureSet::DATABRICKS;
605
606        assert!(dbx.join_syntax.sided_semi_anti_join);
607        assert!(!ansi.join_syntax.sided_semi_anti_join);
608        // The side-less DuckDB spelling stays off (deferred).
609        assert!(!dbx.join_syntax.semi_anti_join);
610        assert!(dbx.expression_syntax.semi_structured_access);
611        assert!(!ansi.expression_syntax.semi_structured_access);
612        assert!(dbx.select_syntax.qualify && !ansi.select_syntax.qualify);
613        assert!(dbx.grouping_syntax.group_by_all && !ansi.grouping_syntax.group_by_all);
614        // Unlike Snowflake, Databricks ships ORDER BY ALL alongside GROUP BY ALL.
615        assert!(dbx.grouping_syntax.order_by_all && !ansi.grouping_syntax.order_by_all);
616        // The Spark-inherited LATERAL VIEW clause; the derived-table LATERAL factor
617        // stays off, so `LATERAL` leads only the view clause under this preset.
618        assert!(dbx.select_syntax.lateral_view_clause && !ansi.select_syntax.lateral_view_clause);
619        assert!(!dbx.table_factor_syntax.lateral);
620
621        assert_eq!(
622            SelectSyntax {
623                qualify: false,
624                lateral_view_clause: false,
625                ..dbx.select_syntax
626            },
627            ansi.select_syntax,
628        );
629        assert_eq!(
630            GroupingSyntax {
631                group_by_all: false,
632                order_by_all: false,
633                ..dbx.grouping_syntax
634            },
635            ansi.grouping_syntax,
636        );
637        assert_eq!(
638            ExpressionSyntax {
639                semi_structured_access: false,
640                ..dbx.expression_syntax
641            },
642            ansi.expression_syntax,
643        );
644        assert_eq!(
645            JoinSyntax {
646                sided_semi_anti_join: false,
647                ..dbx.join_syntax
648            },
649            ansi.join_syntax,
650        );
651    }
652
653    #[test]
654    fn databricks_is_lexically_consistent_and_dependency_clean() {
655        // Both self-consistency registries must be clean: the backtick quote and the
656        // semi-structured `:` trigger each have a single claimant (colon parameters stay
657        // off, `double_quoted_strings` off), and none of the five contextual gates rides an
658        // unset base flag.
659        let dbx = FeatureSet::DATABRICKS;
660        assert_eq!(dbx.lexical_conflict(), None);
661        assert!(dbx.is_lexically_consistent());
662        assert_eq!(dbx.feature_dependencies(), None);
663        assert!(dbx.has_satisfied_feature_dependencies());
664        assert_eq!(dbx.grammar_conflict(), None);
665        assert!(dbx.has_no_grammar_conflict());
666    }
667    #[test]
668    fn databricks_closed_delta_axes_match_documented_set() {
669        crate::dialect::closed_delta::assert_closed_delta(
670            &FeatureSet::ANSI,
671            &FeatureSet::DATABRICKS,
672            &[
673                "identifier_casing",
674                "identifier_quotes",
675                "reserved_column_name",
676                "reserved_function_name",
677                "reserved_type_name",
678                "reserved_bare_alias",
679                "table_expressions",
680                "join_syntax",
681                "expression_syntax",
682                "select_syntax",
683                "grouping_syntax",
684                "show_syntax",
685            ],
686        );
687    }
688}