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    TypeNameSyntax, UtilitySyntax,
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        ..SelectSyntax::ANSI
131    };
132}
133
134impl QueryTailSyntax {
135    /// The `DATABRICKS` preset for query tail syntax.
136    pub const DATABRICKS: Self = Self {
137        ..QueryTailSyntax::ANSI
138    };
139}
140
141impl GroupingSyntax {
142    /// The `DATABRICKS` preset for grouping syntax.
143    pub const DATABRICKS: Self = Self {
144        group_by_all: true,
145        group_by_set_quantifier: false,
146        order_by_all: true,
147        ..GroupingSyntax::ANSI
148    };
149}
150
151impl ExpressionSyntax {
152    /// Databricks expression surface: the ANSI baseline plus semi-structured colon path
153    /// access (`base:key[0].field`), Databricks' `VARIANT`/JSON accessor. Every other
154    /// expression knob is conservatively ANSI.
155    pub const DATABRICKS: Self = Self {
156        semi_structured_access: true,
157        ..ExpressionSyntax::ANSI
158    };
159}
160
161impl TableExpressionSyntax {
162    /// Databricks table-expression surface: the ANSI baseline plus the Delta/Databricks
163    /// `VERSION`/`TIMESTAMP AS OF` time-travel modifiers. The sided `{LEFT|RIGHT}
164    /// {SEMI|ANTI} JOIN` family rides [`JoinSyntax`]; the side-less DuckDB `SEMI JOIN`
165    /// spelling stays off pending `SEMI`/`ANTI` bare-alias reservation modelling. Every
166    /// other table knob is conservatively ANSI.
167    pub const DATABRICKS: Self = Self {
168        // `VERSION AS OF <n>` / `TIMESTAMP AS OF <ts>` — Delta Lake time travel.
169        table_version: true,
170        ..TableExpressionSyntax::ANSI
171    };
172}
173
174impl JoinSyntax {
175    /// The `DATABRICKS` preset for join syntax.
176    pub const DATABRICKS: Self = Self {
177        sided_semi_anti_join: true,
178        ..JoinSyntax::ANSI
179    };
180}
181
182impl TableFactorSyntax {
183    /// The `DATABRICKS` preset for table factor syntax.
184    pub const DATABRICKS: Self = Self {
185        ..TableFactorSyntax::ANSI
186    };
187}
188
189impl UtilitySyntax {
190    /// Databricks utility surface: the ANSI baseline plus the typed `SHOW FUNCTIONS`
191    /// listing. This is the first typed-`SHOW` gate on under Databricks — the sibling
192    /// `SHOW TABLES`/`COLUMNS`/`CREATE TABLE` gates are MySQL-shaped and stay off, but a
193    /// bare `SHOW FUNCTIONS` listing is documented Spark/Databricks surface with a modelled,
194    /// tested parser gate, so it clears the preset's conservatism bar. Every other utility
195    /// knob is conservatively ANSI.
196    pub const DATABRICKS: Self = Self {
197        ..UtilitySyntax::ANSI
198    };
199}
200
201impl ShowSyntax {
202    /// The `DATABRICKS` preset for show syntax.
203    pub const DATABRICKS: Self = Self {
204        show_functions: true,
205        ..ShowSyntax::ANSI
206    };
207}
208
209impl MaintenanceSyntax {
210    /// The `DATABRICKS` preset for maintenance syntax.
211    pub const DATABRICKS: Self = Self {
212        ..MaintenanceSyntax::ANSI
213    };
214}
215
216impl AccessControlSyntax {
217    /// The `DATABRICKS` preset for access control syntax.
218    pub const DATABRICKS: Self = Self {
219        ..AccessControlSyntax::ANSI
220    };
221}
222
223impl FeatureSet {
224    /// Databricks as ANSI-derived dialect data (see the module docs for the full derivation
225    /// rationale and the conservatism bar).
226    pub const DATABRICKS: Self = Self {
227        // Spark preserves the exact case of an unquoted identifier at parse time; its
228        // case-insensitive resolution is an analysis-time concern past this parse contract.
229        // Identity only — never affects acceptance.
230        identifier_casing: Casing::Preserve,
231        // The one lexical delta over ANSI: backtick *and* double-quote identifier quoting.
232        identifier_quotes: DATABRICKS_IDENTIFIER_QUOTES,
233        default_null_ordering: NullOrdering::NullsLast,
234        // The one reserved-set delta over ANSI: `QUALIFY` is reserved in every identifier
235        // position (matching Spark's ANSI reserved-keyword parser), which the `QUALIFY`
236        // clause gate depends on to disambiguate `FROM t QUALIFY …`.
237        reserved_column_name: DATABRICKS_RESERVED_COLUMN_NAME,
238        reserved_function_name: DATABRICKS_RESERVED_FUNCTION_NAME,
239        reserved_type_name: DATABRICKS_RESERVED_TYPE_NAME,
240        reserved_bare_alias: DATABRICKS_RESERVED_BARE_ALIAS,
241        reserved_as_label: KeywordSet::EMPTY,
242        catalog_qualified_names: true,
243        byte_classes: STANDARD_BYTE_CLASSES,
244        binding_powers: STANDARD_BINDING_POWERS,
245        set_operation_powers: STANDARD_SET_OPERATION_BINDING_POWERS,
246        // Conservative ANSI string/number/parameter surface: Databricks' own forms
247        // (backslash escapes, stage/`@`-path references, `$$`-free bind syntax) have no
248        // modelled gate here and are deferred rather than guessed at without an oracle.
249        // Crucially `named_colon` stays off (in `ParameterSyntax::ANSI`) so the `:` trigger
250        // belongs solely to `semi_structured_access` (the lexical assert below enforces it).
251        string_literals: StringLiteralSyntax::ANSI,
252        numeric_literals: NumericLiteralSyntax::ANSI,
253        parameters: ParameterSyntax::ANSI,
254        session_variables: SessionVariableSyntax::ANSI,
255        identifier_syntax: IdentifierSyntax::ANSI,
256        // The sided semi-/anti-join family — one of the capstones this preset exposes.
257        table_expressions: TableExpressionSyntax::DATABRICKS,
258        join_syntax: JoinSyntax::DATABRICKS,
259        table_factor_syntax: TableFactorSyntax::DATABRICKS,
260        // The semi-structured colon path accessor.
261        expression_syntax: ExpressionSyntax::DATABRICKS,
262        operator_syntax: OperatorSyntax::ANSI,
263        call_syntax: CallSyntax::ANSI,
264        string_func_forms: StringFuncForms::ANSI,
265        aggregate_call_syntax: AggregateCallSyntax::ANSI,
266        predicate_syntax: PredicateSyntax::ANSI,
267        pipe_operator: PipeOperator::StringConcat,
268        double_ampersand: DoubleAmpersand::Unsupported,
269        keyword_operators: KeywordOperators::Unsupported,
270        caret_operator: CaretOperator::Unsupported,
271        hash_bitwise_xor: false,
272        comment_syntax: CommentSyntax::ANSI,
273        mutation_syntax: MutationSyntax::ANSI,
274        statement_ddl_gates: StatementDdlGates::ANSI,
275        create_table_clause_syntax: CreateTableClauseSyntax::ANSI,
276        column_definition_syntax: ColumnDefinitionSyntax::ANSI,
277        constraint_syntax: ConstraintSyntax::ANSI,
278        index_alter_syntax: IndexAlterSyntax::ANSI,
279        existence_guards: ExistenceGuards::ANSI,
280        // The `QUALIFY`, `GROUP BY ALL`, and `ORDER BY ALL` clauses.
281        select_syntax: SelectSyntax::DATABRICKS,
282        query_tail_syntax: QueryTailSyntax::DATABRICKS,
283        grouping_syntax: GroupingSyntax::DATABRICKS,
284        // The typed `SHOW FUNCTIONS` listing — the first typed-`SHOW` gate on under
285        // Databricks (see `UtilitySyntax::DATABRICKS`); every other utility knob is ANSI.
286        utility_syntax: UtilitySyntax::DATABRICKS,
287        show_syntax: ShowSyntax::DATABRICKS,
288        maintenance_syntax: MaintenanceSyntax::DATABRICKS,
289        access_control_syntax: AccessControlSyntax::DATABRICKS,
290        type_name_syntax: TypeNameSyntax::ANSI,
291        // No Databricks-specific Tier-1 output spelling yet; render the portable ANSI
292        // canonical type names (a `TargetSpelling::Databricks` is render work a later
293        // ticket owns).
294        target_spelling: TargetSpelling::Ansi,
295    };
296}
297
298/// Prefer [`FeatureSet::DATABRICKS`] for struct update.
299pub const DATABRICKS: FeatureSet = FeatureSet::DATABRICKS;
300
301// Compile-time proof the Databricks preset claims no shared tokenizer trigger twice. Beyond
302// ANSI it adds two triggers — the backtick identifier quote and the `:` semi-structured
303// accessor — each with a single claimant (no other enabled feature lexes a backtick, and
304// `named_colon` stays off so `:` belongs solely to `semi_structured_access`), and it keeps
305// `double_quoted_strings` off so `"` stays the sole identifier quote it already was. Every
306// other delta is a contextual grammar gate or a keyword reservation with no tokenizer
307// trigger. Kept as a ratchet so a future Databricks delta that *does* add a contending
308// trigger (e.g. enabling colon parameters) fails the build here.
309const _: () = assert!(FeatureSet::DATABRICKS.is_lexically_consistent());
310// The two sibling self-consistency registries are ratcheted the same way, so the
311// parse-entry `debug_assert!` folds all three to dead code for this preset: no refinement
312// flag rides an unset base, and no two features contend for one parser-position head.
313const _: () = assert!(FeatureSet::DATABRICKS.has_satisfied_feature_dependencies());
314const _: () = assert!(FeatureSet::DATABRICKS.has_no_grammar_conflict());
315
316#[cfg(test)]
317mod tests {
318    use super::*;
319
320    #[test]
321    fn databricks_is_ansi_plus_the_six_gates_and_two_lexical_facts() {
322        // The preset is ANSI with a documented, closed set of divergent axes: the two
323        // lexical facts (case-preservation, dual identifier quoting), the `QUALIFY`
324        // reservation, and the three enabled sub-presets. Asserting the whole rest equals
325        // ANSI keeps the "ANSI-derived, every delta documented" claim honest against a
326        // future stray edit. Bind to locals so the const reads are not flagged by clippy's
327        // `assertions_on_constants`.
328        let ansi = FeatureSet::ANSI;
329        let dbx = FeatureSet::DATABRICKS;
330
331        // The two lexical facts.
332        assert_eq!(dbx.identifier_casing, Casing::Preserve);
333        assert_ne!(dbx.identifier_casing, ansi.identifier_casing);
334        assert_eq!(dbx.identifier_quotes, DATABRICKS_IDENTIFIER_QUOTES);
335        assert_ne!(dbx.identifier_quotes, ansi.identifier_quotes);
336        // `"` stays an identifier quote, so Databricks must keep `double_quoted_strings`
337        // off (the interplay the preset depends on for lexical consistency).
338        assert!(!dbx.string_literals.double_quoted_strings);
339        // `named_colon` off is the interplay the semi-structured accessor depends on.
340        assert!(!dbx.parameters.named_colon);
341
342        // The four divergent sub-presets.
343        assert_eq!(dbx.select_syntax, SelectSyntax::DATABRICKS);
344        assert_ne!(dbx.select_syntax, ansi.select_syntax);
345        assert_eq!(dbx.expression_syntax, ExpressionSyntax::DATABRICKS);
346        assert_ne!(dbx.expression_syntax, ansi.expression_syntax);
347        assert_eq!(dbx.table_expressions, TableExpressionSyntax::DATABRICKS);
348        assert_eq!(dbx.join_syntax, JoinSyntax::DATABRICKS);
349        assert_ne!(dbx.join_syntax, ansi.join_syntax);
350        // The utility surface diverges only in the typed `SHOW FUNCTIONS` gate — the first
351        // typed-`SHOW` flag on under Databricks; forcing it off recovers ANSI verbatim.
352        assert_eq!(dbx.utility_syntax, UtilitySyntax::DATABRICKS);
353        assert_eq!(dbx.show_syntax, ShowSyntax::DATABRICKS);
354        assert_ne!(dbx.show_syntax, ansi.show_syntax);
355        assert!(dbx.show_syntax.show_functions);
356        assert_eq!(
357            ShowSyntax {
358                show_functions: false,
359                ..dbx.show_syntax
360            },
361            ansi.show_syntax,
362        );
363
364        // The reserved-set delta: ANSI base plus QUALIFY, in every identifier position.
365        assert_eq!(dbx.reserved_column_name, DATABRICKS_RESERVED_COLUMN_NAME);
366        assert_ne!(dbx.reserved_column_name, ansi.reserved_column_name);
367        assert_eq!(
368            dbx.reserved_function_name,
369            DATABRICKS_RESERVED_FUNCTION_NAME
370        );
371        assert_eq!(dbx.reserved_type_name, DATABRICKS_RESERVED_TYPE_NAME);
372        assert_eq!(dbx.reserved_bare_alias, DATABRICKS_RESERVED_BARE_ALIAS);
373        // `QUALIFY` is the sole addition — dropping it recovers the ANSI sets verbatim.
374        assert_eq!(
375            dbx.reserved_column_name
376                .difference(DATABRICKS_QUALIFY_RESERVATION),
377            ansi.reserved_column_name,
378        );
379        assert!(dbx.reserved_column_name.contains(Keyword::Qualify));
380        assert!(dbx.reserved_bare_alias.contains(Keyword::Qualify));
381        // `AS`-label position stays open (`SELECT 1 AS qualify`).
382        assert_eq!(dbx.reserved_as_label, KeywordSet::EMPTY);
383
384        // Everything else is inherited verbatim from ANSI.
385        assert_eq!(dbx.string_literals, ansi.string_literals);
386        assert_eq!(dbx.numeric_literals, ansi.numeric_literals);
387        assert_eq!(dbx.parameters, ansi.parameters);
388        assert_eq!(dbx.session_variables, ansi.session_variables);
389        assert_eq!(dbx.identifier_syntax, ansi.identifier_syntax);
390        assert_eq!(dbx.operator_syntax, ansi.operator_syntax);
391        assert_eq!(dbx.call_syntax, ansi.call_syntax);
392        assert_eq!(dbx.predicate_syntax, ansi.predicate_syntax);
393        assert_eq!(dbx.mutation_syntax, ansi.mutation_syntax);
394        assert_eq!(dbx.statement_ddl_gates, ansi.statement_ddl_gates);
395        assert_eq!(
396            dbx.create_table_clause_syntax,
397            ansi.create_table_clause_syntax
398        );
399        assert_eq!(dbx.column_definition_syntax, ansi.column_definition_syntax);
400        assert_eq!(dbx.constraint_syntax, ansi.constraint_syntax);
401        assert_eq!(dbx.index_alter_syntax, ansi.index_alter_syntax);
402        assert_eq!(dbx.existence_guards, ansi.existence_guards);
403        assert_eq!(dbx.type_name_syntax, ansi.type_name_syntax);
404        assert_eq!(dbx.byte_classes, ansi.byte_classes);
405        assert_eq!(dbx.binding_powers, ansi.binding_powers);
406        assert_eq!(dbx.target_spelling, ansi.target_spelling);
407        assert_eq!(dbx.default_null_ordering, ansi.default_null_ordering);
408    }
409
410    #[test]
411    fn databricks_enables_exactly_the_six_staged_gates() {
412        // The capstone: sided semi-/anti-joins, semi-structured access, QUALIFY,
413        // GROUP BY ALL, ORDER BY ALL, and LATERAL VIEW are on, and each is off in the
414        // ANSI base it derives from. Forcing the gates back off recovers the ANSI
415        // sub-presets verbatim.
416        let ansi = FeatureSet::ANSI;
417        let dbx = FeatureSet::DATABRICKS;
418
419        assert!(dbx.join_syntax.sided_semi_anti_join);
420        assert!(!ansi.join_syntax.sided_semi_anti_join);
421        // The side-less DuckDB spelling stays off (deferred).
422        assert!(!dbx.join_syntax.semi_anti_join);
423        assert!(dbx.expression_syntax.semi_structured_access);
424        assert!(!ansi.expression_syntax.semi_structured_access);
425        assert!(dbx.select_syntax.qualify && !ansi.select_syntax.qualify);
426        assert!(dbx.grouping_syntax.group_by_all && !ansi.grouping_syntax.group_by_all);
427        // Unlike Snowflake, Databricks ships ORDER BY ALL alongside GROUP BY ALL.
428        assert!(dbx.grouping_syntax.order_by_all && !ansi.grouping_syntax.order_by_all);
429        // The Spark-inherited LATERAL VIEW clause; the derived-table LATERAL factor
430        // stays off, so `LATERAL` leads only the view clause under this preset.
431        assert!(dbx.select_syntax.lateral_view_clause && !ansi.select_syntax.lateral_view_clause);
432        assert!(!dbx.table_factor_syntax.lateral);
433
434        assert_eq!(
435            SelectSyntax {
436                qualify: false,
437                lateral_view_clause: false,
438                ..dbx.select_syntax
439            },
440            ansi.select_syntax,
441        );
442        assert_eq!(
443            GroupingSyntax {
444                group_by_all: false,
445                order_by_all: false,
446                ..dbx.grouping_syntax
447            },
448            ansi.grouping_syntax,
449        );
450        assert_eq!(
451            ExpressionSyntax {
452                semi_structured_access: false,
453                ..dbx.expression_syntax
454            },
455            ansi.expression_syntax,
456        );
457        assert_eq!(
458            JoinSyntax {
459                sided_semi_anti_join: false,
460                ..dbx.join_syntax
461            },
462            ansi.join_syntax,
463        );
464    }
465
466    #[test]
467    fn databricks_is_lexically_consistent_and_dependency_clean() {
468        // Both self-consistency registries must be clean: the backtick quote and the
469        // semi-structured `:` trigger each have a single claimant (colon parameters stay
470        // off, `double_quoted_strings` off), and none of the five contextual gates rides an
471        // unset base flag.
472        let dbx = FeatureSet::DATABRICKS;
473        assert_eq!(dbx.lexical_conflict(), None);
474        assert!(dbx.is_lexically_consistent());
475        assert_eq!(dbx.feature_dependencies(), None);
476        assert!(dbx.has_satisfied_feature_dependencies());
477        assert_eq!(dbx.grammar_conflict(), None);
478        assert!(dbx.has_no_grammar_conflict());
479    }
480}