| // Licensed to the Apache Software Foundation (ASF) under one | |
| // or more contributor license agreements. See the NOTICE file | |
| // distributed with this work for additional information | |
| // regarding copyright ownership. The ASF licenses this file | |
| // to you under the Apache License, Version 2.0 (the | |
| // "License"); you may not use this file except in compliance | |
| // with the License. You may obtain a copy of the License at | |
| // | |
| // http://www.apache.org/licenses/LICENSE-2.0 | |
| // | |
| // Unless required by applicable law or agreed to in writing, | |
| // software distributed under the License is distributed on an | |
| // "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY | |
| // KIND, either express or implied. See the License for the | |
| // specific language governing permissions and limitations | |
| // under the License. | |
| use crate::dialect::Dialect; | |
| use crate::keywords::Keyword; | |
| use core::iter::Peekable; | |
| use core::str::Chars; | |
| use super::keywords::RESERVED_FOR_IDENTIFIER; | |
| use super::PostgreSqlDialect; | |
| /// A [`Dialect`] for [RedShift](https://aws.amazon.com/redshift/) | |
| #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] | |
| #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] | |
| pub struct RedshiftSqlDialect {} | |
| // In most cases the redshift dialect is identical to [`PostgresSqlDialect`]. | |
| // | |
| // Notable differences: | |
| // 1. Redshift treats brackets `[` and `]` differently. For example, `SQL SELECT a[1][2] FROM b` | |
| // in the Postgres dialect, the query will be parsed as an array, while in the Redshift dialect it will | |
| // be a json path | |
| impl Dialect for RedshiftSqlDialect { | |
| /// Determine if a character starts a potential nested quoted identifier. | |
| /// Example: RedShift supports the following quote styles to all mean the same thing: | |
| /// ```sql | |
| /// SELECT 1 AS foo; | |
| /// SELECT 1 AS "foo"; | |
| /// SELECT 1 AS [foo]; | |
| /// SELECT 1 AS ["foo"]; | |
| /// ``` | |
| fn is_nested_delimited_identifier_start(&self, ch: char) -> bool { | |
| ch == '[' | |
| } | |
| /// Only applicable whenever [`Self::is_nested_delimited_identifier_start`] returns true | |
| /// If the next sequence of tokens potentially represent a nested identifier, then this method | |
| /// returns a tuple containing the outer quote style, and if present, the inner (nested) quote style. | |
| /// | |
| /// Example (Redshift): | |
| /// ```text | |
| /// `["foo"]` => Some(`[`, Some(`"`)) | |
| /// `[foo]` => Some(`[`, None) | |
| /// `[0]` => None | |
| /// `"foo"` => None | |
| /// ``` | |
| fn peek_nested_delimited_identifier_quotes( | |
| &self, | |
| mut chars: Peekable<Chars<'_>>, | |
| ) -> Option<(char, Option<char>)> { | |
| if chars.peek() != Some(&'[') { | |
| return None; | |
| } | |
| chars.next(); | |
| let mut not_white_chars = chars.skip_while(|ch| ch.is_whitespace()).peekable(); | |
| if let Some(&ch) = not_white_chars.peek() { | |
| if ch == '"' { | |
| return Some(('[', Some('"'))); | |
| } | |
| if self.is_identifier_start(ch) { | |
| return Some(('[', None)); | |
| } | |
| } | |
| None | |
| } | |
| fn is_identifier_start(&self, ch: char) -> bool { | |
| // UTF-8 multibyte characters are supported in identifiers via the PostgreSqlDialect. | |
| // https://docs.aws.amazon.com/redshift/latest/dg/r_names.html | |
| PostgreSqlDialect {}.is_identifier_start(ch) || ch == '#' | |
| } | |
| fn is_identifier_part(&self, ch: char) -> bool { | |
| // UTF-8 multibyte characters are supported in identifiers via the PostgreSqlDialect. | |
| // https://docs.aws.amazon.com/redshift/latest/dg/r_names.html | |
| PostgreSqlDialect {}.is_identifier_part(ch) || ch == '#' | |
| } | |
| /// See <https://docs.aws.amazon.com/redshift/latest/dg/r_names.html> | |
| fn identifier_quote_style(&self, _identifier: &str) -> Option<char> { | |
| Some('"') | |
| } | |
| /// redshift has `CONVERT(type, value)` instead of `CONVERT(value, type)` | |
| /// <https://docs.aws.amazon.com/redshift/latest/dg/r_CONVERT_function.html> | |
| fn convert_type_before_value(&self) -> bool { | |
| true | |
| } | |
| fn supports_connect_by(&self) -> bool { | |
| true | |
| } | |
| /// Redshift expects the `TOP` option before the `ALL/DISTINCT` option: | |
| /// <https://docs.aws.amazon.com/redshift/latest/dg/r_SELECT_list.html#r_SELECT_list-parameters> | |
| fn supports_top_before_distinct(&self) -> bool { | |
| true | |
| } | |
| /// Redshift supports PartiQL: <https://docs.aws.amazon.com/redshift/latest/dg/super-overview.html> | |
| fn supports_partiql(&self) -> bool { | |
| true | |
| } | |
| fn supports_unpivot_expr(&self) -> bool { | |
| true | |
| } | |
| fn supports_string_escape_constant(&self) -> bool { | |
| true | |
| } | |
| /// See <https://docs.aws.amazon.com/redshift/latest/dg/r_APPROXIMATE_PERCENTILE_DISC.html> | |
| fn supports_approximate_percentile_disc(&self) -> bool { | |
| true | |
| } | |
| fn supports_geometric_types(&self) -> bool { | |
| true | |
| } | |
| fn supports_bitwise_shift_operators(&self) -> bool { | |
| true | |
| } | |
| fn supports_array_typedef_with_brackets(&self) -> bool { | |
| true | |
| } | |
| fn allow_extract_single_quotes(&self) -> bool { | |
| true | |
| } | |
| fn supports_string_literal_backslash_escape(&self) -> bool { | |
| true | |
| } | |
| fn supports_select_wildcard_exclude(&self) -> bool { | |
| true | |
| } | |
| /// Redshift supports aliasing wildcard expressions: | |
| /// <https://docs.aws.amazon.com/redshift/latest/dg/r_SELECT_list.html> | |
| fn supports_select_wildcard_with_alias(&self) -> bool { | |
| true | |
| } | |
| fn supports_left_associative_joins_without_parens(&self) -> bool { | |
| false | |
| } | |
| fn supports_select_exclude(&self) -> bool { | |
| true | |
| } | |
| fn supports_create_table_like_parenthesized(&self) -> bool { | |
| true | |
| } | |
| fn supports_string_literal_concatenation_with_newline(&self) -> bool { | |
| true | |
| } | |
| fn supports_window_function_null_treatment_arg(&self) -> bool { | |
| true | |
| } | |
| /// `INTERVAL` is listed as reserved in Redshift docs but the actual parser allows it as an | |
| /// identifier, consistent with pgsql. | |
| fn is_reserved_for_identifier(&self, kw: Keyword) -> bool { | |
| if matches!(kw, Keyword::INTERVAL) { | |
| false | |
| } else { | |
| RESERVED_FOR_IDENTIFIER.contains(&kw) | |
| } | |
| } | |
| } |