squonk_ast/dialect/mssql.rs
1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Moderately AI Inc.
3
4//! The MSSQL / T-SQL dialect preset (ANSI-derived, deliberately conservative).
5//!
6//! Microsoft SQL Server's T-SQL diverges widely across its type, function, and statement
7//! surface, and — unlike the five shipped oracle-compared presets — this workspace has **no
8//! MSSQL oracle**, so over-acceptance cannot be measured. Conservatism is therefore the
9//! honesty bar: this preset derives from [`FeatureSet::ANSI`], the strict standard baseline,
10//! and enables only the T-SQL surface that already has a modelled, tested parser gate and
11//! clear documentary evidence — in six cases the flag's *own* doc names T-SQL as the
12//! motivating dialect. Every other axis keeps its ANSI value; a reader can predict from this
13//! module exactly what MSSQL accepts beyond the standard, and unsupported T-SQL 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//! Seven gates, each documented (in the flag's own doc) as T-SQL surface:
19//!
20//! - [`apply_join`](JoinSyntax::apply_join) — the `CROSS APPLY` / `OUTER APPLY`
21//! lateral-correlated join operators. This is the flag this preset exists to make real: it
22//! shipped staged (Lenient-only) before this preset gave it an engine home. The leading
23//! `CROSS`/`OUTER` keyword anchors the operator, so no reserved-word interplay is needed
24//! (the preceding factor's alias can never swallow it).
25//! - [`table_hints`](TableExpressionSyntax::table_hints) — the `WITH ( <hint>, … )` table-hint
26//! tail (`FROM t WITH (NOLOCK)`, `WITH (INDEX(ix), FORCESEEK)`), a T-SQL locking / optimizer
27//! directive on a table factor. Reached only through the `WITH (` sequence at the
28//! table-factor tail, where `WITH` is never otherwise consumed on a base table, so it never
29//! contends with the leading-`WITH` CTE clause at statement start; when off (ANSI base) the
30//! trailing `WITH` is left unconsumed and the construct is a clean reject. The common
31//! documented hints ([`TableHintKeyword`](crate::ast::TableHintKeyword)) are typed for
32//! planner consumers; an unrecognized word is preserved verbatim
33//! ([`TableHint::Other`](crate::ast::TableHint)). Numeric index ids (`INDEX(0)`) and
34//! the legacy no-`WITH` parenthesized form (`FROM t (NOLOCK)`) are conservative deferrals.
35//! - [`table_version`](TableExpressionSyntax::table_version) — the temporal-table
36//! `FOR SYSTEM_TIME {AS OF | FROM..TO | BETWEEN..AND | CONTAINED IN | ALL}` query modifier
37//! on a table factor, a typed [`TableVersion`](crate::ast::TableVersion) for planner
38//! consumers. It sits at the table-factor position (right after the table name, before the
39//! alias), so its `FOR SYSTEM_TIME` trigger never contends with the query-level `FOR XML` /
40//! `FOR JSON` tail or the `FOR` locking clause — those are read only after the whole
41//! `FROM`/`WHERE`, and the word after `FOR` (`SYSTEM_TIME` vs `XML`) partitions them.
42//! - Bracket identifiers `[name]` — T-SQL's signature delimiter, modelled by the
43//! [`IdentifierQuote::Asymmetric`] `{ open: '[', close: ']' }` style whose own doc cites
44//! T-SQL. Listed in [`MSSQL_IDENTIFIER_QUOTES`] alongside the standard `"…"`. Enabling the
45//! `[` opener would contend with the `[`-punctuation expression grammar
46//! ([`ExpressionSyntax::subscript`] / [`array_constructor`](ExpressionSyntax::array_constructor)
47//! / [`collection_literals`](ExpressionSyntax::collection_literals), the
48//! [`LexicalConflict::BracketIdentifierVersusArraySyntax`](super::LexicalConflict) hazard),
49//! but all three are **off** in the ANSI base this preset keeps — and that is
50//! behaviour-accurate: T-SQL genuinely lacks `[]` array subscripting, so the bracket is
51//! unambiguously an identifier delimiter here. The lexical-consistency `const` assert below
52//! enforces the single claimant.
53//! - [`named_at`](ParameterSyntax::named_at) — the `@name` parameter / local-variable sigil,
54//! whose own doc names T-SQL. Its `@name` trigger contends with
55//! [`SessionVariableSyntax::user_variables`] (MySQL's `@name` read), which stays off (ANSI's
56//! value) so `@name` has a single claimant — the
57//! [`AtNameParameterVersusUserVariable`](super::LexicalConflict) hazard the assert below
58//! rules out. The `@@name` system-variable form is disjoint (its second `@` is not an
59//! identifier byte) and stays off too.
60//! - [`national_strings`](StringLiteralSyntax::national_strings) — `N'…'` national-character
61//! string constants, whose own doc names T-SQL. A pure lexical gate over the ANSI string
62//! surface; `backslash_escapes` stays off (its doc reads "MySQL default; not T-SQL").
63//! - [`money_literals`](NumericLiteralSyntax::money_literals) — `$1234.56` / `$.5` money
64//! literals (the `$` currency sigil prefixes a decimal), whose own doc names T-SQL. Its
65//! `$`+digit trigger contends with [`ParameterSyntax::positional_dollar`] (PostgreSQL's
66//! `$1`), which stays off (ANSI's value) so `$`+digit has a single claimant — the
67//! [`MoneyVersusPositionalDollar`](super::LexicalConflict) hazard the assert below rules
68//! out.
69//!
70//! # The two lexical facts over ANSI
71//!
72//! - **Dual identifier quoting.** T-SQL quotes identifiers with the bracket `[…]` **and** the
73//! standard `"…"` (its default `QUOTED_IDENTIFIER ON`). [`MSSQL_IDENTIFIER_QUOTES`] lists
74//! both; T-SQL has no MySQL-style backtick, so — unlike SQLite/Databricks — no `` ` `` opener
75//! appears. `double_quoted_strings` stays off (ANSI's value) so `"x"` reads as an
76//! identifier, keeping `QUOTED_IDENTIFIER ON` behaviour and the preset lexically consistent.
77//! - **Case folding.** T-SQL identifier resolution is case-insensitive (collation-dependent),
78//! so [`identifier_casing`](FeatureSet::identifier_casing) is [`Casing::Lower`] — the
79//! [`Casing`] doc names T-SQL as one of the two dialects [`Casing::Lower`] approximates
80//! ("case-preserving storage, case-insensitive comparison"). The interned text still renders
81//! exactly as written; the fold is identity-only and never affects acceptance. (The
82//! per-identifier-kind table-vs-column sensitivity split is a documented `Casing` limitation
83//! and a deliberate future extension, not modelled here.)
84//!
85//! # Deliberately deferred (conservative reject)
86//!
87//! `SELECT … INTO <table>` stays off: [`select_into`](SelectSyntax::select_into)'s own doc
88//! models only PostgreSQL's `SELECT … INTO [TEMP] <table>` create-table form and does not cite
89//! T-SQL, and with no MSSQL oracle the exact boundary (T-SQL has no `TEMP` keyword — it spells
90//! temp tables `#name` instead) cannot be verified, so shipping the PG-shaped gate would be an
91//! unmeasured guess. Likewise `TOP (n)`, `GO` batch separators,
92//! `#temp` tables, the `OUTPUT` clause, and the T-SQL `MERGE` variants have no modelled gate;
93//! several are already catalogued by the structural-extensibility spike. All are clean rejects
94//! routed to follow-up tickets, never silent over-accepts.
95
96use super::{
97 AccessControlSyntax, AggregateCallSyntax, CallSyntax, CaretOperator, Casing,
98 ColumnDefinitionSyntax, CommentSyntax, ConstraintSyntax, CreateTableClauseSyntax,
99 DoubleAmpersand, ExistenceGuards, ExpressionSyntax, FeatureSet, GroupingSyntax,
100 IdentifierQuote, IdentifierSyntax, IndexAlterSyntax, JoinSyntax, KeywordOperators, KeywordSet,
101 MaintenanceSyntax, MutationSyntax, NullOrdering, NumericLiteralSyntax, OperatorSyntax,
102 ParameterSyntax, PipeOperator, PredicateSyntax, QueryTailSyntax, RESERVED_BARE_ALIAS,
103 RESERVED_COLUMN_NAME, RESERVED_FUNCTION_NAME, RESERVED_TYPE_NAME, STANDARD_BYTE_CLASSES,
104 SelectSyntax, SessionVariableSyntax, ShowSyntax, StatementDdlGates, StringFuncForms,
105 StringLiteralSyntax, TableExpressionSyntax, TableFactorSyntax, TargetSpelling, TypeNameSyntax,
106 UtilitySyntax,
107};
108use crate::precedence::{STANDARD_BINDING_POWERS, STANDARD_SET_OPERATION_BINDING_POWERS};
109
110/// MSSQL / T-SQL identifier quoting: the bracket `[…]` **and** the SQL standard `"…"`, both at
111/// once. The two openers are distinct bytes, so their order is immaterial. T-SQL has no
112/// MySQL-style backtick, so — unlike the SQLite/Lenient bracket sets — no `` ` `` opener
113/// appears. `"` stays a quote here (and `double_quoted_strings` is correspondingly off in
114/// [`StringLiteralSyntax::ANSI`], which this preset keeps, matching T-SQL's default
115/// `QUOTED_IDENTIFIER ON`), so `"x"` is an identifier, never a string.
116pub const MSSQL_IDENTIFIER_QUOTES: &[IdentifierQuote] = &[
117 IdentifierQuote::Symmetric('"'),
118 IdentifierQuote::Asymmetric {
119 open: '[',
120 close: ']',
121 },
122];
123
124impl StringLiteralSyntax {
125 /// MSSQL string surface: the ANSI baseline plus `N'…'` national-character string constants.
126 /// `backslash_escapes` stays off (its doc reads "MySQL default; not T-SQL"). Every other
127 /// string knob is conservatively ANSI.
128 pub const MSSQL: Self = Self {
129 national_strings: true,
130 ..StringLiteralSyntax::ANSI
131 };
132}
133
134impl NumericLiteralSyntax {
135 /// MSSQL numeric surface: the ANSI baseline plus `$1234.56` / `$.5` money literals. Every
136 /// other numeric knob is conservatively ANSI.
137 pub const MSSQL: Self = Self {
138 money_literals: true,
139 ..NumericLiteralSyntax::ANSI
140 };
141}
142
143impl ParameterSyntax {
144 /// MSSQL parameter surface: the ANSI baseline plus the `@name` parameter / local-variable
145 /// sigil. `positional_dollar` stays off (ANSI's value) so `$`+digit belongs solely to
146 /// [`NumericLiteralSyntax::money_literals`], and `user_variables` stays off (in
147 /// [`SessionVariableSyntax::ANSI`]) so `@name` belongs solely to this — both enforced by
148 /// the lexical assert below.
149 pub const MSSQL: Self = Self {
150 named_at: true,
151 ..ParameterSyntax::ANSI
152 };
153}
154
155impl TableExpressionSyntax {
156 /// MSSQL table-expression surface: the ANSI baseline plus the `WITH (...)` table-hint
157 /// tail and the temporal-table `FOR SYSTEM_TIME` modifier (all five forms). The
158 /// `CROSS APPLY` / `OUTER APPLY` join operators ride [`JoinSyntax`]; every other table
159 /// knob is conservatively ANSI.
160 pub const MSSQL: Self = Self {
161 // `WITH (NOLOCK)` / `WITH (INDEX(ix), FORCESEEK)` table hints — see the module docs.
162 table_hints: true,
163 // `FOR SYSTEM_TIME {AS OF | FROM..TO | BETWEEN..AND | CONTAINED IN | ALL}` — the
164 // temporal-table query modifier.
165 table_version: true,
166 ..TableExpressionSyntax::ANSI
167 };
168}
169
170impl JoinSyntax {
171 /// The `MSSQL` preset for join syntax.
172 pub const MSSQL: Self = Self {
173 apply_join: true,
174 ..JoinSyntax::ANSI
175 };
176}
177
178impl QueryTailSyntax {
179 /// MSSQL query-tail surface: the ANSI baseline plus the `FOR XML`/`FOR JSON`
180 /// result-shaping tail. MSSQL has no query-tail row-locking clause (it spells
181 /// isolation with `WITH (…)` table hints, [`TableExpressionSyntax::MSSQL`]), so
182 /// `locking_clauses` stays off and the `FOR` lead is unambiguously the
183 /// result-shaping clause here.
184 pub const MSSQL: Self = Self {
185 for_xml_json_clause: true,
186 ..QueryTailSyntax::ANSI
187 };
188}
189
190impl TableFactorSyntax {
191 /// The `MSSQL` preset for table factor syntax.
192 pub const MSSQL: Self = Self {
193 // SQL Server's `OPENJSON(<json> [, <path>]) [WITH (…)]` rowset-function table factor —
194 // the sole engine with this exact form (documented; no differential oracle here, so the
195 // gate follows the grammar on this conservative preset, the `apply_join` precedent).
196 open_json: true,
197 ..TableFactorSyntax::ANSI
198 };
199}
200
201impl FeatureSet {
202 /// MSSQL / T-SQL as ANSI-derived dialect data (see the module docs for the full derivation
203 /// rationale and the conservatism bar).
204 pub const MSSQL: Self = Self {
205 // T-SQL identifier resolution is case-insensitive (collation-dependent); `Casing::Lower`
206 // is the closest single fit (the `Casing` doc names T-SQL). Identity only — the interned
207 // text still renders exactly as written, so this never affects acceptance.
208 identifier_casing: Casing::Lower,
209 // The lexical delta over ANSI: bracket `[…]` *and* double-quote identifier quoting.
210 identifier_quotes: MSSQL_IDENTIFIER_QUOTES,
211 default_null_ordering: NullOrdering::NullsLast,
212 // No reserved-set delta over ANSI — MSSQL adds no keyword reservation here.
213 reserved_column_name: RESERVED_COLUMN_NAME,
214 reserved_function_name: RESERVED_FUNCTION_NAME,
215 reserved_type_name: RESERVED_TYPE_NAME,
216 reserved_bare_alias: RESERVED_BARE_ALIAS,
217 reserved_as_label: KeywordSet::EMPTY,
218 catalog_qualified_names: true,
219 byte_classes: STANDARD_BYTE_CLASSES,
220 binding_powers: STANDARD_BINDING_POWERS,
221 set_operation_powers: STANDARD_SET_OPERATION_BINDING_POWERS,
222 // `N'…'` national-character strings.
223 string_literals: StringLiteralSyntax::MSSQL,
224 // `$1234.56` money literals.
225 numeric_literals: NumericLiteralSyntax::MSSQL,
226 // `@name` parameters / local variables.
227 parameters: ParameterSyntax::MSSQL,
228 session_variables: SessionVariableSyntax::ANSI,
229 identifier_syntax: IdentifierSyntax::ANSI,
230 // `CROSS APPLY` / `OUTER APPLY` — the capstone this preset exposes.
231 table_expressions: TableExpressionSyntax::MSSQL,
232 join_syntax: JoinSyntax::MSSQL,
233 table_factor_syntax: TableFactorSyntax::MSSQL,
234 expression_syntax: ExpressionSyntax::ANSI,
235 operator_syntax: OperatorSyntax::ANSI,
236 call_syntax: CallSyntax::ANSI,
237 string_func_forms: StringFuncForms::ANSI,
238 aggregate_call_syntax: AggregateCallSyntax::ANSI,
239 predicate_syntax: PredicateSyntax::ANSI,
240 pipe_operator: PipeOperator::StringConcat,
241 double_ampersand: DoubleAmpersand::Unsupported,
242 keyword_operators: KeywordOperators::Unsupported,
243 caret_operator: CaretOperator::Unsupported,
244 hash_bitwise_xor: false,
245 comment_syntax: CommentSyntax::ANSI,
246 mutation_syntax: MutationSyntax::ANSI,
247 statement_ddl_gates: StatementDdlGates::ANSI,
248 create_table_clause_syntax: CreateTableClauseSyntax::ANSI,
249 column_definition_syntax: ColumnDefinitionSyntax::ANSI,
250 constraint_syntax: ConstraintSyntax::ANSI,
251 index_alter_syntax: IndexAlterSyntax::ANSI,
252 existence_guards: ExistenceGuards::ANSI,
253 // `SELECT … INTO <table>` stays off (deferred — see the module docs); every other
254 // SELECT knob is conservatively ANSI.
255 select_syntax: SelectSyntax::ANSI,
256 query_tail_syntax: QueryTailSyntax::MSSQL,
257 grouping_syntax: GroupingSyntax::ANSI,
258 utility_syntax: UtilitySyntax::ANSI,
259 show_syntax: ShowSyntax::ANSI,
260 maintenance_syntax: MaintenanceSyntax::ANSI,
261 access_control_syntax: AccessControlSyntax::ANSI,
262 type_name_syntax: TypeNameSyntax::ANSI,
263 // No MSSQL-specific Tier-1 output spelling yet; render the portable ANSI canonical type
264 // names (a `TargetSpelling::Mssql` is render work a later ticket owns).
265 target_spelling: TargetSpelling::Ansi,
266 };
267}
268
269/// Prefer [`FeatureSet::MSSQL`] for struct update.
270pub const MSSQL: FeatureSet = FeatureSet::MSSQL;
271
272// Compile-time proof the MSSQL preset claims no shared tokenizer trigger twice. Beyond ANSI it
273// adds three triggers — the `[` bracket identifier opener, the `@` named-at sigil, and the `$`
274// money sigil — each with a single claimant: no enabled expression grammar lexes `[` as
275// punctuation (`subscript`/`array_constructor`/`collection_literals` stay off, so T-SQL's
276// bracket is unambiguously an identifier delimiter), `user_variables` stays off so `@name`
277// belongs solely to `named_at`, and `positional_dollar` stays off so `$`+digit belongs solely
278// to `money_literals`. `"` stays the sole identifier quote it already was (`double_quoted_strings`
279// off). Kept as a ratchet so a future MSSQL delta that *does* add a contending trigger fails the
280// build here.
281const _: () = assert!(FeatureSet::MSSQL.is_lexically_consistent());
282// The two sibling self-consistency registries are ratcheted the same way, so the
283// parse-entry `debug_assert!` folds all three to dead code for this preset: no refinement
284// flag rides an unset base, and no two features contend for one parser-position head.
285const _: () = assert!(FeatureSet::MSSQL.has_satisfied_feature_dependencies());
286const _: () = assert!(FeatureSet::MSSQL.has_no_grammar_conflict());
287
288#[cfg(test)]
289mod tests {
290 use super::*;
291
292 #[test]
293 fn mssql_is_ansi_plus_the_six_gates_and_two_lexical_facts() {
294 // The preset is ANSI with a documented, closed set of divergent axes: the two lexical
295 // facts (case-folding, dual identifier quoting) and the four enabled sub-presets that
296 // carry the six gates (the `table_expressions` sub-preset carries two — `apply_join`
297 // and `table_hints`). Asserting the whole rest equals ANSI keeps the "ANSI-derived,
298 // every delta documented" claim honest against a future stray edit. Bind to locals so
299 // the const reads are not flagged by clippy's `assertions_on_constants`.
300 let ansi = FeatureSet::ANSI;
301 let mssql = FeatureSet::MSSQL;
302
303 // The two lexical facts.
304 assert_eq!(mssql.identifier_casing, Casing::Lower);
305 assert_ne!(mssql.identifier_casing, ansi.identifier_casing);
306 assert_eq!(mssql.identifier_quotes, MSSQL_IDENTIFIER_QUOTES);
307 assert_ne!(mssql.identifier_quotes, ansi.identifier_quotes);
308 // `"` stays an identifier quote, so MSSQL must keep `double_quoted_strings` off (the
309 // `QUOTED_IDENTIFIER ON` behaviour the preset depends on for lexical consistency).
310 assert!(!mssql.string_literals.double_quoted_strings);
311 // The single-claimant interplays the enabled sigils depend on.
312 assert!(!mssql.session_variables.user_variables);
313 assert!(!mssql.parameters.positional_dollar);
314 // T-SQL has no backtick identifier quote.
315 assert!(
316 !mssql
317 .identifier_quotes
318 .iter()
319 .any(|quote| quote.open() == '`')
320 );
321
322 // The four divergent sub-presets.
323 assert_eq!(mssql.string_literals, StringLiteralSyntax::MSSQL);
324 assert_ne!(mssql.string_literals, ansi.string_literals);
325 assert_eq!(mssql.numeric_literals, NumericLiteralSyntax::MSSQL);
326 assert_ne!(mssql.numeric_literals, ansi.numeric_literals);
327 assert_eq!(mssql.parameters, ParameterSyntax::MSSQL);
328 assert_ne!(mssql.parameters, ansi.parameters);
329 assert_eq!(mssql.table_expressions, TableExpressionSyntax::MSSQL);
330 assert_ne!(mssql.table_expressions, ansi.table_expressions);
331
332 // No reserved-set delta: every position is inherited verbatim from ANSI.
333 assert_eq!(mssql.reserved_column_name, ansi.reserved_column_name);
334 assert_eq!(mssql.reserved_function_name, ansi.reserved_function_name);
335 assert_eq!(mssql.reserved_type_name, ansi.reserved_type_name);
336 assert_eq!(mssql.reserved_bare_alias, ansi.reserved_bare_alias);
337 assert_eq!(mssql.reserved_as_label, KeywordSet::EMPTY);
338
339 // Everything else is inherited verbatim from ANSI — including SELECT (SELECT INTO is
340 // deferred, so it stays off).
341 assert_eq!(mssql.select_syntax, ansi.select_syntax);
342 assert_eq!(mssql.expression_syntax, ansi.expression_syntax);
343 assert_eq!(mssql.session_variables, ansi.session_variables);
344 assert_eq!(mssql.identifier_syntax, ansi.identifier_syntax);
345 assert_eq!(mssql.operator_syntax, ansi.operator_syntax);
346 assert_eq!(mssql.call_syntax, ansi.call_syntax);
347 assert_eq!(mssql.predicate_syntax, ansi.predicate_syntax);
348 assert_eq!(mssql.mutation_syntax, ansi.mutation_syntax);
349 assert_eq!(mssql.statement_ddl_gates, ansi.statement_ddl_gates);
350 assert_eq!(
351 mssql.create_table_clause_syntax,
352 ansi.create_table_clause_syntax
353 );
354 assert_eq!(
355 mssql.column_definition_syntax,
356 ansi.column_definition_syntax
357 );
358 assert_eq!(mssql.constraint_syntax, ansi.constraint_syntax);
359 assert_eq!(mssql.index_alter_syntax, ansi.index_alter_syntax);
360 assert_eq!(mssql.existence_guards, ansi.existence_guards);
361 assert_eq!(mssql.utility_syntax, ansi.utility_syntax);
362 assert_eq!(mssql.type_name_syntax, ansi.type_name_syntax);
363 assert_eq!(mssql.byte_classes, ansi.byte_classes);
364 assert_eq!(mssql.binding_powers, ansi.binding_powers);
365 assert_eq!(mssql.target_spelling, ansi.target_spelling);
366 assert_eq!(mssql.default_null_ordering, ansi.default_null_ordering);
367 }
368
369 #[test]
370 fn mssql_enables_exactly_the_six_staged_gates() {
371 // The capstone: CROSS/OUTER APPLY, `WITH (...)` table hints, bracket identifiers,
372 // `@name` parameters, `N'…'` national strings, and `$…` money literals are on, and each
373 // is off in the ANSI base it derives from. Forcing the flags back off recovers the ANSI
374 // sub-presets verbatim.
375 let ansi = FeatureSet::ANSI;
376 let mssql = FeatureSet::MSSQL;
377
378 assert!(mssql.join_syntax.apply_join);
379 assert!(!ansi.join_syntax.apply_join);
380 assert!(mssql.table_expressions.table_hints);
381 assert!(!ansi.table_expressions.table_hints);
382 assert!(mssql.parameters.named_at && !ansi.parameters.named_at);
383 assert!(mssql.string_literals.national_strings && !ansi.string_literals.national_strings);
384 assert!(mssql.numeric_literals.money_literals && !ansi.numeric_literals.money_literals);
385 // The bracket opener is the fifth gate (an identifier-quote delta, not a bool).
386 assert!(
387 mssql
388 .identifier_quotes
389 .iter()
390 .any(|quote| quote.open() == '[')
391 );
392 assert!(
393 !ansi
394 .identifier_quotes
395 .iter()
396 .any(|quote| quote.open() == '[')
397 );
398
399 assert_eq!(
400 TableExpressionSyntax {
401 table_hints: false,
402 table_version: false,
403 ..mssql.table_expressions
404 },
405 ansi.table_expressions,
406 );
407 assert_eq!(
408 JoinSyntax {
409 apply_join: false,
410 ..mssql.join_syntax
411 },
412 ansi.join_syntax,
413 );
414 assert_eq!(
415 ParameterSyntax {
416 named_at: false,
417 ..mssql.parameters
418 },
419 ansi.parameters,
420 );
421 assert_eq!(
422 StringLiteralSyntax {
423 national_strings: false,
424 ..mssql.string_literals
425 },
426 ansi.string_literals,
427 );
428 assert_eq!(
429 NumericLiteralSyntax {
430 money_literals: false,
431 ..mssql.numeric_literals
432 },
433 ansi.numeric_literals,
434 );
435 }
436
437 #[test]
438 fn mssql_is_lexically_consistent_and_dependency_clean() {
439 // Both self-consistency registries must be clean: the `[` bracket quote, the `@` named-at
440 // sigil, and the `$` money sigil each have a single claimant (array/subscript grammar off,
441 // `user_variables` off, `positional_dollar` off, `double_quoted_strings` off), and none of
442 // the five enabled gates rides an unset base flag.
443 let mssql = FeatureSet::MSSQL;
444 assert_eq!(mssql.lexical_conflict(), None);
445 assert!(mssql.is_lexically_consistent());
446 assert_eq!(mssql.feature_dependencies(), None);
447 assert!(mssql.has_satisfied_feature_dependencies());
448 assert_eq!(mssql.grammar_conflict(), None);
449 assert!(mssql.has_no_grammar_conflict());
450 }
451}