squonk_ast/dialect/hive.rs
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Moderately AI Inc.
3
4//! The Hive / HiveQL dialect preset (ANSI-derived, deliberately conservative).
5//!
6//! Apache Hive's HiveQL diverges widely across its type, function, and statement surface, and
7//! — unlike the five shipped oracle-compared presets — this workspace has **no Hive oracle**,
8//! so over-acceptance cannot be measured. Conservatism is therefore the honesty bar: this
9//! preset derives from [`FeatureSet::ANSI`], the strict standard baseline, and enables only the
10//! Hive surface that already has a modelled, tested parser gate and clear documentary evidence
11//! — in the headline case the flag's *own* doc names Hive as a motivating dialect. Every other
12//! axis keeps its ANSI value; a reader can predict from this module exactly what Hive accepts
13//! beyond the standard, and unsupported Hive syntax is a clean reject routed to a focused
14//! follow-up ticket, never a silent over-accept.
15//!
16//! # What this preset adds over ANSI
17//!
18//! Two grammar gates carry the headline Hive surface:
19//!
20//! - [`sided_semi_anti_join`](JoinSyntax::sided_semi_anti_join) — the
21//! `{LEFT|RIGHT} {SEMI|ANTI} JOIN` sided semi-/anti-join spelling, whose flag doc names
22//! Spark/Hive/Databricks as the motivating family. Hive is the dialect that *originated* the
23//! `LEFT SEMI JOIN`; the Databricks preset already turned this flag on, and Hive is the
24//! second engine preset to enable it. The leading `LEFT`/`RIGHT` is already a reserved join
25//! side, so no reserved-word interplay is needed (the preceding factor's alias can never
26//! swallow it). See the over-acceptance deferral note below.
27//! - [`lateral_view_clause`](SelectSyntax::lateral_view_clause) — the
28//! `LATERAL VIEW [OUTER] explode(col) t [AS a, b]` table-generating clause Hive
29//! originated (LanguageManual LateralView: `fromClause: FROM baseTable (lateralView)*`).
30//! The derived-table `LATERAL` factor stays off, so `LATERAL` leads only this clause
31//! under the preset. See the `AS`-optional over-acceptance deferral note below.
32//!
33//! # The two lexical facts over ANSI
34//!
35//! - **Backtick identifier quoting.** Hive quotes identifiers with the backtick `` `name` ``
36//! alone (its default `hive.support.quoted.identifiers=column` mode), and its `"…"` and `'…'`
37//! are *both* string literals — HiveQL string literals may be written with single or double
38//! quotes. So [`HIVE_IDENTIFIER_QUOTES`] lists only the backtick (unlike the
39//! SQLite/Databricks/MSSQL bracket-or-double-quote sets, and unlike ANSI's `"…"`), and
40//! [`double_quoted_strings`](StringLiteralSyntax::double_quoted_strings) is correspondingly
41//! **on** so `"x"` lexes as a string, never an identifier — exactly the MySQL default
42//! (`ANSI_QUOTES` off) lexis, and the same shape the BigQuery preset ships. The two facts are
43//! coupled: `"` has a single claimant (the string scanner) precisely because it is absent
44//! from the quote set, the [`DoubleQuoteStringVersusIdentifier`](super::LexicalConflict)
45//! hazard the `const` assert below rules out. The backtick likewise has a single claimant —
46//! no enabled expression grammar lexes a backtick.
47//! - **Case folding.** Hive resolves unquoted identifiers case-insensitively (its metastore
48//! lowercases table and column names), so [`identifier_casing`](FeatureSet::identifier_casing)
49//! is [`Casing::Lower`] — the value the [`Casing`] doc prescribes for the "case-preserving
50//! storage, case-insensitive comparison" model it describes for MySQL/T-SQL columns. Hive is
51//! uniformly case-insensitive (it lacks even the case-sensitive-table split that paragraph
52//! flags as the single-fold known limitation), so the fit is exact. The interned text still
53//! renders exactly as written; the fold is identity-only and never affects acceptance.
54//!
55//! # Deliberately deferred (conservative reject)
56//!
57//! - **`RIGHT`/`ANTI` sided joins.** Classic Hive documents only `LEFT SEMI JOIN` (and later
58//! `LEFT ANTI JOIN`); the atomic [`sided_semi_anti_join`](JoinSyntax::sided_semi_anti_join) flag also admits the `RIGHT`-sided
59//! spelling and the `ANTI` flavour across both sides — a known conservative-direction
60//! over-acceptance a future side-refinement (or a Hive oracle) would tighten. This mirrors
61//! the deferral the Databricks preset recorded for the same atomic flag; captured on the
62//! owning ticket rather than split into a narrower gate that no measured boundary yet
63//! justifies.
64//! - **`AS`-less lateral-view column aliases.** Hive's grammar requires the `AS` before a
65//! lateral view's column aliases; Spark's spells it `AS?`, and the one atomic
66//! [`lateral_view_clause`](SelectSyntax::lateral_view_clause) gate accepts the wider
67//! Spark bound — a known conservative-direction over-acceptance for this preset, the
68//! same class as the sided-join deferral above (see the [`LateralView`](crate::ast::LateralView)
69//! acceptance-bound doc); captured on the owning ticket.
70//!
71//! Everything else Hive (the `STRUCT`/`ARRAY<…>`/`MAP<…>` complex type surface,
72//! `TRANSFORM`/`MAP`/`REDUCE` script operators, `DISTRIBUTE BY`/`SORT BY`/`CLUSTER BY` clauses,
73//! `TABLESAMPLE` bucketing, backslash string escapes, …) has no modelled gate and is a clean
74//! reject routed to follow-up tickets, never a silent over-accept.
75
76use super::{
77 AccessControlSyntax, AggregateCallSyntax, CallSyntax, CaretOperator, Casing,
78 ColumnDefinitionSyntax, CommentSyntax, ConstraintSyntax, CreateTableClauseSyntax,
79 DoubleAmpersand, ExistenceGuards, ExpressionSyntax, FeatureSet, GroupingSyntax,
80 IdentifierQuote, IdentifierSyntax, IndexAlterSyntax, JoinSyntax, KeywordOperators, KeywordSet,
81 MaintenanceSyntax, MutationSyntax, NullOrdering, NumericLiteralSyntax, OperatorSyntax,
82 ParameterSyntax, PipeOperator, PredicateSyntax, QueryTailSyntax, RESERVED_BARE_ALIAS,
83 RESERVED_COLUMN_NAME, RESERVED_FUNCTION_NAME, RESERVED_TYPE_NAME, STANDARD_BYTE_CLASSES,
84 SelectSyntax, SessionVariableSyntax, ShowSyntax, StatementDdlGates, StringFuncForms,
85 StringLiteralSyntax, TableExpressionSyntax, TableFactorSyntax, TargetSpelling, TypeNameSyntax,
86 UtilitySyntax,
87};
88use crate::precedence::{STANDARD_BINDING_POWERS, STANDARD_SET_OPERATION_BINDING_POWERS};
89
90/// Hive identifier quoting: the backtick `` `…` `` alone. Hive spells a quoted identifier
91/// `` `a` `` (its default `hive.support.quoted.identifiers=column` mode); its `"a"` and `'a'`
92/// are *both* string constants, so `"` is deliberately absent here (and
93/// [`StringLiteralSyntax::HIVE`] turns `double_quoted_strings` on so `"` unambiguously lexes a
94/// string) — the same backtick-only lexis MySQL uses under its default `ANSI_QUOTES`-off mode.
95pub const HIVE_IDENTIFIER_QUOTES: &[IdentifierQuote] = &[IdentifierQuote::Symmetric('`')];
96
97impl StringLiteralSyntax {
98 /// Hive string surface: the ANSI baseline plus `"…"` double-quoted string constants (HiveQL
99 /// string literals may be written with single *or* double quotes, reserving the backtick
100 /// for identifiers). Enabling this is what lets [`HIVE_IDENTIFIER_QUOTES`] drop `"` from the
101 /// quote set without stranding the byte. Every other string knob is conservatively ANSI —
102 /// Hive's backslash escape sequences have no Hive-citing flag doc or oracle here and are
103 /// deferred rather than guessed at (`backslash_escapes` stays off).
104 pub const HIVE: Self = Self {
105 double_quoted_strings: true,
106 ..StringLiteralSyntax::ANSI
107 };
108}
109
110impl TableExpressionSyntax {
111 /// Hive table-expression surface: the ANSI baseline plus the sided
112 /// `{LEFT|RIGHT} {SEMI|ANTI} JOIN` family, whose flag doc names Hive as a motivating
113 /// dialect (Hive originated `LEFT SEMI JOIN`). The side-less DuckDB spelling
114 /// (`semi_anti_join`) stays off — it is a different engine family and needs `SEMI`/`ANTI`
115 /// bare-alias reservation modelling not done here. Every other table knob is conservatively
116 /// ANSI.
117 pub const HIVE: Self = Self {
118 ..TableExpressionSyntax::ANSI
119 };
120}
121
122impl JoinSyntax {
123 /// The `HIVE` preset for join syntax.
124 pub const HIVE: Self = Self {
125 sided_semi_anti_join: true,
126 ..JoinSyntax::ANSI
127 };
128}
129
130impl TableFactorSyntax {
131 /// The `HIVE` preset for table factor syntax.
132 pub const HIVE: Self = Self {
133 ..TableFactorSyntax::ANSI
134 };
135}
136
137impl SelectSyntax {
138 /// Hive SELECT surface: the ANSI baseline plus the `LATERAL VIEW` table-generating
139 /// clause Hive originated (LanguageManual LateralView). The derived-table `LATERAL`
140 /// factor ([`TableFactorSyntax::HIVE`]) stays off, so `LATERAL` leads only this
141 /// clause under the preset. Every other SELECT knob is conservatively ANSI.
142 pub const HIVE: Self = Self {
143 lateral_view_clause: true,
144 ..SelectSyntax::ANSI
145 };
146}
147
148impl FeatureSet {
149 /// Hive / HiveQL as ANSI-derived dialect data (see the module docs for the full derivation
150 /// rationale and the conservatism bar).
151 pub const HIVE: Self = Self {
152 // Hive resolves unquoted identifiers case-insensitively (its metastore lowercases table
153 // and column names); `Casing::Lower` is the exact fit (Hive lacks even the
154 // case-sensitive-table split the `Casing` known-limitation paragraph flags). Identity
155 // only — the interned text still renders exactly as written, so this never affects
156 // acceptance.
157 identifier_casing: Casing::Lower,
158 // The lexical delta over ANSI: backtick-only identifier quoting (with `"` handed to the
159 // string scanner via `double_quoted_strings` below).
160 identifier_quotes: HIVE_IDENTIFIER_QUOTES,
161 default_null_ordering: NullOrdering::NullsLast,
162 // No reserved-set delta over ANSI — this conservative preset adds no keyword
163 // reservation (the sided-join gate rides the already-reserved LEFT/RIGHT join sides).
164 reserved_column_name: RESERVED_COLUMN_NAME,
165 reserved_function_name: RESERVED_FUNCTION_NAME,
166 reserved_type_name: RESERVED_TYPE_NAME,
167 reserved_bare_alias: RESERVED_BARE_ALIAS,
168 reserved_as_label: KeywordSet::EMPTY,
169 catalog_qualified_names: true,
170 byte_classes: STANDARD_BYTE_CLASSES,
171 binding_powers: STANDARD_BINDING_POWERS,
172 set_operation_powers: STANDARD_SET_OPERATION_BINDING_POWERS,
173 // `"…"` double-quoted strings (the coupled half of the backtick-only identifier lexis).
174 string_literals: StringLiteralSyntax::HIVE,
175 numeric_literals: NumericLiteralSyntax::ANSI,
176 parameters: ParameterSyntax::ANSI,
177 session_variables: SessionVariableSyntax::ANSI,
178 identifier_syntax: IdentifierSyntax::ANSI,
179 // The sided `{LEFT|RIGHT} {SEMI|ANTI} JOIN` family — the capstone this preset exposes.
180 table_expressions: TableExpressionSyntax::HIVE,
181 join_syntax: JoinSyntax::HIVE,
182 table_factor_syntax: TableFactorSyntax::HIVE,
183 expression_syntax: ExpressionSyntax::ANSI,
184 operator_syntax: OperatorSyntax::ANSI,
185 call_syntax: CallSyntax::ANSI,
186 string_func_forms: StringFuncForms::ANSI,
187 aggregate_call_syntax: AggregateCallSyntax::ANSI,
188 predicate_syntax: PredicateSyntax::ANSI,
189 pipe_operator: PipeOperator::StringConcat,
190 double_ampersand: DoubleAmpersand::Unsupported,
191 keyword_operators: KeywordOperators::Unsupported,
192 caret_operator: CaretOperator::Unsupported,
193 hash_bitwise_xor: false,
194 comment_syntax: CommentSyntax::ANSI,
195 mutation_syntax: MutationSyntax::ANSI,
196 statement_ddl_gates: StatementDdlGates::ANSI,
197 create_table_clause_syntax: CreateTableClauseSyntax::ANSI,
198 column_definition_syntax: ColumnDefinitionSyntax::ANSI,
199 constraint_syntax: ConstraintSyntax::ANSI,
200 index_alter_syntax: IndexAlterSyntax::ANSI,
201 existence_guards: ExistenceGuards::ANSI,
202 // The `LATERAL VIEW` table-generating clause — the second capstone this preset
203 // exposes; every other SELECT knob is conservatively ANSI.
204 select_syntax: SelectSyntax::HIVE,
205 query_tail_syntax: QueryTailSyntax::ANSI,
206 grouping_syntax: GroupingSyntax::ANSI,
207 utility_syntax: UtilitySyntax::ANSI,
208 show_syntax: ShowSyntax::ANSI,
209 maintenance_syntax: MaintenanceSyntax::ANSI,
210 access_control_syntax: AccessControlSyntax::ANSI,
211 type_name_syntax: TypeNameSyntax::ANSI,
212 // No Hive-specific Tier-1 output spelling yet; render the portable ANSI canonical type
213 // names (a `TargetSpelling::Hive` is render work a later ticket owns).
214 target_spelling: TargetSpelling::Ansi,
215 };
216}
217
218/// Prefer [`FeatureSet::HIVE`] for struct update.
219pub const HIVE: FeatureSet = FeatureSet::HIVE;
220
221// Compile-time proof the Hive preset claims no shared tokenizer trigger twice. Beyond ANSI it
222// adds one lexical trigger — the backtick identifier opener — with a single claimant (no
223// enabled expression grammar lexes a backtick), and it hands `"` to the string scanner
224// (`double_quoted_strings` on) *while dropping `"` from the identifier quote set*, so `"` also
225// keeps a single claimant. The sided-join gate is contextual keyword grammar with no tokenizer
226// trigger. Kept as a ratchet so a future Hive delta that *does* add a contending trigger (e.g.
227// re-listing `"` as an identifier quote) fails the build here.
228const _: () = assert!(FeatureSet::HIVE.is_lexically_consistent());
229// The two sibling self-consistency registries are ratcheted the same way, so the
230// parse-entry `debug_assert!` folds all three to dead code for this preset: no refinement
231// flag rides an unset base, and no two features contend for one parser-position head.
232const _: () = assert!(FeatureSet::HIVE.has_satisfied_feature_dependencies());
233const _: () = assert!(FeatureSet::HIVE.has_no_grammar_conflict());
234
235#[cfg(test)]
236mod tests {
237 use super::*;
238
239 #[test]
240 fn hive_is_ansi_plus_the_two_gates_and_two_lexical_facts() {
241 // The preset is ANSI with a documented, closed set of divergent axes: the two lexical
242 // facts (case-folding, backtick-only quoting coupled with double-quoted strings), the
243 // enabled table-expression gate, and the enabled SELECT gate. Asserting the whole rest
244 // equals ANSI keeps the "ANSI-derived, every delta documented" claim honest against a
245 // future stray edit. Bind to locals so the const reads are not flagged by clippy's
246 // `assertions_on_constants`.
247 let ansi = FeatureSet::ANSI;
248 let hive = FeatureSet::HIVE;
249
250 // The two lexical facts.
251 assert_eq!(hive.identifier_casing, Casing::Lower);
252 assert_ne!(hive.identifier_casing, ansi.identifier_casing);
253 assert_eq!(hive.identifier_quotes, HIVE_IDENTIFIER_QUOTES);
254 assert_ne!(hive.identifier_quotes, ansi.identifier_quotes);
255 // The coupling: `"` is dropped from the quote set and handed to the string scanner.
256 assert!(hive.string_literals.double_quoted_strings);
257 assert!(
258 !hive
259 .identifier_quotes
260 .iter()
261 .any(|quote| quote.open() == '"')
262 );
263 // Backtick is the sole identifier quote; no bracket (unlike SQLite/MSSQL) and no `"`.
264 assert!(
265 hive.identifier_quotes
266 .iter()
267 .any(|quote| quote.open() == '`')
268 );
269 assert_eq!(hive.identifier_quotes.len(), 1);
270
271 // The one divergent string sub-preset (the double-quote coupling).
272 assert_eq!(hive.string_literals, StringLiteralSyntax::HIVE);
273 assert_ne!(hive.string_literals, ansi.string_literals);
274 // The divergent table-expression sub-preset.
275 assert_eq!(hive.table_expressions, TableExpressionSyntax::HIVE);
276 assert_eq!(hive.join_syntax, JoinSyntax::HIVE);
277 assert_ne!(hive.join_syntax, ansi.join_syntax);
278 // The divergent SELECT sub-preset (the LATERAL VIEW clause).
279 assert_eq!(hive.select_syntax, SelectSyntax::HIVE);
280 assert_ne!(hive.select_syntax, ansi.select_syntax);
281
282 // No reserved-set delta: every position is inherited verbatim from ANSI.
283 assert_eq!(hive.reserved_column_name, ansi.reserved_column_name);
284 assert_eq!(hive.reserved_function_name, ansi.reserved_function_name);
285 assert_eq!(hive.reserved_type_name, ansi.reserved_type_name);
286 assert_eq!(hive.reserved_bare_alias, ansi.reserved_bare_alias);
287 assert_eq!(hive.reserved_as_label, KeywordSet::EMPTY);
288
289 // Everything else is inherited verbatim from ANSI.
290 assert_eq!(hive.numeric_literals, ansi.numeric_literals);
291 assert_eq!(hive.parameters, ansi.parameters);
292 assert_eq!(hive.expression_syntax, ansi.expression_syntax);
293 assert_eq!(hive.session_variables, ansi.session_variables);
294 assert_eq!(hive.identifier_syntax, ansi.identifier_syntax);
295 assert_eq!(hive.operator_syntax, ansi.operator_syntax);
296 assert_eq!(hive.call_syntax, ansi.call_syntax);
297 assert_eq!(hive.predicate_syntax, ansi.predicate_syntax);
298 assert_eq!(hive.mutation_syntax, ansi.mutation_syntax);
299 assert_eq!(hive.statement_ddl_gates, ansi.statement_ddl_gates);
300 assert_eq!(
301 hive.create_table_clause_syntax,
302 ansi.create_table_clause_syntax
303 );
304 assert_eq!(hive.column_definition_syntax, ansi.column_definition_syntax);
305 assert_eq!(hive.constraint_syntax, ansi.constraint_syntax);
306 assert_eq!(hive.index_alter_syntax, ansi.index_alter_syntax);
307 assert_eq!(hive.existence_guards, ansi.existence_guards);
308 assert_eq!(hive.utility_syntax, ansi.utility_syntax);
309 assert_eq!(hive.type_name_syntax, ansi.type_name_syntax);
310 assert_eq!(hive.byte_classes, ansi.byte_classes);
311 assert_eq!(hive.binding_powers, ansi.binding_powers);
312 assert_eq!(hive.target_spelling, ansi.target_spelling);
313 assert_eq!(hive.default_null_ordering, ansi.default_null_ordering);
314 }
315
316 #[test]
317 fn hive_enables_exactly_the_two_gates_and_double_quote_string() {
318 // The capstones: the sided semi-/anti-join family, the LATERAL VIEW clause, and
319 // double-quoted strings are on, and each is off in the ANSI base it derives from.
320 // Forcing the flags back off recovers the ANSI sub-presets verbatim.
321 let ansi = FeatureSet::ANSI;
322 let hive = FeatureSet::HIVE;
323
324 assert!(hive.join_syntax.sided_semi_anti_join);
325 assert!(!ansi.join_syntax.sided_semi_anti_join);
326 // The side-less DuckDB spelling stays off (a different engine family).
327 assert!(!hive.join_syntax.semi_anti_join);
328 // The LATERAL VIEW clause; the derived-table LATERAL factor stays off, so
329 // `LATERAL` leads only the view clause under this preset.
330 assert!(hive.select_syntax.lateral_view_clause && !ansi.select_syntax.lateral_view_clause);
331 assert!(!hive.table_factor_syntax.lateral);
332 assert!(
333 hive.string_literals.double_quoted_strings
334 && !ansi.string_literals.double_quoted_strings
335 );
336 // The backtick opener is the lexical gate (an identifier-quote delta, not a bool).
337 assert!(
338 hive.identifier_quotes
339 .iter()
340 .any(|quote| quote.open() == '`')
341 );
342 assert!(
343 !ansi
344 .identifier_quotes
345 .iter()
346 .any(|quote| quote.open() == '`')
347 );
348
349 assert_eq!(
350 JoinSyntax {
351 sided_semi_anti_join: false,
352 ..hive.join_syntax
353 },
354 ansi.join_syntax,
355 );
356 assert_eq!(
357 SelectSyntax {
358 lateral_view_clause: false,
359 ..hive.select_syntax
360 },
361 ansi.select_syntax,
362 );
363 assert_eq!(
364 StringLiteralSyntax {
365 double_quoted_strings: false,
366 ..hive.string_literals
367 },
368 ansi.string_literals,
369 );
370 }
371
372 #[test]
373 fn hive_is_lexically_consistent_and_dependency_clean() {
374 // Both self-consistency registries must be clean: the backtick quote has a single
375 // claimant, `"` is handed to the string scanner (dropped from the quote set), and the
376 // sided-join gate rides no unset base flag.
377 let hive = FeatureSet::HIVE;
378 assert_eq!(hive.lexical_conflict(), None);
379 assert!(hive.is_lexically_consistent());
380 assert_eq!(hive.feature_dependencies(), None);
381 assert!(hive.has_satisfied_feature_dependencies());
382 assert_eq!(hive.grammar_conflict(), None);
383 assert!(hive.has_no_grammar_conflict());
384 }
385}