Skip to main content

sql_dialect_fmt_syntax/
dialect.rs

1//! The SQL [`Dialect`] selector — the runtime seam for multi-dialect support.
2//!
3//! The engine (lossless lexer, never-fail parser, Doc-IR formatter) is dialect-agnostic; only the
4//! ~20% that differs between dialects — reserved keywords, lexer quoting/special tokens, which
5//! statements/operators the grammar accepts, and a few formatter rules — is gated on a [`Dialect`].
6//! Modeled on `sqlparser-rs`'s `Dialect` trait: rather than a trait object, a small `Copy` enum
7//! threads through the lexer, parser, and formatter, and the divergence points are expressed as
8//! `#[must_use]` predicate methods on it.
9//!
10//! Today every predicate returns the **Snowflake-correct** answer ([`Dialect::Snowflake`] is the
11//! [`Default`]), so adding the seam changes no behavior. The Databricks arms encode where the two
12//! dialects diverge and are refined in later phases.
13
14/// The SQL dialect a lex/parse/format request targets.
15///
16/// `#[non_exhaustive]` so further dialects can be added without it being a breaking change. Default
17/// is [`Dialect::Snowflake`].
18#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
19#[non_exhaustive]
20pub enum Dialect {
21    /// Snowflake SQL — the original and default dialect of this toolchain.
22    #[default]
23    Snowflake,
24    /// Databricks SQL / Spark SQL. Behavior is being filled in across later phases; today its
25    /// predicate answers describe the *intended* divergence but no Databricks-specific lexing,
26    /// parsing, or formatting is wired up yet.
27    Databricks,
28}
29
30impl Dialect {
31    /// Dollar quoting: `$$ ... $$` dollar-quoted bodies and `$1` / `$name` positional/variable
32    /// references. Snowflake only — Databricks has no `$$` body or `$n` reference.
33    #[must_use]
34    pub fn supports_dollar_quoting(self) -> bool {
35        matches!(self, Dialect::Snowflake)
36    }
37
38    /// The flow operator `->>` chaining statements into a pipeline. Snowflake only.
39    #[must_use]
40    pub fn supports_flow_operator(self) -> bool {
41        matches!(self, Dialect::Snowflake)
42    }
43
44    /// `COPY INTO <target> FROM <source>` bulk load/unload. Snowflake only.
45    #[must_use]
46    pub fn supports_copy_into(self) -> bool {
47        matches!(self, Dialect::Snowflake)
48    }
49
50    /// `CREATE SEMANTIC VIEW ...` semantic-layer DDL. Snowflake only.
51    #[must_use]
52    pub fn supports_semantic_view(self) -> bool {
53        matches!(self, Dialect::Snowflake)
54    }
55
56    /// Snowflake Scripting blocks: `BEGIN ... END`, `DECLARE`, `LET`, and the `:=` assignment
57    /// operator. Snowflake only.
58    #[must_use]
59    pub fn supports_scripting_blocks(self) -> bool {
60        matches!(self, Dialect::Snowflake)
61    }
62
63    /// Stage references: `@stage` / `@~` / `@%table` paths in `FROM`, `COPY`, and `PUT`/`GET`.
64    /// Snowflake only.
65    #[must_use]
66    pub fn supports_stage_refs(self) -> bool {
67        matches!(self, Dialect::Snowflake)
68    }
69
70    /// Backtick-quoted identifiers: `` `col` ``. Databricks only (Snowflake quotes with `"`).
71    #[must_use]
72    pub fn supports_backtick_identifiers(self) -> bool {
73        matches!(self, Dialect::Databricks)
74    }
75
76    /// `LATERAL VIEW explode(...)` table-generating clause. Databricks only.
77    #[must_use]
78    pub fn supports_lateral_view(self) -> bool {
79        matches!(self, Dialect::Databricks)
80    }
81
82    /// Delta/Spark table DDL options: `USING`, `LOCATION`, `TBLPROPERTIES`, `OPTIONS`, and
83    /// `PARTITIONED BY`. Databricks only.
84    #[must_use]
85    pub fn supports_delta_table_options(self) -> bool {
86        matches!(self, Dialect::Databricks)
87    }
88
89    /// Higher-order-function lambdas: `x -> expr` and `(x, y) -> expr`. Databricks only for now.
90    #[must_use]
91    pub fn supports_lambda_expr(self) -> bool {
92        matches!(self, Dialect::Databricks)
93    }
94
95    /// Time travel via `VERSION AS OF` / `TIMESTAMP AS OF`. Databricks only (Snowflake uses
96    /// `AT` / `BEFORE`).
97    #[must_use]
98    pub fn supports_as_of_travel(self) -> bool {
99        matches!(self, Dialect::Databricks)
100    }
101
102    /// Delta/Spark maintenance + cache statements — `VACUUM`, `OPTIMIZE … ZORDER BY`,
103    /// `INSERT OVERWRITE`, `CACHE`/`UNCACHE`/`REFRESH`, `DESCRIBE HISTORY`, and the
104    /// `WHEN NOT MATCHED BY SOURCE`/`INSERT *` MERGE extensions. Databricks only. The leading words
105    /// (`VACUUM`, `OPTIMIZE`, `CACHE`, …) are recognized **contextually** at statement start, so they
106    /// stay ordinary identifiers under Snowflake (and elsewhere under Databricks), and Snowflake
107    /// output is byte-identical.
108    #[must_use]
109    pub fn supports_delta_commands(self) -> bool {
110        matches!(self, Dialect::Databricks)
111    }
112}
113
114#[cfg(test)]
115mod tests {
116    use super::Dialect;
117
118    #[test]
119    fn default_is_snowflake() {
120        assert_eq!(Dialect::default(), Dialect::Snowflake);
121    }
122
123    #[test]
124    fn snowflake_only_predicates() {
125        let s = Dialect::Snowflake;
126        assert!(s.supports_dollar_quoting());
127        assert!(s.supports_flow_operator());
128        assert!(s.supports_copy_into());
129        assert!(s.supports_semantic_view());
130        assert!(s.supports_scripting_blocks());
131        assert!(s.supports_stage_refs());
132        assert!(!s.supports_backtick_identifiers());
133        assert!(!s.supports_lateral_view());
134        assert!(!s.supports_delta_table_options());
135        assert!(!s.supports_lambda_expr());
136        assert!(!s.supports_as_of_travel());
137        assert!(!s.supports_delta_commands());
138    }
139
140    #[test]
141    fn databricks_only_predicates() {
142        let d = Dialect::Databricks;
143        assert!(!d.supports_dollar_quoting());
144        assert!(!d.supports_flow_operator());
145        assert!(!d.supports_copy_into());
146        assert!(!d.supports_semantic_view());
147        assert!(!d.supports_scripting_blocks());
148        assert!(!d.supports_stage_refs());
149        assert!(d.supports_backtick_identifiers());
150        assert!(d.supports_lateral_view());
151        assert!(d.supports_delta_table_options());
152        assert!(d.supports_lambda_expr());
153        assert!(d.supports_as_of_travel());
154        assert!(d.supports_delta_commands());
155    }
156}