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}