Skip to main content

squonk_ast/dialect/
redshift.rs

1// SPDX-License-Identifier: MIT
2// Copyright (c) 2026 Moderately AI Inc.
3
4//! The Amazon Redshift dialect preset (ANSI-derived, deliberately conservative).
5//!
6//! Redshift is genuinely a **PostgreSQL 8 fork** — the honest temptation is to derive it from
7//! [`FeatureSet::POSTGRES`]. This preset deliberately does *not*, and the reasoning is the
8//! project's evidence bar rather than convenience:
9//!
10//! - **No Redshift oracle exists.** Like the five other no-oracle presets
11//!   (BigQuery/ClickHouse/Snowflake/Databricks/MSSQL/Hive), over-acceptance here cannot be
12//!   *measured*. A `POSTGRES`-derived base would inherit our PostgreSQL preset's whole surface —
13//!   but that preset is oracle-fitted to **PostgreSQL 17**, decades past the PG-8 fork point, so
14//!   it carries modern features Redshift never had (dollar-quoting, `jsonb` operators, SQL/JSON
15//!   functions, `MERGE … RETURNING`, quantified `LIKE ANY (array)`, …). Deriving from `POSTGRES`
16//!   would silently over-accept every one of those, and each omission would then need its *own*
17//!   Redshift evidence to turn back off — a larger, less honest surface than starting strict.
18//! - **Conservatism is the honesty bar.** Deriving from [`FeatureSet::ANSI`], the strict standard
19//!   baseline, means every divergence from the standard is a documented, evidence-cited decision a
20//!   reader can audit from this one module, and unsupported Redshift syntax is a clean reject
21//!   routed to a focused follow-up ticket, never a silent over-accept.
22//! - **Our flag docs attribute the PG-isms to PostgreSQL, not Redshift.** The evidence bar for
23//!   turning a *dialect-attributed* grammar flag on is that our own flag doc names the dialect (as
24//!   the sided-join doc names Hive). A sweep of `dialect/mod.rs` finds **zero** Redshift
25//!   citations, and the candidate PG-heritage flags (`ilike`, `similar_to`, `distinct_on`,
26//!   `qualify`) are each documented as PostgreSQL/DuckDB/Teradata features. Those flags are
27//!   therefore conservative-off and deferred (see below) — not because Redshift lacks the
28//!   feature, but because turning them on here would assert an unmeasured equivalence.
29//!
30//! # What this preset adds over ANSI
31//!
32//! Two axes (both evidence-backed; the rest stays ANSI verbatim):
33//!
34//! - **Case folding to lowercase.** Redshift resolves unquoted identifiers case-insensitively and
35//!   folds them to lowercase (its default `enable_case_sensitive_identifier` is off — the
36//!   PostgreSQL-inherited behaviour, differing from PG only in fold *direction* being the same
37//!   lowercase), so [`identifier_casing`](FeatureSet::identifier_casing) is [`Casing::Lower`]
38//!   rather than ANSI's [`Casing::Upper`]. The fold is identity-only: the interned text still
39//!   renders exactly as written and acceptance never changes — this is a name-resolution fact, not
40//!   a parse boundary. `Casing` is *dialect-open* (a general folding model, not a flag our docs
41//!   tie to one engine), so external Redshift documentation is the admissible evidence.
42//! - **Table-position PartiQL / SUPER JSON path**
43//!   ([`table_json_path`](TableExpressionSyntax::table_json_path)) — `FROM src[0].a` navigation
44//!   of a SUPER column (sqlparser-rs's `supports_partiql` surface). A parse boundary, not just
45//!   identity.
46//!
47//! Lexis remains ANSI: Redshift quotes identifiers with the standard `"…"` (unlike Hive's
48//! backtick or MSSQL's bracket) and spells strings with `'…'`, so the ANSI
49//! [`STANDARD_IDENTIFIER_QUOTES`] and [`StringLiteralSyntax::ANSI`] are exact and this preset
50//! adds no new lexical trigger (the `const` assert below stays clean).
51//!
52//! # Deliberately deferred (conservative reject)
53//!
54//! Redshift genuinely accepts each of these (it inherited most from PostgreSQL 8); each is a clean
55//! reject here, routed to a follow-up rather than guessed at without an oracle:
56//!
57//! - **`ILIKE` and `SIMILAR TO`.** Redshift ships both (AWS documents them), but our
58//!   [`ilike`](PredicateSyntax::ilike)/[`similar_to`](PredicateSyntax::similar_to) flag docs
59//!   attribute them to PostgreSQL and do not name Redshift — dialect-attributed, so external
60//!   Redshift evidence is not admissible under the bar. Deferred pending either a Redshift oracle
61//!   or a flag-doc citation update.
62//! - **`DISTINCT ON`.** Same shape: Redshift inherits PostgreSQL's `SELECT DISTINCT ON (…)`, but
63//!   [`distinct_on`](SelectSyntax::distinct_on) is documented as the PostgreSQL extension.
64//! - **`QUALIFY`.** Redshift added `QUALIFY`, but our [`qualify`](SelectSyntax::qualify) doc cites
65//!   DuckDB (Teradata-origin) and needs the reserved-keyword modelling Snowflake's preset does;
66//!   deferred rather than half-modelled.
67//! - **The large unmodelled Redshift surface.** `DISTKEY`/`SORTKEY`/`DISTSTYLE`/`ENCODE` table
68//!   attributes, `UNLOAD`/`COPY` bulk-load statements, full `SUPER` type DDL (beyond the table-
69//!   position JSON path above), `INTERVAL` literal spellings, and Redshift's window-frame
70//!   differences all have no modelled gate and are clean rejects routed to follow-up tickets.
71//!
72//! A reader can predict from this module exactly what Redshift accepts beyond the standard: the
73//! lowercase identifier fold and table-position `table_json_path`, and nothing else, until each
74//! deferred surface earns its own gate.
75
76use super::{
77    AccessControlSyntax, AggregateCallSyntax, CallSyntax, CaretOperator, Casing,
78    ColumnDefinitionSyntax, CommentSyntax, ConstraintSyntax, CreateTableClauseSyntax,
79    DoubleAmpersand, ExistenceGuards, ExpressionSyntax, FeatureSet, GroupingSyntax,
80    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    STANDARD_IDENTIFIER_QUOTES, SelectSyntax, SessionVariableSyntax, ShowSyntax, StatementDdlGates,
85    StringFuncForms, StringLiteralSyntax, TableExpressionSyntax, TableFactorSyntax, TargetSpelling,
86    TransactionSyntax, TypeNameSyntax, UtilitySyntax, ViewSequenceClauseSyntax,
87};
88use crate::precedence::{STANDARD_BINDING_POWERS, STANDARD_SET_OPERATION_BINDING_POWERS};
89
90impl FeatureSet {
91    /// Amazon Redshift as ANSI-derived dialect data (see the module docs for the full derivation
92    /// rationale — including why a PostgreSQL-8 fork still derives from ANSI, not `POSTGRES` — and
93    /// the conservatism bar).
94    pub const REDSHIFT: Self = Self {
95        // Delta 1/2 over ANSI: Redshift folds unquoted identifiers to lowercase (its default
96        // `enable_case_sensitive_identifier` off — the PostgreSQL-inherited lowercase model).
97        // Identity only: the interned text still renders exactly as written, so this never affects
98        // acceptance. `Casing` is dialect-open, so external Redshift docs are the admissible
99        // evidence here (unlike the dialect-attributed PG-heritage flags, which stay off).
100        identifier_casing: Casing::Lower,
101        // Standard `"…"` identifier quoting; Redshift adds no second delimiter (unlike Hive's
102        // backtick or MSSQL's bracket), so no lexical trigger is added over ANSI.
103        identifier_quotes: STANDARD_IDENTIFIER_QUOTES,
104        default_null_ordering: NullOrdering::NullsLast,
105        // No reserved-set delta over ANSI — this conservative preset reserves no extra keyword
106        // (the deferred `QUALIFY`/`DISTINCT ON` gates that would need reservation are off).
107        reserved_column_name: RESERVED_COLUMN_NAME,
108        reserved_function_name: RESERVED_FUNCTION_NAME,
109        reserved_type_name: RESERVED_TYPE_NAME,
110        reserved_bare_alias: RESERVED_BARE_ALIAS,
111        reserved_as_label: KeywordSet::EMPTY,
112        catalog_qualified_names: true,
113        byte_classes: STANDARD_BYTE_CLASSES,
114        binding_powers: STANDARD_BINDING_POWERS,
115        set_operation_powers: STANDARD_SET_OPERATION_BINDING_POWERS,
116        // Conservative ANSI string surface: Redshift spells strings with `'…'` (its `"…"` is a
117        // quoted identifier, exactly ANSI). Redshift's own extensions have no modelled gate here.
118        string_literals: StringLiteralSyntax::ANSI,
119        numeric_literals: NumericLiteralSyntax::ANSI,
120        parameters: ParameterSyntax::ANSI,
121        session_variables: SessionVariableSyntax::ANSI,
122        identifier_syntax: IdentifierSyntax::ANSI,
123        // ANSI table-expression surface plus the PartiQL / SUPER table-position JSON path
124        // (`FROM src[0].a`) navigating a SUPER column, sqlparser-rs's `supports_partiql`.
125        table_expressions: TableExpressionSyntax {
126            table_json_path: true,
127            only: false,
128            table_sample: false,
129            parenthesized_joins: true,
130            table_alias_column_lists: true,
131            join_using_alias: false,
132            index_hints: false,
133            table_hints: false,
134            partition_selection: false,
135            base_table_alias_column_lists: true,
136            string_literal_aliases: false,
137            aliased_parenthesized_join: true,
138            bare_table_alias_is_bare_label: false,
139            table_version: false,
140            indexed_by: false,
141            prefix_colon_alias: false,
142        },
143        join_syntax: JoinSyntax::ANSI,
144        table_factor_syntax: TableFactorSyntax::ANSI,
145        expression_syntax: ExpressionSyntax::ANSI,
146        operator_syntax: OperatorSyntax::ANSI,
147        call_syntax: CallSyntax::ANSI,
148        string_func_forms: StringFuncForms::ANSI,
149        aggregate_call_syntax: AggregateCallSyntax::ANSI,
150        // `ILIKE`/`SIMILAR TO` are deferred (dialect-attributed to PostgreSQL in our docs, no
151        // Redshift citation) — every predicate knob is conservatively ANSI.
152        predicate_syntax: PredicateSyntax::ANSI,
153        pipe_operator: PipeOperator::StringConcat,
154        double_ampersand: DoubleAmpersand::Unsupported,
155        keyword_operators: KeywordOperators::Unsupported,
156        caret_operator: CaretOperator::Unsupported,
157        hash_bitwise_xor: false,
158        comment_syntax: CommentSyntax::ANSI,
159        mutation_syntax: MutationSyntax::ANSI,
160        statement_ddl_gates: StatementDdlGates::ANSI,
161        view_sequence_clause_syntax: ViewSequenceClauseSyntax::ANSI,
162        create_table_clause_syntax: CreateTableClauseSyntax::ANSI,
163        column_definition_syntax: ColumnDefinitionSyntax::ANSI,
164        constraint_syntax: ConstraintSyntax::ANSI,
165        index_alter_syntax: IndexAlterSyntax::ANSI,
166        existence_guards: ExistenceGuards::ANSI,
167        // `DISTINCT ON`/`QUALIFY` are deferred (see the module docs) — every SELECT knob is
168        // conservatively ANSI.
169        select_syntax: SelectSyntax::ANSI,
170        query_tail_syntax: QueryTailSyntax::ANSI,
171        grouping_syntax: GroupingSyntax::ANSI,
172        utility_syntax: UtilitySyntax::ANSI,
173        transaction_syntax: TransactionSyntax::ANSI,
174        show_syntax: ShowSyntax::ANSI,
175        maintenance_syntax: MaintenanceSyntax::ANSI,
176        access_control_syntax: AccessControlSyntax::ANSI,
177        type_name_syntax: TypeNameSyntax::ANSI,
178        // No Redshift-specific Tier-1 output spelling yet; render the portable ANSI canonical type
179        // names (a `TargetSpelling::Redshift` is render work a later ticket owns).
180        target_spelling: TargetSpelling::Ansi,
181    };
182}
183
184/// Prefer [`FeatureSet::REDSHIFT`] for struct update.
185pub const REDSHIFT: FeatureSet = FeatureSet::REDSHIFT;
186
187// Compile-time proof the Redshift preset claims no shared tokenizer trigger twice. It adds *no*
188// lexical trigger over ANSI (standard `"…"` identifier quoting, `'…'` strings — the same lexis as
189// the ANSI baseline), so this holds as trivially as ANSI's own assert. Kept as a ratchet so a
190// future Redshift delta that *does* add a contending trigger (e.g. `$$…$$` dollar-quoting) fails
191// the build here rather than silently shadowing a meaning.
192const _: () = assert!(FeatureSet::REDSHIFT.is_lexically_consistent());
193// The two sibling self-consistency registries are ratcheted the same way, so the
194// parse-entry `debug_assert!` folds all three to dead code for this preset: no refinement
195// flag rides an unset base, and no two features contend for one parser-position head.
196const _: () = assert!(FeatureSet::REDSHIFT.has_satisfied_feature_dependencies());
197const _: () = assert!(FeatureSet::REDSHIFT.has_no_grammar_conflict());
198
199#[cfg(test)]
200mod tests {
201    use super::*;
202
203    #[test]
204    fn redshift_is_ansi_plus_only_the_lowercase_fold() {
205        // Closed-delta honesty: despite Redshift being a PostgreSQL-8 fork, this preset is
206        // ANSI plus exactly two divergent top-level axes (casing + table_json_path).
207        let ansi = FeatureSet::ANSI;
208        let redshift = FeatureSet::REDSHIFT;
209
210        assert_eq!(redshift.identifier_casing, Casing::Lower);
211        assert!(redshift.table_expressions.table_json_path);
212        // Deferred PG-heritage flags stay off.
213        assert!(!redshift.predicate_syntax.ilike);
214        assert!(!redshift.predicate_syntax.similar_to);
215        assert!(!redshift.select_syntax.distinct_on);
216        assert!(!redshift.select_syntax.qualify);
217
218        crate::dialect::closed_delta::assert_closed_delta(
219            &ansi,
220            &redshift,
221            &["identifier_casing", "table_expressions"],
222        );
223    }
224
225    #[test]
226    fn redshift_recovers_ansi_when_the_fold_is_forced_back() {
227        // The two deltas isolated: forcing the fold direction back to ANSI's `Upper` and
228        // dropping the PartiQL / SUPER table-position path recovers the ANSI FeatureSet
229        // verbatim, proving the lowercase fold and the `table_json_path` grant are the *only*
230        // divergences.
231        let ansi = FeatureSet::ANSI;
232        let redshift = FeatureSet::REDSHIFT;
233        assert_eq!(
234            FeatureSet {
235                identifier_casing: Casing::Upper,
236                table_expressions: TableExpressionSyntax {
237                    table_json_path: false,
238                    ..redshift.table_expressions
239                },
240                ..redshift
241            },
242            ansi,
243        );
244    }
245
246    #[test]
247    fn redshift_is_lexically_consistent_and_dependency_clean() {
248        // Both self-consistency registries must be clean: adding no lexical trigger over ANSI, the
249        // conflict registry is trivially empty, and riding no dependent grammar flag, the
250        // dependency registry is empty too.
251        let redshift = FeatureSet::REDSHIFT;
252        assert_eq!(redshift.lexical_conflict(), None);
253        assert!(redshift.is_lexically_consistent());
254        assert_eq!(redshift.feature_dependencies(), None);
255        assert!(redshift.has_satisfied_feature_dependencies());
256        assert_eq!(redshift.grammar_conflict(), None);
257        assert!(redshift.has_no_grammar_conflict());
258    }
259}