diff --git a/README.md b/README.md index 3226b9549..86f7a0cbb 100644 --- a/README.md +++ b/README.md @@ -24,7 +24,7 @@ let sql = "SELECT a, b, 123, myfunc(b) \ WHERE a > b AND b < 100 \ ORDER BY a DESC, b"; -let dialect = GenericDialect {}; // or AnsiDialect, or your own dialect ... +let dialect = GenericDialect::default(); // or AnsiDialect, or your own dialect ... let ast = Parser::parse_sql(&dialect, sql).unwrap(); diff --git a/examples/cli.rs b/examples/cli.rs index 72f963b1e..3d02545bf 100644 --- a/examples/cli.rs +++ b/examples/cli.rs @@ -37,18 +37,18 @@ $ cargo run --feature json_example --example cli FILENAME.sql [--dialectname] ); let dialect: Box = match std::env::args().nth(2).unwrap_or_default().as_ref() { - "--ansi" => Box::new(AnsiDialect {}), - "--bigquery" => Box::new(BigQueryDialect {}), - "--postgres" => Box::new(PostgreSqlDialect {}), - "--ms" => Box::new(MsSqlDialect {}), - "--mysql" => Box::new(MySqlDialect {}), - "--snowflake" => Box::new(SnowflakeDialect {}), - "--hive" => Box::new(HiveDialect {}), - "--redshift" => Box::new(RedshiftSqlDialect {}), - "--clickhouse" => Box::new(ClickHouseDialect {}), - "--duckdb" => Box::new(DuckDbDialect {}), - "--sqlite" => Box::new(SQLiteDialect {}), - "--generic" | "" => Box::new(GenericDialect {}), + "--ansi" => Box::new(AnsiDialect::default()), + "--bigquery" => Box::new(BigQueryDialect::default()), + "--postgres" => Box::new(PostgreSqlDialect::default()), + "--ms" => Box::new(MsSqlDialect::default()), + "--mysql" => Box::new(MySqlDialect::default()), + "--snowflake" => Box::new(SnowflakeDialect::default()), + "--hive" => Box::new(HiveDialect::default()), + "--redshift" => Box::new(RedshiftSqlDialect::default()), + "--clickhouse" => Box::new(ClickHouseDialect::default()), + "--duckdb" => Box::new(DuckDbDialect::default()), + "--sqlite" => Box::new(SQLiteDialect::default()), + "--generic" | "" => Box::new(GenericDialect::default()), s => panic!("Unexpected parameter: {s}"), }; diff --git a/examples/parse_select.rs b/examples/parse_select.rs index 71fe1fa1e..126027c29 100644 --- a/examples/parse_select.rs +++ b/examples/parse_select.rs @@ -21,7 +21,7 @@ fn main() { WHERE a > b AND b < 100 \ ORDER BY a DESC, b"; - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let ast = Parser::parse_sql(&dialect, sql).unwrap(); diff --git a/fuzz/fuzz_targets/fuzz_parse_sql.rs b/fuzz/fuzz_targets/fuzz_parse_sql.rs index 629fa360b..1d373cf79 100644 --- a/fuzz/fuzz_targets/fuzz_parse_sql.rs +++ b/fuzz/fuzz_targets/fuzz_parse_sql.rs @@ -5,7 +5,7 @@ use sqlparser::parser::Parser; fn main() { loop { fuzz!(|data: String| { - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let _ = Parser::parse_sql(&dialect, &data); }); } diff --git a/sqlparser_bench/benches/sqlparser_bench.rs b/sqlparser_bench/benches/sqlparser_bench.rs index 5293c0f50..552ba8ff5 100644 --- a/sqlparser_bench/benches/sqlparser_bench.rs +++ b/sqlparser_bench/benches/sqlparser_bench.rs @@ -16,7 +16,7 @@ use sqlparser::parser::Parser; fn basic_queries(c: &mut Criterion) { let mut group = c.benchmark_group("sqlparser-rs parsing benchmark"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let string = "SELECT * FROM table WHERE 1 = 1"; group.bench_function("sqlparser::select", |b| { diff --git a/src/ast/visitor.rs b/src/ast/visitor.rs index 1b8a43802..54c0b1565 100644 --- a/src/ast/visitor.rs +++ b/src/ast/visitor.rs @@ -155,7 +155,7 @@ visit_noop!(bigdecimal::BigDecimal); /// } /// /// let sql = "SELECT a FROM foo where x IN (SELECT y FROM bar)"; -/// let statements = Parser::parse_sql(&GenericDialect{}, sql) +/// let statements = Parser::parse_sql(&GenericDialect::default(), sql) /// .unwrap(); /// /// // Drive the visitor through the AST @@ -266,7 +266,7 @@ pub trait Visitor { /// } /// /// let sql = "SELECT to_replace FROM foo where to_replace IN (SELECT to_replace FROM bar)"; -/// let mut statements = Parser::parse_sql(&GenericDialect{}, sql).unwrap(); +/// let mut statements = Parser::parse_sql(&GenericDialect::default(), sql).unwrap(); /// /// // Drive the visitor through the AST /// statements.visit(&mut Replacer); @@ -361,7 +361,7 @@ impl ControlFlow> VisitorMut for RelationVisi /// # use sqlparser::ast::{visit_relations}; /// # use core::ops::ControlFlow; /// let sql = "SELECT a FROM foo where x IN (SELECT y FROM bar)"; -/// let statements = Parser::parse_sql(&GenericDialect{}, sql) +/// let statements = Parser::parse_sql(&GenericDialect::default(), sql) /// .unwrap(); /// /// // visit statements, capturing relations (table names) @@ -401,7 +401,7 @@ where /// # use sqlparser::ast::{ObjectName, visit_relations_mut}; /// # use core::ops::ControlFlow; /// let sql = "SELECT a FROM foo"; -/// let mut statements = Parser::parse_sql(&GenericDialect{}, sql) +/// let mut statements = Parser::parse_sql(&GenericDialect::default(), sql) /// .unwrap(); /// /// // visit statements, renaming table foo to bar @@ -449,7 +449,7 @@ impl ControlFlow> VisitorMut for ExprVisitor { /// # use sqlparser::ast::{visit_expressions}; /// # use core::ops::ControlFlow; /// let sql = "SELECT a FROM foo where x IN (SELECT y FROM bar)"; -/// let statements = Parser::parse_sql(&GenericDialect{}, sql) +/// let statements = Parser::parse_sql(&GenericDialect::default(), sql) /// .unwrap(); /// /// // visit all expressions @@ -493,7 +493,7 @@ where /// # use sqlparser::ast::{Expr, visit_expressions_mut, visit_statements_mut}; /// # use core::ops::ControlFlow; /// let sql = "SELECT (SELECT y FROM z LIMIT 9) FROM t LIMIT 3"; -/// let mut statements = Parser::parse_sql(&GenericDialect{}, sql).unwrap(); +/// let mut statements = Parser::parse_sql(&GenericDialect::default(), sql).unwrap(); /// /// // Remove all select limits in sub-queries /// visit_expressions_mut(&mut statements, |expr| { @@ -518,7 +518,7 @@ where /// # use sqlparser::ast::*; /// # use core::ops::ControlFlow; /// let sql = "SELECT x, y FROM t"; -/// let mut statements = Parser::parse_sql(&GenericDialect{}, sql).unwrap(); +/// let mut statements = Parser::parse_sql(&GenericDialect::default(), sql).unwrap(); /// /// visit_expressions_mut(&mut statements, |expr| { /// if matches!(expr, Expr::Identifier(col_name) if col_name.value == "x") { @@ -579,7 +579,7 @@ impl ControlFlow> VisitorMut for StatementVisi /// # use sqlparser::ast::{visit_statements}; /// # use core::ops::ControlFlow; /// let sql = "SELECT a FROM foo where x IN (SELECT y FROM bar); CREATE TABLE baz(q int)"; -/// let statements = Parser::parse_sql(&GenericDialect{}, sql) +/// let statements = Parser::parse_sql(&GenericDialect::default(), sql) /// .unwrap(); /// /// // visit all statements @@ -616,7 +616,7 @@ where /// # use sqlparser::ast::{Statement, visit_statements_mut}; /// # use core::ops::ControlFlow; /// let sql = "SELECT x FROM foo LIMIT 9+$limit; SELECT * FROM t LIMIT f()"; -/// let mut statements = Parser::parse_sql(&GenericDialect{}, sql).unwrap(); +/// let mut statements = Parser::parse_sql(&GenericDialect::default(), sql).unwrap(); /// /// // Remove all select limits in outer statements (not in sub-queries) /// visit_statements_mut(&mut statements, |stmt| { @@ -715,7 +715,7 @@ mod tests { } fn do_visit(sql: &str) -> Vec { - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap(); let s = Parser::new(&dialect) .with_tokens(tokens) diff --git a/src/dialect/ansi.rs b/src/dialect/ansi.rs index 61ae5829e..7a818ce91 100644 --- a/src/dialect/ansi.rs +++ b/src/dialect/ansi.rs @@ -10,13 +10,26 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::dialect::Dialect; +use crate::dialect::{Dialect, DialectFlags}; /// A [`Dialect`] for [ANSI SQL](https://en.wikipedia.org/wiki/SQL:2011). #[derive(Debug)] -pub struct AnsiDialect {} +pub struct AnsiDialect(DialectFlags); + +impl Default for AnsiDialect { + fn default() -> Self { + Self(DialectFlags { + require_interval_qualifier: true, + ..Default::default() + }) + } +} impl Dialect for AnsiDialect { + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_identifier_start(&self, ch: char) -> bool { ch.is_ascii_lowercase() || ch.is_ascii_uppercase() } @@ -24,8 +37,4 @@ impl Dialect for AnsiDialect { fn is_identifier_part(&self, ch: char) -> bool { ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_' } - - fn require_interval_qualifier(&self) -> bool { - true - } } diff --git a/src/dialect/bigquery.rs b/src/dialect/bigquery.rs index 3bce6702b..e5498864c 100644 --- a/src/dialect/bigquery.rs +++ b/src/dialect/bigquery.rs @@ -10,13 +10,39 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::dialect::Dialect; +use crate::dialect::{Dialect, DialectFlags}; /// A [`Dialect`] for [Google Bigquery](https://cloud.google.com/bigquery/) -#[derive(Debug, Default)] -pub struct BigQueryDialect; +#[derive(Debug)] +pub struct BigQueryDialect(DialectFlags); + +impl Default for BigQueryDialect { + fn default() -> Self { + Self(DialectFlags { + // See https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals + supports_triple_quoted_string: true, + // See https://cloud.google.com/bigquery/docs/reference/standard-sql/navigation_functions#first_value + supports_window_function_null_treatment_arg: true, + // See https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#escape_sequences + supports_string_literal_backslash_escape: true, + // See https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls#ref_named_window + supports_window_clause_named_window_reference: true, + // See https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#set + supports_parenthesized_set_variables: true, + // See https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#select_except + supports_select_wildcard_except: true, + // See https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#interval_type + require_interval_qualifier: true, + ..Default::default() + }) + } +} impl Dialect for BigQueryDialect { + fn flags(&self) -> &DialectFlags { + &self.0 + } + // See https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#identifiers fn is_delimited_identifier_start(&self, ch: char) -> bool { ch == '`' @@ -33,38 +59,4 @@ impl Dialect for BigQueryDialect { fn is_identifier_part(&self, ch: char) -> bool { ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch.is_ascii_digit() || ch == '_' } - - /// See [doc](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals) - fn supports_triple_quoted_string(&self) -> bool { - true - } - - /// See [doc](https://cloud.google.com/bigquery/docs/reference/standard-sql/navigation_functions#first_value) - fn supports_window_function_null_treatment_arg(&self) -> bool { - true - } - - // See https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#escape_sequences - fn supports_string_literal_backslash_escape(&self) -> bool { - true - } - - /// See [doc](https://cloud.google.com/bigquery/docs/reference/standard-sql/window-function-calls#ref_named_window) - fn supports_window_clause_named_window_reference(&self) -> bool { - true - } - - /// See [doc](https://cloud.google.com/bigquery/docs/reference/standard-sql/procedural-language#set) - fn supports_parenthesized_set_variables(&self) -> bool { - true - } - - // See https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#select_except - fn supports_select_wildcard_except(&self) -> bool { - true - } - - fn require_interval_qualifier(&self) -> bool { - true - } } diff --git a/src/dialect/clickhouse.rs b/src/dialect/clickhouse.rs index 09735cbee..438065511 100644 --- a/src/dialect/clickhouse.rs +++ b/src/dialect/clickhouse.rs @@ -10,13 +10,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::dialect::Dialect; +use crate::dialect::{Dialect, DialectFlags}; // A [`Dialect`] for [ClickHouse](https://clickhouse.com/). #[derive(Debug)] -pub struct ClickHouseDialect {} +pub struct ClickHouseDialect(DialectFlags); + +impl Default for ClickHouseDialect { + fn default() -> Self { + Self(DialectFlags { + supports_string_literal_backslash_escape: true, + supports_select_wildcard_except: true, + describe_requires_table_keyword: true, + require_interval_qualifier: true, + ..Default::default() + }) + } +} impl Dialect for ClickHouseDialect { + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_identifier_start(&self, ch: char) -> bool { // See https://clickhouse.com/docs/en/sql-reference/syntax/#syntax-identifiers ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_' @@ -25,20 +41,4 @@ impl Dialect for ClickHouseDialect { fn is_identifier_part(&self, ch: char) -> bool { self.is_identifier_start(ch) || ch.is_ascii_digit() } - - fn supports_string_literal_backslash_escape(&self) -> bool { - true - } - - fn supports_select_wildcard_except(&self) -> bool { - true - } - - fn describe_requires_table_keyword(&self) -> bool { - true - } - - fn require_interval_qualifier(&self) -> bool { - true - } } diff --git a/src/dialect/databricks.rs b/src/dialect/databricks.rs index d3661444b..7f736a0e4 100644 --- a/src/dialect/databricks.rs +++ b/src/dialect/databricks.rs @@ -1,13 +1,31 @@ -use crate::dialect::Dialect; +use crate::dialect::{Dialect, DialectFlags}; /// A [`Dialect`] for [Databricks SQL](https://www.databricks.com/) /// /// See . -#[derive(Debug, Default)] -pub struct DatabricksDialect; +#[derive(Debug)] +pub struct DatabricksDialect(DialectFlags); + +impl Default for DatabricksDialect { + fn default() -> Self { + Self(DialectFlags { + supports_filter_during_aggregation: true, + // https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-groupby.html + supports_group_by_expr: true, + supports_lambda_functions: true, + // https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select.html#syntax + supports_select_wildcard_except: true, + require_interval_qualifier: true, + ..Default::default() + }) + } +} +/// see impl Dialect for DatabricksDialect { - // see https://docs.databricks.com/en/sql/language-manual/sql-ref-identifiers.html + fn flags(&self) -> &DialectFlags { + &self.0 + } fn is_delimited_identifier_start(&self, ch: char) -> bool { matches!(ch, '`') @@ -20,26 +38,4 @@ impl Dialect for DatabricksDialect { fn is_identifier_part(&self, ch: char) -> bool { matches!(ch, 'a'..='z' | 'A'..='Z' | '0'..='9' | '_') } - - fn supports_filter_during_aggregation(&self) -> bool { - true - } - - // https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select-groupby.html - fn supports_group_by_expr(&self) -> bool { - true - } - - fn supports_lambda_functions(&self) -> bool { - true - } - - // https://docs.databricks.com/en/sql/language-manual/sql-ref-syntax-qry-select.html#syntax - fn supports_select_wildcard_except(&self) -> bool { - true - } - - fn require_interval_qualifier(&self) -> bool { - true - } } diff --git a/src/dialect/duckdb.rs b/src/dialect/duckdb.rs index 1fc211685..5e48bc73a 100644 --- a/src/dialect/duckdb.rs +++ b/src/dialect/duckdb.rs @@ -10,16 +10,36 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::dialect::Dialect; +use crate::dialect::{Dialect, DialectFlags}; /// A [`Dialect`] for [DuckDB](https://duckdb.org/) -#[derive(Debug, Default)] -pub struct DuckDbDialect; +#[derive(Debug)] +pub struct DuckDbDialect(DialectFlags); + +impl Default for DuckDbDialect { + fn default() -> Self { + Self(DialectFlags { + supports_trailing_commas: true, + supports_filter_during_aggregation: true, + supports_group_by_expr: true, + supports_named_fn_args_with_eq_operator: true, + // DuckDB uses this syntax for `STRUCT`s. + // + // https://duckdb.org/docs/sql/data_types/struct.html#creating-structs + supports_dictionary_syntax: true, + // DuckDB uses this syntax for `MAP`s. + // + // https://duckdb.org/docs/sql/data_types/map.html#creating-maps + support_map_literal_syntax: true, + ..Default::default() + }) + } +} // In most cases the redshift dialect is identical to [`PostgresSqlDialect`]. impl Dialect for DuckDbDialect { - fn supports_trailing_commas(&self) -> bool { - true + fn flags(&self) -> &DialectFlags { + &self.0 } fn is_identifier_start(&self, ch: char) -> bool { @@ -29,30 +49,4 @@ impl Dialect for DuckDbDialect { fn is_identifier_part(&self, ch: char) -> bool { ch.is_alphabetic() || ch.is_ascii_digit() || ch == '$' || ch == '_' } - - fn supports_filter_during_aggregation(&self) -> bool { - true - } - - fn supports_group_by_expr(&self) -> bool { - true - } - - fn supports_named_fn_args_with_eq_operator(&self) -> bool { - true - } - - // DuckDB uses this syntax for `STRUCT`s. - // - // https://duckdb.org/docs/sql/data_types/struct.html#creating-structs - fn supports_dictionary_syntax(&self) -> bool { - true - } - - // DuckDB uses this syntax for `MAP`s. - // - // https://duckdb.org/docs/sql/data_types/map.html#creating-maps - fn support_map_literal_syntax(&self) -> bool { - true - } } diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index c8f1c00d9..43b34d9d4 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -10,14 +10,40 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::dialect::Dialect; +use crate::dialect::{Dialect, DialectFlags}; /// A permissive, general purpose [`Dialect`], which parses a wide variety of SQL /// statements, from many different dialects. -#[derive(Debug, Default)] -pub struct GenericDialect; +#[derive(Debug)] +pub struct GenericDialect(DialectFlags); + +impl Default for GenericDialect { + fn default() -> Self { + Self(DialectFlags { + supports_unicode_string_literal: true, + supports_group_by_expr: true, + supports_connect_by: true, + supports_match_recognize: true, + supports_start_transaction_modifier: true, + supports_window_function_null_treatment_arg: true, + supports_dictionary_syntax: true, + supports_window_clause_named_window_reference: true, + supports_parenthesized_set_variables: true, + supports_select_wildcard_except: true, + support_map_literal_syntax: true, + allow_extract_custom: true, + allow_extract_single_quotes: true, + supports_create_index_with_clause: true, + ..Default::default() + }) + } +} impl Dialect for GenericDialect { + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_delimited_identifier_start(&self, ch: char) -> bool { ch == '"' || ch == '`' } @@ -34,60 +60,4 @@ impl Dialect for GenericDialect { || ch == '#' || ch == '_' } - - fn supports_unicode_string_literal(&self) -> bool { - true - } - - fn supports_group_by_expr(&self) -> bool { - true - } - - fn supports_connect_by(&self) -> bool { - true - } - - fn supports_match_recognize(&self) -> bool { - true - } - - fn supports_start_transaction_modifier(&self) -> bool { - true - } - - fn supports_window_function_null_treatment_arg(&self) -> bool { - true - } - - fn supports_dictionary_syntax(&self) -> bool { - true - } - - fn supports_window_clause_named_window_reference(&self) -> bool { - true - } - - fn supports_parenthesized_set_variables(&self) -> bool { - true - } - - fn supports_select_wildcard_except(&self) -> bool { - true - } - - fn support_map_literal_syntax(&self) -> bool { - true - } - - fn allow_extract_custom(&self) -> bool { - true - } - - fn allow_extract_single_quotes(&self) -> bool { - true - } - - fn supports_create_index_with_clause(&self) -> bool { - true - } } diff --git a/src/dialect/hive.rs b/src/dialect/hive.rs index b32d44cb9..f6628105a 100644 --- a/src/dialect/hive.rs +++ b/src/dialect/hive.rs @@ -10,13 +10,28 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::dialect::Dialect; +use crate::dialect::{Dialect, DialectFlags}; /// A [`Dialect`] for [Hive](https://hive.apache.org/). #[derive(Debug)] -pub struct HiveDialect {} +pub struct HiveDialect(DialectFlags); + +impl Default for HiveDialect { + fn default() -> Self { + Self(DialectFlags { + supports_filter_during_aggregation: true, + supports_numeric_prefix: true, + require_interval_qualifier: true, + ..Default::default() + }) + } +} impl Dialect for HiveDialect { + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_delimited_identifier_start(&self, ch: char) -> bool { (ch == '"') || (ch == '`') } @@ -34,16 +49,4 @@ impl Dialect for HiveDialect { || ch == '{' || ch == '}' } - - fn supports_filter_during_aggregation(&self) -> bool { - true - } - - fn supports_numeric_prefix(&self) -> bool { - true - } - - fn require_interval_qualifier(&self) -> bool { - true - } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 6b80243ff..748484367 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -63,6 +63,152 @@ macro_rules! dialect_of { }; } +/// Constant settings for a dialect. +/// +/// For configuration to be defined here, rather than on the rate: +/// * It must be a const setting, not value that's dependent on context - that has to be a function +/// * have a default that matches `Default::default()` +#[derive(Debug, Default, Clone)] +pub struct DialectFlags { + /// Determine if the dialect supports escaping characters via '\' in string literals. + /// + /// Some dialects like BigQuery and Snowflake support this while others like + /// Postgres do not. Such that the following is accepted by the former but + /// rejected by the latter. + /// ```sql + /// SELECT 'ab\'cd'; + /// ``` + /// + /// Conversely, such dialects reject the following statement which + /// otherwise would be valid in the other dialects. + /// ```sql + /// SELECT '\'; + /// ``` + pub supports_string_literal_backslash_escape: bool, + /// Determine if the dialect supports string literals with `U&` prefix. + /// This is used to specify Unicode code points in string literals. + /// For example, in PostgreSQL, the following is a valid string literal: + /// ```sql + /// SELECT U&'\0061\0062\0063'; + /// ``` + /// This is equivalent to the string literal `'abc'`. + /// See + /// - [Postgres docs](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-UESCAPE) + /// - [H2 docs](http://www.h2database.com/html/grammar.html#string) + pub supports_unicode_string_literal: bool, + /// Does the dialect support `FILTER (WHERE expr)` for aggregate queries? + pub supports_filter_during_aggregation: bool, + /// true if the dialect supports referencing another named window + /// within a window clause declaration. + /// + /// Example + /// ```sql + /// SELECT * FROM mytable + /// WINDOW mynamed_window AS another_named_window + /// ``` + pub supports_window_clause_named_window_reference: bool, + /// true if the dialect supports `ARRAY_AGG() [WITHIN GROUP (ORDER BY)]` expressions. + /// Otherwise, the dialect should expect an `ORDER BY` without the `WITHIN GROUP` clause, e.g. [`ANSI`] + /// + /// [`ANSI`]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#array-aggregate-function + pub supports_within_after_array_aggregation: bool, + /// true if the dialects supports `group sets, roll up, or cube` expressions. + pub supports_group_by_expr: bool, + /// true if the dialect supports CONNECT BY. + pub supports_connect_by: bool, + /// true if the dialect supports the MATCH_RECOGNIZE operation. + pub supports_match_recognize: bool, + /// true if the dialect supports `(NOT) IN ()` expressions + pub supports_in_empty_list: bool, + /// true if the dialect supports `BEGIN {DEFERRED | IMMEDIATE | EXCLUSIVE} [TRANSACTION]` statements + pub supports_start_transaction_modifier: bool, + /// true if the dialect supports named arguments of the form FUN(a = '1', b = '2'). + pub supports_named_fn_args_with_eq_operator: bool, + /// true if the dialect supports identifiers starting with a numeric + /// prefix such as tables named `59901_user_login` + pub supports_numeric_prefix: bool, + /// true if the dialects supports specifying null treatment + /// as part of a window function's parameter list as opposed + /// to after the parameter list. + /// + /// i.e The following syntax returns true + /// ```sql + /// FIRST_VALUE(a IGNORE NULLS) OVER () + /// ``` + /// while the following syntax returns false + /// ```sql + /// FIRST_VALUE(a) IGNORE NULLS OVER () + /// ``` + pub supports_window_function_null_treatment_arg: bool, + /// true if the dialect supports defining structs or objects using a + /// syntax like `{'x': 1, 'y': 2, 'z': 3}`. + pub supports_dictionary_syntax: bool, + /// true if the dialect supports defining object using the + /// syntax like `Map {1: 10, 2: 20}`. + pub support_map_literal_syntax: bool, + /// true if the dialect supports lambda functions, for example: + /// + /// ```sql + /// SELECT transform(array(1, 2, 3), x -> x + 1); -- returns [2,3,4] + /// ``` + pub supports_lambda_functions: bool, + /// true if the dialect supports multiple variable assignment + /// using parentheses in a `SET` variable declaration. + /// + /// ```sql + /// SET (variable[, ...]) = (expression[, ...]); + /// ``` + pub supports_parenthesized_set_variables: bool, + /// true if the dialect supports an `EXCEPT` clause following a + /// wildcard in a select list. + /// + /// For example + /// ```sql + /// SELECT * EXCEPT order_id FROM orders; + /// ``` + pub supports_select_wildcard_except: bool, + /// true if the dialect has a CONVERT function which accepts a type first + /// and an expression second, e.g. `CONVERT(varchar, 1)` + pub convert_type_before_value: bool, + /// true if the dialect supports triple quoted string + /// e.g. `"""abc"""` + pub supports_triple_quoted_string: bool, + /// Does the dialect support trailing commas around the query? + pub supports_trailing_commas: bool, + /// true if this dialect requires the `TABLE` keyword after `DESCRIBE` + /// + /// Defaults to false. + /// + /// If true, the following statement is valid: `DESCRIBE TABLE my_table` + /// If false, the following statements are valid: `DESCRIBE my_table` and `DESCRIBE table` + pub describe_requires_table_keyword: bool, + /// true if this dialect allows the `EXTRACT` function to words other than [`Keyword`]. + pub allow_extract_custom: bool, + /// Returns true if this dialect allows the `EXTRACT` function to use single quotes in the part being extracted. + pub allow_extract_single_quotes: bool, + /// Does the dialect support with clause in create index statement? + /// e.g. `CREATE INDEX idx ON t WITH (key = value, key2)` + pub supports_create_index_with_clause: bool, + /// Whether `INTERVAL` expressions require units (called "qualifiers" in the ANSI SQL spec) to be specified, + /// e.g. `INTERVAL 1 DAY` vs `INTERVAL 1`. + /// + /// Expressions within intervals (e.g. `INTERVAL '1' + '1' DAY`) are only allowed when units are required. + /// + /// See for more information. + /// + /// When `true`: + /// * `INTERVAL '1' DAY` is VALID + /// * `INTERVAL 1 + 1 DAY` is VALID + /// * `INTERVAL '1' + '1' DAY` is VALID + /// * `INTERVAL '1'` is INVALID + /// + /// When `false`: + /// * `INTERVAL '1'` is VALID + /// * `INTERVAL '1' DAY` is VALID — unit is not required, but still allowed + /// * `INTERVAL 1 + 1 DAY` is INVALID + pub require_interval_qualifier: bool, +} + /// Encapsulates the differences between SQL implementations. /// /// # SQL Dialects @@ -82,7 +228,7 @@ macro_rules! dialect_of { /// /// ``` /// # use sqlparser::dialect::AnsiDialect; -/// let dialect = AnsiDialect {}; +/// let dialect = AnsiDialect::default(); /// ``` /// /// It is also possible to dynamically create a [`Dialect`] from its @@ -107,6 +253,8 @@ pub trait Dialect: Debug + Any { self.type_id() } + fn flags(&self) -> &DialectFlags; + /// Determine if a character starts a quoted identifier. The default /// implementation, accepting "double quoted" ids is both ANSI-compliant /// and appropriate for most dialects (with the notable exception of @@ -137,183 +285,15 @@ pub trait Dialect: Debug + Any { false } - /// Determine if the dialect supports escaping characters via '\' in string literals. - /// - /// Some dialects like BigQuery and Snowflake support this while others like - /// Postgres do not. Such that the following is accepted by the former but - /// rejected by the latter. - /// ```sql - /// SELECT 'ab\'cd'; - /// ``` - /// - /// Conversely, such dialects reject the following statement which - /// otherwise would be valid in the other dialects. - /// ```sql - /// SELECT '\'; - /// ``` - fn supports_string_literal_backslash_escape(&self) -> bool { - false - } - - /// Determine if the dialect supports string literals with `U&` prefix. - /// This is used to specify Unicode code points in string literals. - /// For example, in PostgreSQL, the following is a valid string literal: - /// ```sql - /// SELECT U&'\0061\0062\0063'; - /// ``` - /// This is equivalent to the string literal `'abc'`. - /// See - /// - [Postgres docs](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-UESCAPE) - /// - [H2 docs](http://www.h2database.com/html/grammar.html#string) - fn supports_unicode_string_literal(&self) -> bool { - false - } - - /// Does the dialect support `FILTER (WHERE expr)` for aggregate queries? - fn supports_filter_during_aggregation(&self) -> bool { - false - } - - /// Returns true if the dialect supports referencing another named window - /// within a window clause declaration. - /// - /// Example - /// ```sql - /// SELECT * FROM mytable - /// WINDOW mynamed_window AS another_named_window - /// ``` - fn supports_window_clause_named_window_reference(&self) -> bool { - false - } - - /// Returns true if the dialect supports `ARRAY_AGG() [WITHIN GROUP (ORDER BY)]` expressions. - /// Otherwise, the dialect should expect an `ORDER BY` without the `WITHIN GROUP` clause, e.g. [`ANSI`] - /// - /// [`ANSI`]: https://jakewheat.github.io/sql-overview/sql-2016-foundation-grammar.html#array-aggregate-function - fn supports_within_after_array_aggregation(&self) -> bool { - false - } - - /// Returns true if the dialects supports `group sets, roll up, or cube` expressions. - fn supports_group_by_expr(&self) -> bool { - false - } - - /// Returns true if the dialect supports CONNECT BY. - fn supports_connect_by(&self) -> bool { - false - } - - /// Returns true if the dialect supports the MATCH_RECOGNIZE operation. - fn supports_match_recognize(&self) -> bool { - false - } - - /// Returns true if the dialect supports `(NOT) IN ()` expressions - fn supports_in_empty_list(&self) -> bool { - false - } - - /// Returns true if the dialect supports `BEGIN {DEFERRED | IMMEDIATE | EXCLUSIVE} [TRANSACTION]` statements - fn supports_start_transaction_modifier(&self) -> bool { - false - } - - /// Returns true if the dialect supports named arguments of the form FUN(a = '1', b = '2'). - fn supports_named_fn_args_with_eq_operator(&self) -> bool { - false - } - - /// Returns true if the dialect supports identifiers starting with a numeric - /// prefix such as tables named `59901_user_login` - fn supports_numeric_prefix(&self) -> bool { - false - } - - /// Returns true if the dialects supports specifying null treatment - /// as part of a window function's parameter list as opposed - /// to after the parameter list. - /// - /// i.e The following syntax returns true - /// ```sql - /// FIRST_VALUE(a IGNORE NULLS) OVER () - /// ``` - /// while the following syntax returns false - /// ```sql - /// FIRST_VALUE(a) IGNORE NULLS OVER () - /// ``` - fn supports_window_function_null_treatment_arg(&self) -> bool { - false - } - - /// Returns true if the dialect supports defining structs or objects using a - /// syntax like `{'x': 1, 'y': 2, 'z': 3}`. - fn supports_dictionary_syntax(&self) -> bool { - false - } - - /// Returns true if the dialect supports defining object using the - /// syntax like `Map {1: 10, 2: 20}`. - fn support_map_literal_syntax(&self) -> bool { - false - } - - /// Returns true if the dialect supports lambda functions, for example: - /// - /// ```sql - /// SELECT transform(array(1, 2, 3), x -> x + 1); -- returns [2,3,4] - /// ``` - fn supports_lambda_functions(&self) -> bool { - false - } - - /// Returns true if the dialect supports multiple variable assignment - /// using parentheses in a `SET` variable declaration. - /// - /// ```sql - /// SET (variable[, ...]) = (expression[, ...]); - /// ``` - fn supports_parenthesized_set_variables(&self) -> bool { - false - } - - /// Returns true if the dialect supports an `EXCEPT` clause following a - /// wildcard in a select list. - /// - /// For example - /// ```sql - /// SELECT * EXCEPT order_id FROM orders; - /// ``` - fn supports_select_wildcard_except(&self) -> bool { - false - } - - /// Returns true if the dialect has a CONVERT function which accepts a type first - /// and an expression second, e.g. `CONVERT(varchar, 1)` - fn convert_type_before_value(&self) -> bool { - false - } - - /// Returns true if the dialect supports triple quoted string - /// e.g. `"""abc"""` - fn supports_triple_quoted_string(&self) -> bool { - false - } - /// Dialect-specific prefix parser override fn parse_prefix(&self, _parser: &mut Parser) -> Option> { // return None to fall back to the default behavior None } - /// Does the dialect support trailing commas around the query? - fn supports_trailing_commas(&self) -> bool { - false - } - /// Does the dialect support trailing commas in the projection list? fn supports_projection_trailing_commas(&self) -> bool { - self.supports_trailing_commas() + self.flags().supports_trailing_commas } /// Dialect-specific infix parser override @@ -489,53 +469,6 @@ pub trait Dialect: Debug + Any { fn prec_unknown(&self) -> u8 { 0 } - - /// Returns true if this dialect requires the `TABLE` keyword after `DESCRIBE` - /// - /// Defaults to false. - /// - /// If true, the following statement is valid: `DESCRIBE TABLE my_table` - /// If false, the following statements are valid: `DESCRIBE my_table` and `DESCRIBE table` - fn describe_requires_table_keyword(&self) -> bool { - false - } - - /// Returns true if this dialect allows the `EXTRACT` function to words other than [`Keyword`]. - fn allow_extract_custom(&self) -> bool { - false - } - - /// Returns true if this dialect allows the `EXTRACT` function to use single quotes in the part being extracted. - fn allow_extract_single_quotes(&self) -> bool { - false - } - - /// Does the dialect support with clause in create index statement? - /// e.g. `CREATE INDEX idx ON t WITH (key = value, key2)` - fn supports_create_index_with_clause(&self) -> bool { - false - } - - /// Whether `INTERVAL` expressions require units (called "qualifiers" in the ANSI SQL spec) to be specified, - /// e.g. `INTERVAL 1 DAY` vs `INTERVAL 1`. - /// - /// Expressions within intervals (e.g. `INTERVAL '1' + '1' DAY`) are only allowed when units are required. - /// - /// See for more information. - /// - /// When `true`: - /// * `INTERVAL '1' DAY` is VALID - /// * `INTERVAL 1 + 1 DAY` is VALID - /// * `INTERVAL '1' + '1' DAY` is VALID - /// * `INTERVAL '1'` is INVALID - /// - /// When `false`: - /// * `INTERVAL '1'` is VALID - /// * `INTERVAL '1' DAY` is VALID — unit is not required, but still allowed - /// * `INTERVAL 1 + 1 DAY` is INVALID - fn require_interval_qualifier(&self) -> bool { - false - } } /// This represents the operators for which precedence must be defined @@ -575,19 +508,19 @@ impl dyn Dialect { pub fn dialect_from_str(dialect_name: impl AsRef) -> Option> { let dialect_name = dialect_name.as_ref(); match dialect_name.to_lowercase().as_str() { - "generic" => Some(Box::new(GenericDialect)), - "mysql" => Some(Box::new(MySqlDialect {})), - "postgresql" | "postgres" => Some(Box::new(PostgreSqlDialect {})), - "hive" => Some(Box::new(HiveDialect {})), - "sqlite" => Some(Box::new(SQLiteDialect {})), - "snowflake" => Some(Box::new(SnowflakeDialect)), - "redshift" => Some(Box::new(RedshiftSqlDialect {})), - "mssql" => Some(Box::new(MsSqlDialect {})), - "clickhouse" => Some(Box::new(ClickHouseDialect {})), - "bigquery" => Some(Box::new(BigQueryDialect)), - "ansi" => Some(Box::new(AnsiDialect {})), - "duckdb" => Some(Box::new(DuckDbDialect {})), - "databricks" => Some(Box::new(DatabricksDialect {})), + "generic" => Some(Box::new(GenericDialect::default())), + "mysql" => Some(Box::new(MySqlDialect::default())), + "postgresql" | "postgres" => Some(Box::new(PostgreSqlDialect::default())), + "hive" => Some(Box::new(HiveDialect::default())), + "sqlite" => Some(Box::new(SQLiteDialect::default())), + "snowflake" => Some(Box::new(SnowflakeDialect::default())), + "redshift" => Some(Box::new(RedshiftSqlDialect::default())), + "mssql" => Some(Box::new(MsSqlDialect::default())), + "clickhouse" => Some(Box::new(ClickHouseDialect::default())), + "bigquery" => Some(Box::new(BigQueryDialect::default())), + "ansi" => Some(Box::new(AnsiDialect::default())), + "duckdb" => Some(Box::new(DuckDbDialect::default())), + "databricks" => Some(Box::new(DatabricksDialect::default())), _ => None, } } @@ -602,8 +535,8 @@ mod tests { #[test] fn test_is_dialect() { - let generic_dialect: &dyn Dialect = &GenericDialect {}; - let ansi_dialect: &dyn Dialect = &AnsiDialect {}; + let generic_dialect: &dyn Dialect = &GenericDialect::default(); + let ansi_dialect: &dyn Dialect = &AnsiDialect::default(); let generic_holder = DialectHolder { dialect: generic_dialect, @@ -653,10 +586,15 @@ mod tests { #[test] fn identifier_quote_style() { + let dialects = ( + GenericDialect::default(), + SQLiteDialect::default(), + PostgreSqlDialect::default(), + ); let tests: Vec<(&dyn Dialect, &str, Option)> = vec![ - (&GenericDialect {}, "id", None), - (&SQLiteDialect {}, "id", Some('`')), - (&PostgreSqlDialect {}, "id", Some('"')), + (&dialects.0, "id", None), + (&dialects.1, "id", Some('`')), + (&dialects.2, "id", Some('"')), ]; for (dialect, ident, expected) in tests { @@ -672,10 +610,33 @@ mod tests { /// would tweak the behavior of the dialect. For the test case, /// it wraps all methods unaltered. #[derive(Debug)] - struct WrappedDialect(MySqlDialect); + struct WrappedDialect(MySqlDialect, DialectFlags); + + impl Default for WrappedDialect { + fn default() -> Self { + let mysql = MySqlDialect::default(); + let f = mysql.flags(); + let flags = DialectFlags { + supports_filter_during_aggregation: f.supports_filter_during_aggregation, + supports_within_after_array_aggregation: f + .supports_within_after_array_aggregation, + supports_group_by_expr: f.supports_group_by_expr, + supports_in_empty_list: f.supports_in_empty_list, + convert_type_before_value: f.convert_type_before_value, + supports_string_literal_backslash_escape: f + .supports_string_literal_backslash_escape, + ..Default::default() + }; + Self(mysql, flags) + } + } impl Dialect for WrappedDialect { - fn dialect(&self) -> std::any::TypeId { + fn flags(&self) -> &DialectFlags { + &self.1 + } + + fn dialect(&self) -> TypeId { self.0.dialect() } @@ -691,64 +652,31 @@ mod tests { self.0.identifier_quote_style(identifier) } - fn supports_string_literal_backslash_escape(&self) -> bool { - self.0.supports_string_literal_backslash_escape() - } - - fn is_proper_identifier_inside_quotes( - &self, - chars: std::iter::Peekable>, - ) -> bool { + fn is_proper_identifier_inside_quotes(&self, chars: Peekable>) -> bool { self.0.is_proper_identifier_inside_quotes(chars) } - fn supports_filter_during_aggregation(&self) -> bool { - self.0.supports_filter_during_aggregation() - } - - fn supports_within_after_array_aggregation(&self) -> bool { - self.0.supports_within_after_array_aggregation() - } - - fn supports_group_by_expr(&self) -> bool { - self.0.supports_group_by_expr() - } - - fn supports_in_empty_list(&self) -> bool { - self.0.supports_in_empty_list() - } - - fn convert_type_before_value(&self) -> bool { - self.0.convert_type_before_value() - } - - fn parse_prefix( - &self, - parser: &mut sqlparser::parser::Parser, - ) -> Option> { + fn parse_prefix(&self, parser: &mut Parser) -> Option> { self.0.parse_prefix(parser) } fn parse_infix( &self, - parser: &mut sqlparser::parser::Parser, + parser: &mut Parser, expr: &Expr, precedence: u8, - ) -> Option> { + ) -> Option> { self.0.parse_infix(parser, expr, precedence) } - fn get_next_precedence( - &self, - parser: &sqlparser::parser::Parser, - ) -> Option> { + fn get_next_precedence(&self, parser: &Parser) -> Option> { self.0.get_next_precedence(parser) } fn parse_statement( &self, - parser: &mut sqlparser::parser::Parser, - ) -> Option> { + parser: &mut Parser, + ) -> Option> { self.0.parse_statement(parser) } @@ -759,8 +687,8 @@ mod tests { #[allow(clippy::needless_raw_string_hashes)] let statement = r#"SELECT 'Wayne\'s World'"#; - let res1 = Parser::parse_sql(&MySqlDialect {}, statement); - let res2 = Parser::parse_sql(&WrappedDialect(MySqlDialect {}), statement); + let res1 = Parser::parse_sql(&MySqlDialect::default(), statement); + let res2 = Parser::parse_sql(&WrappedDialect::default(), statement); assert!(res1.is_ok()); assert_eq!(res1, res2); } diff --git a/src/dialect/mssql.rs b/src/dialect/mssql.rs index ec247092f..cdfc2967e 100644 --- a/src/dialect/mssql.rs +++ b/src/dialect/mssql.rs @@ -10,13 +10,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::dialect::Dialect; +use crate::dialect::{Dialect, DialectFlags}; /// A [`Dialect`] for [Microsoft SQL Server](https://www.microsoft.com/en-us/sql-server/) #[derive(Debug)] -pub struct MsSqlDialect {} +pub struct MsSqlDialect(DialectFlags); + +impl Default for MsSqlDialect { + fn default() -> Self { + Self(DialectFlags { + // SQL Server has `CONVERT(type, value)` instead of `CONVERT(value, type)` + // https://learn.microsoft.com/en-us/sql/t-sql/functions/cast-and-convert-transact-sql?view=sql-server-ver16 + convert_type_before_value: true, + supports_connect_by: true, + ..Default::default() + }) + } +} impl Dialect for MsSqlDialect { + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_delimited_identifier_start(&self, ch: char) -> bool { ch == '"' || ch == '[' } @@ -34,14 +50,4 @@ impl Dialect for MsSqlDialect { || ch == '#' || ch == '_' } - - /// SQL Server has `CONVERT(type, value)` instead of `CONVERT(value, type)` - /// - fn convert_type_before_value(&self) -> bool { - true - } - - fn supports_connect_by(&self) -> bool { - true - } } diff --git a/src/dialect/mysql.rs b/src/dialect/mysql.rs index b8c4631fd..2c0bd8646 100644 --- a/src/dialect/mysql.rs +++ b/src/dialect/mysql.rs @@ -13,6 +13,7 @@ #[cfg(not(feature = "std"))] use alloc::boxed::Box; +use crate::dialect::DialectFlags; use crate::{ ast::{BinaryOperator, Expr, LockTable, LockTableType, Statement}, dialect::Dialect, @@ -22,9 +23,25 @@ use crate::{ /// A [`Dialect`] for [MySQL](https://www.mysql.com/) #[derive(Debug)] -pub struct MySqlDialect {} +pub struct MySqlDialect(DialectFlags); + +impl Default for MySqlDialect { + fn default() -> Self { + Self(DialectFlags { + // See https://dev.mysql.com/doc/refman/8.0/en/string-literals.html#character-escape-sequences + supports_string_literal_backslash_escape: true, + supports_numeric_prefix: true, + require_interval_qualifier: true, + ..Default::default() + }) + } +} impl Dialect for MySqlDialect { + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_identifier_start(&self, ch: char) -> bool { // See https://dev.mysql.com/doc/refman/8.0/en/identifiers.html. // Identifiers which begin with a digit are recognized while tokenizing numbers, @@ -48,15 +65,6 @@ impl Dialect for MySqlDialect { Some('`') } - // See https://dev.mysql.com/doc/refman/8.0/en/string-literals.html#character-escape-sequences - fn supports_string_literal_backslash_escape(&self) -> bool { - true - } - - fn supports_numeric_prefix(&self) -> bool { - true - } - fn parse_infix( &self, parser: &mut crate::parser::Parser, @@ -84,10 +92,6 @@ impl Dialect for MySqlDialect { None } } - - fn require_interval_qualifier(&self) -> bool { - true - } } /// `LOCK TABLES` diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index eba3a6989..ee2b3a973 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -12,14 +12,28 @@ use log::debug; use crate::ast::{CommentObject, Statement}; -use crate::dialect::{Dialect, Precedence}; +use crate::dialect::{Dialect, DialectFlags, Precedence}; use crate::keywords::Keyword; use crate::parser::{Parser, ParserError}; use crate::tokenizer::Token; /// A [`Dialect`] for [PostgreSQL](https://www.postgresql.org/) #[derive(Debug)] -pub struct PostgreSqlDialect {} +pub struct PostgreSqlDialect(DialectFlags); + +impl Default for PostgreSqlDialect { + fn default() -> Self { + Self(DialectFlags { + supports_unicode_string_literal: true, + supports_filter_during_aggregation: true, + supports_group_by_expr: true, + allow_extract_custom: true, + allow_extract_single_quotes: true, + supports_create_index_with_clause: true, + ..Default::default() + }) + } +} const DOUBLE_COLON_PREC: u8 = 140; const BRACKET_PREC: u8 = 130; @@ -39,6 +53,10 @@ const AND_PREC: u8 = 20; const OR_PREC: u8 = 10; impl Dialect for PostgreSqlDialect { + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn identifier_quote_style(&self, _identifier: &str) -> Option { Some('"') } @@ -58,10 +76,6 @@ impl Dialect for PostgreSqlDialect { ch.is_alphabetic() || ch.is_ascii_digit() || ch == '$' || ch == '_' } - fn supports_unicode_string_literal(&self) -> bool { - true - } - /// See fn is_custom_operator_part(&self, ch: char) -> bool { matches!( @@ -126,14 +140,6 @@ impl Dialect for PostgreSqlDialect { } } - fn supports_filter_during_aggregation(&self) -> bool { - true - } - - fn supports_group_by_expr(&self) -> bool { - true - } - fn prec_value(&self, prec: Precedence) -> u8 { match prec { Precedence::DoubleColon => DOUBLE_COLON_PREC, @@ -154,18 +160,6 @@ impl Dialect for PostgreSqlDialect { Precedence::Or => OR_PREC, } } - - fn allow_extract_custom(&self) -> bool { - true - } - - fn allow_extract_single_quotes(&self) -> bool { - true - } - - fn supports_create_index_with_clause(&self) -> bool { - true - } } pub fn parse_comment(parser: &mut Parser) -> Result { diff --git a/src/dialect/redshift.rs b/src/dialect/redshift.rs index bd4dc817c..237ad68b1 100644 --- a/src/dialect/redshift.rs +++ b/src/dialect/redshift.rs @@ -10,7 +10,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -use crate::dialect::Dialect; +use crate::dialect::{Dialect, DialectFlags}; use core::iter::Peekable; use core::str::Chars; @@ -18,7 +18,19 @@ use super::PostgreSqlDialect; /// A [`Dialect`] for [RedShift](https://aws.amazon.com/redshift/) #[derive(Debug)] -pub struct RedshiftSqlDialect {} +pub struct RedshiftSqlDialect(DialectFlags); + +impl Default for RedshiftSqlDialect { + fn default() -> Self { + Self(DialectFlags { + // redshift has `CONVERT(type, value)` instead of `CONVERT(value, type)` + // + convert_type_before_value: true, + supports_connect_by: true, + ..Default::default() + }) + } +} // In most cases the redshift dialect is identical to [`PostgresSqlDialect`]. // @@ -27,6 +39,10 @@ pub struct RedshiftSqlDialect {} // 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 { + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_delimited_identifier_start(&self, ch: char) -> bool { ch == '"' || ch == '[' } @@ -46,21 +62,11 @@ impl Dialect for RedshiftSqlDialect { fn is_identifier_start(&self, ch: char) -> bool { // Extends Postgres dialect with sharp - PostgreSqlDialect {}.is_identifier_start(ch) || ch == '#' + PostgreSqlDialect::default().is_identifier_start(ch) || ch == '#' } fn is_identifier_part(&self, ch: char) -> bool { // Extends Postgres dialect with sharp - PostgreSqlDialect {}.is_identifier_part(ch) || ch == '#' - } - - /// redshift has `CONVERT(type, value)` instead of `CONVERT(value, type)` - /// - fn convert_type_before_value(&self) -> bool { - true - } - - fn supports_connect_by(&self) -> bool { - true + PostgreSqlDialect::default().is_identifier_part(ch) || ch == '#' } } diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 4f37004b1..34e0e7349 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -20,7 +20,7 @@ use crate::ast::helpers::stmt_data_loading::{ use crate::ast::{ CommentDef, Ident, ObjectName, RowAccessPolicy, Statement, Tag, WrappedCollection, }; -use crate::dialect::{Dialect, Precedence}; +use crate::dialect::{Dialect, DialectFlags, Precedence}; use crate::keywords::Keyword; use crate::parser::{Parser, ParserError}; use crate::tokenizer::Token; @@ -32,11 +32,41 @@ use alloc::vec::Vec; use alloc::{format, vec}; /// A [`Dialect`] for [Snowflake](https://www.snowflake.com/) -#[derive(Debug, Default)] -pub struct SnowflakeDialect; +#[derive(Debug)] +pub struct SnowflakeDialect(DialectFlags); + +impl Default for SnowflakeDialect { + fn default() -> Self { + Self(DialectFlags { + // See https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#escape_sequences + supports_string_literal_backslash_escape: true, + supports_within_after_array_aggregation: true, + supports_connect_by: true, + supports_match_recognize: true, + // Snowflake uses this syntax for "object constants" (the values of which + // are not actually required to be constants). + // + // https://docs.snowflake.com/en/sql-reference/data-types-semistructured#label-object-constant + supports_dictionary_syntax: true, + // Snowflake doesn't document this but `FIRST_VALUE(arg, { IGNORE | RESPECT } NULLS)` + // works (i.e. inside the argument list instead of after). + supports_window_function_null_treatment_arg: true, + // See https://docs.snowflake.com/en/sql-reference/sql/set#syntax + supports_parenthesized_set_variables: true, + describe_requires_table_keyword: true, + allow_extract_custom: true, + allow_extract_single_quotes: true, + ..Default::default() + }) + } +} +/// see impl Dialect for SnowflakeDialect { - // see https://docs.snowflake.com/en/sql-reference/identifiers-syntax.html + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_identifier_start(&self, ch: char) -> bool { ch.is_ascii_lowercase() || ch.is_ascii_uppercase() || ch == '_' } @@ -53,42 +83,6 @@ impl Dialect for SnowflakeDialect { || ch == '_' } - // See https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#escape_sequences - fn supports_string_literal_backslash_escape(&self) -> bool { - true - } - - fn supports_within_after_array_aggregation(&self) -> bool { - true - } - - fn supports_connect_by(&self) -> bool { - true - } - - fn supports_match_recognize(&self) -> bool { - true - } - - // Snowflake uses this syntax for "object constants" (the values of which - // are not actually required to be constants). - // - // https://docs.snowflake.com/en/sql-reference/data-types-semistructured#label-object-constant - fn supports_dictionary_syntax(&self) -> bool { - true - } - - // Snowflake doesn't document this but `FIRST_VALUE(arg, { IGNORE | RESPECT } NULLS)` - // works (i.e. inside the argument list instead of after). - fn supports_window_function_null_treatment_arg(&self) -> bool { - true - } - - /// See [doc](https://docs.snowflake.com/en/sql-reference/sql/set#syntax) - fn supports_parenthesized_set_variables(&self) -> bool { - true - } - fn parse_statement(&self, parser: &mut Parser) -> Option> { if parser.parse_keyword(Keyword::CREATE) { // possibly CREATE STAGE @@ -154,18 +148,6 @@ impl Dialect for SnowflakeDialect { _ => None, } } - - fn describe_requires_table_keyword(&self) -> bool { - true - } - - fn allow_extract_custom(&self) -> bool { - true - } - - fn allow_extract_single_quotes(&self) -> bool { - true - } } /// Parse snowflake create table statement. diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs index cc08d7961..a4861e078 100644 --- a/src/dialect/sqlite.rs +++ b/src/dialect/sqlite.rs @@ -11,7 +11,7 @@ // limitations under the License. use crate::ast::Statement; -use crate::dialect::Dialect; +use crate::dialect::{Dialect, DialectFlags}; use crate::keywords::Keyword; use crate::parser::{Parser, ParserError}; @@ -22,12 +22,27 @@ use crate::parser::{Parser, ParserError}; /// type specified, as in `CREATE TABLE t1 (a)`. In the AST, these columns will /// have the data type [`Unspecified`](crate::ast::DataType::Unspecified). #[derive(Debug)] -pub struct SQLiteDialect {} +pub struct SQLiteDialect(DialectFlags); +impl Default for SQLiteDialect { + fn default() -> Self { + Self(DialectFlags { + supports_filter_during_aggregation: true, + supports_start_transaction_modifier: true, + supports_in_empty_list: true, + ..Default::default() + }) + } +} + +/// see +/// parse `...`, [...] and "..." as identifier +/// TODO: support depending on the context tread '...' as identifier too. impl Dialect for SQLiteDialect { - // see https://www.sqlite.org/lang_keywords.html - // parse `...`, [...] and "..." as identifier - // TODO: support depending on the context tread '...' as identifier too. + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_delimited_identifier_start(&self, ch: char) -> bool { ch == '`' || ch == '"' || ch == '[' } @@ -44,14 +59,6 @@ impl Dialect for SQLiteDialect { || ('\u{007f}'..='\u{ffff}').contains(&ch) } - fn supports_filter_during_aggregation(&self) -> bool { - true - } - - fn supports_start_transaction_modifier(&self) -> bool { - true - } - fn is_identifier_part(&self, ch: char) -> bool { self.is_identifier_start(ch) || ch.is_ascii_digit() } @@ -64,8 +71,4 @@ impl Dialect for SQLiteDialect { None } } - - fn supports_in_empty_list(&self) -> bool { - true - } } diff --git a/src/lib.rs b/src/lib.rs index ba0132ed8..3f9c74eef 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -27,7 +27,7 @@ //! use sqlparser::dialect::GenericDialect; //! use sqlparser::parser::Parser; //! -//! let dialect = GenericDialect {}; // or AnsiDialect +//! let dialect = GenericDialect::default(); // or AnsiDialect //! //! let sql = "SELECT a, b, 123, myfunc(b) \ //! FROM table_1 \ @@ -51,7 +51,7 @@ //! let sql = "SELECT a FROM table_1"; //! //! // parse to a Vec -//! let ast = Parser::parse_sql(&GenericDialect, sql).unwrap(); +//! let ast = Parser::parse_sql(&GenericDialect::default(), sql).unwrap(); //! //! // The original SQL text can be generated from the AST //! assert_eq!(ast[0].to_string(), sql); diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 8d920b2ce..2f34954f6 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -287,7 +287,7 @@ impl<'a> Parser<'a> { /// ``` /// # use sqlparser::{parser::{Parser, ParserError}, dialect::GenericDialect}; /// # fn main() -> Result<(), ParserError> { - /// let dialect = GenericDialect{}; + /// let dialect = GenericDialect::default(); /// let statements = Parser::new(&dialect) /// .try_with_sql("SELECT * FROM foo")? /// .parse_statements()?; @@ -301,7 +301,8 @@ impl<'a> Parser<'a> { state: ParserState::Normal, dialect, recursion_counter: RecursionCounter::new(DEFAULT_REMAINING_DEPTH), - options: ParserOptions::new().with_trailing_commas(dialect.supports_trailing_commas()), + options: ParserOptions::new() + .with_trailing_commas(dialect.flags().supports_trailing_commas), } } @@ -315,7 +316,7 @@ impl<'a> Parser<'a> { /// ``` /// # use sqlparser::{parser::{Parser, ParserError}, dialect::GenericDialect}; /// # fn main() -> Result<(), ParserError> { - /// let dialect = GenericDialect{}; + /// let dialect = GenericDialect::default(); /// let result = Parser::new(&dialect) /// .with_recursion_limit(1) /// .try_with_sql("SELECT * FROM foo WHERE (a OR (b OR (c OR d)))")? @@ -339,7 +340,7 @@ impl<'a> Parser<'a> { /// ``` /// # use sqlparser::{parser::{Parser, ParserError, ParserOptions}, dialect::GenericDialect}; /// # fn main() -> Result<(), ParserError> { - /// let dialect = GenericDialect{}; + /// let dialect = GenericDialect::default(); /// let options = ParserOptions::new() /// .with_trailing_commas(true) /// .with_unescape(false); @@ -396,7 +397,7 @@ impl<'a> Parser<'a> { /// ``` /// # use sqlparser::{parser::{Parser, ParserError}, dialect::GenericDialect}; /// # fn main() -> Result<(), ParserError> { - /// let dialect = GenericDialect{}; + /// let dialect = GenericDialect::default(); /// let statements = Parser::new(&dialect) /// // Parse a SQL string with 2 separate statements /// .try_with_sql("SELECT * FROM foo; SELECT * FROM bar;")? @@ -444,7 +445,7 @@ impl<'a> Parser<'a> { /// ``` /// # use sqlparser::{parser::{Parser, ParserError}, dialect::GenericDialect}; /// # fn main() -> Result<(), ParserError> { - /// let dialect = GenericDialect{}; + /// let dialect = GenericDialect::default(); /// let statements = Parser::parse_sql( /// &dialect, "SELECT * FROM foo" /// )?; @@ -1080,7 +1081,7 @@ impl<'a> Parser<'a> { let expr = self.parse_subexpr(self.dialect.prec_value(Precedence::PlusMinus))?; Ok(Expr::Prior(Box::new(expr))) } - Keyword::MAP if self.peek_token() == Token::LBrace && self.dialect.support_map_literal_syntax() => { + Keyword::MAP if self.peek_token() == Token::LBrace && self.dialect.flags().support_map_literal_syntax => { self.parse_duckdb_map_literal() } // Here `w` is a word, check if it's a part of a multipart @@ -1145,7 +1146,7 @@ impl<'a> Parser<'a> { value: self.parse_introduced_string_value()?, }) } - Token::Arrow if self.dialect.supports_lambda_functions() => { + Token::Arrow if self.dialect.flags().supports_lambda_functions => { self.expect_token(&Token::Arrow)?; return Ok(Expr::Lambda(LambdaFunction { params: OneOrManyWithParens::One(w.to_ident()), @@ -1257,7 +1258,7 @@ impl<'a> Parser<'a> { self.prev_token(); Ok(Expr::Value(self.parse_value()?)) } - Token::LBrace if self.dialect.supports_dictionary_syntax() => { + Token::LBrace if self.dialect.flags().supports_dictionary_syntax => { self.prev_token(); self.parse_duckdb_struct_literal() } @@ -1287,7 +1288,7 @@ impl<'a> Parser<'a> { } fn try_parse_lambda(&mut self) -> Option { - if !self.dialect.supports_lambda_functions() { + if !self.dialect.flags().supports_lambda_functions { return None; } self.maybe_parse(|p| { @@ -1347,7 +1348,7 @@ impl<'a> Parser<'a> { vec![] }; - let filter = if self.dialect.supports_filter_during_aggregation() + let filter = if self.dialect.flags().supports_filter_during_aggregation && self.parse_keyword(Keyword::FILTER) && self.consume_token(&Token::LParen) && self.parse_keyword(Keyword::WHERE) @@ -1481,7 +1482,7 @@ impl<'a> Parser<'a> { /// Parse a group by expr. Group by expr can be one of group sets, roll up, cube, or simple expr. fn parse_group_by_expr(&mut self) -> Result { - if self.dialect.supports_group_by_expr() { + if self.dialect.flags().supports_group_by_expr { if self.parse_keywords(&[Keyword::GROUPING, Keyword::SETS]) { self.expect_token(&Token::LParen)?; let result = self.parse_comma_separated(|p| p.parse_tuple(false, true))?; @@ -1621,7 +1622,7 @@ impl<'a> Parser<'a> { /// - `CONVERT('héhé', CHAR CHARACTER SET utf8mb4)` (MySQL) /// - `CONVERT(DECIMAL(10, 5), 42)` (MSSQL) - the type comes first pub fn parse_convert_expr(&mut self) -> Result { - if self.dialect.convert_type_before_value() { + if self.dialect.flags().convert_type_before_value { return self.parse_mssql_convert(); } self.expect_token(&Token::LParen)?; @@ -1968,14 +1969,14 @@ impl<'a> Parser<'a> { Keyword::TIMEZONE_HOUR => Ok(DateTimeField::TimezoneHour), Keyword::TIMEZONE_MINUTE => Ok(DateTimeField::TimezoneMinute), Keyword::TIMEZONE_REGION => Ok(DateTimeField::TimezoneRegion), - _ if self.dialect.allow_extract_custom() => { + _ if self.dialect.flags().allow_extract_custom => { self.prev_token(); let custom = self.parse_identifier(false)?; Ok(DateTimeField::Custom(custom)) } _ => self.expected("date/time field", next_token), }, - Token::SingleQuotedString(_) if self.dialect.allow_extract_single_quotes() => { + Token::SingleQuotedString(_) if self.dialect.flags().allow_extract_single_quotes => { self.prev_token(); let custom = self.parse_identifier(false)?; Ok(DateTimeField::Custom(custom)) @@ -2079,7 +2080,7 @@ impl<'a> Parser<'a> { // to match the different flavours of INTERVAL syntax, we only allow expressions // if the dialect requires an interval qualifier, // see https://github.com/sqlparser-rs/sqlparser-rs/pull/1398 for more details - let value = if self.dialect.require_interval_qualifier() { + let value = if self.dialect.flags().require_interval_qualifier { // parse a whole expression so `INTERVAL 1 + 1 DAY` is valid self.parse_expr()? } else { @@ -2095,7 +2096,7 @@ impl<'a> Parser<'a> { // this more general implementation. let leading_field = if self.next_token_is_temporal_unit() { Some(self.parse_date_time_field()?) - } else if self.dialect.require_interval_qualifier() { + } else if self.dialect.flags().require_interval_qualifier { return parser_err!( "INTERVAL requires a unit after the literal value", self.peek_token().location @@ -3002,7 +3003,7 @@ impl<'a> Parser<'a> { } else { Expr::InList { expr: Box::new(expr), - list: if self.dialect.supports_in_empty_list() { + list: if self.dialect.flags().supports_in_empty_list { self.parse_comma_separated0(Parser::parse_expr, Token::RParen)? } else { self.parse_comma_separated(Parser::parse_expr)? @@ -3059,7 +3060,7 @@ impl<'a> Parser<'a> { /// # use sqlparser::parser::Parser; /// # use sqlparser::keywords::Keyword; /// # use sqlparser::tokenizer::{Token, Word}; - /// let dialect = GenericDialect {}; + /// let dialect = GenericDialect::default(); /// let mut parser = Parser::new(&dialect).try_with_sql("ORDER BY foo, bar").unwrap(); /// /// // Note that Rust infers the number of tokens to peek based on the @@ -5341,7 +5342,7 @@ impl<'a> Parser<'a> { None }; - let with = if self.dialect.supports_create_index_with_clause() + let with = if self.dialect.flags().supports_create_index_with_clause && self.parse_keyword(Keyword::WITH) { self.expect_token(&Token::LParen)?; @@ -8013,7 +8014,7 @@ impl<'a> Parser<'a> { /// use sqlparser::dialect::GenericDialect; /// use sqlparser::parser::Parser; /// - /// let dialect = GenericDialect {}; + /// let dialect = GenericDialect::default(); /// let expected = vec![Ident::new("one"), Ident::new("two")]; /// /// // expected usage @@ -8466,7 +8467,7 @@ impl<'a> Parser<'a> { _ => None, }; - let has_table_keyword = if self.dialect.describe_requires_table_keyword() { + let has_table_keyword = if self.dialect.flags().describe_requires_table_keyword { // only allow to use TABLE keyword for DESC|DESCRIBE statement self.parse_keyword(Keyword::TABLE) } else { @@ -9058,7 +9059,7 @@ impl<'a> Parser<'a> { Default::default() }; - let connect_by = if self.dialect.supports_connect_by() + let connect_by = if self.dialect.flags().supports_connect_by && self .parse_one_of_keywords(&[Keyword::START, Keyword::CONNECT]) .is_some() @@ -9198,7 +9199,7 @@ impl<'a> Parser<'a> { let variables = if self.parse_keywords(&[Keyword::TIME, Keyword::ZONE]) { OneOrManyWithParens::One(ObjectName(vec!["TIMEZONE".into()])) - } else if self.dialect.supports_parenthesized_set_variables() + } else if self.dialect.flags().supports_parenthesized_set_variables && self.consume_token(&Token::LParen) { let variables = OneOrManyWithParens::Many( @@ -9887,7 +9888,7 @@ impl<'a> Parser<'a> { } } - if self.dialect.supports_match_recognize() + if self.dialect.flags().supports_match_recognize && self.parse_keyword(Keyword::MATCH_RECOGNIZE) { table = self.parse_match_recognize(table)?; @@ -10750,7 +10751,7 @@ impl<'a> Parser<'a> { arg, operator: FunctionArgOperator::RightArrow, }) - } else if self.dialect.supports_named_fn_args_with_eq_operator() + } else if self.dialect.flags().supports_named_fn_args_with_eq_operator && self.peek_nth_token(1) == Token::Eq { let name = self.parse_identifier(false)?; @@ -10834,7 +10835,11 @@ impl<'a> Parser<'a> { let mut clauses = vec![]; - if self.dialect.supports_window_function_null_treatment_arg() { + if self + .dialect + .flags() + .supports_window_function_null_treatment_arg + { if let Some(null_treatment) = self.parse_null_treatment()? { clauses.push(FunctionArgumentClause::IgnoreOrRespectNulls(null_treatment)); } @@ -10938,7 +10943,7 @@ impl<'a> Parser<'a> { } else { None }; - let opt_except = if self.dialect.supports_select_wildcard_except() { + let opt_except = if self.dialect.flags().supports_select_wildcard_except { self.parse_optional_select_item_except()? } else { None @@ -11327,7 +11332,7 @@ impl<'a> Parser<'a> { } pub fn parse_begin(&mut self) -> Result { - let modifier = if !self.dialect.supports_start_transaction_modifier() { + let modifier = if !self.dialect.flags().supports_start_transaction_modifier { None } else if self.parse_keyword(Keyword::DEFERRED) { Some(TransactionModifier::Deferred) @@ -11810,7 +11815,11 @@ impl<'a> Parser<'a> { let window_expr = if self.consume_token(&Token::LParen) { NamedWindowExpr::WindowSpec(self.parse_window_spec()?) - } else if self.dialect.supports_window_clause_named_window_reference() { + } else if self + .dialect + .flags() + .supports_window_clause_named_window_reference + { NamedWindowExpr::NamedWindow(self.parse_identifier(false)?) } else { return self.expected("(", self.peek_token()); @@ -12037,7 +12046,10 @@ mod tests { fn test_ansii_character_string_types() { // Character string types: let dialect = TestedDialects { - dialects: vec![Box::new(GenericDialect {}), Box::new(AnsiDialect {})], + dialects: vec![ + Box::new(GenericDialect::default()), + Box::new(AnsiDialect::default()), + ], options: None, }; @@ -12167,7 +12179,10 @@ mod tests { fn test_ansii_character_large_object_types() { // Character large object types: let dialect = TestedDialects { - dialects: vec![Box::new(GenericDialect {}), Box::new(AnsiDialect {})], + dialects: vec![ + Box::new(GenericDialect::default()), + Box::new(AnsiDialect::default()), + ], options: None, }; @@ -12200,7 +12215,10 @@ mod tests { #[test] fn test_parse_custom_types() { let dialect = TestedDialects { - dialects: vec![Box::new(GenericDialect {}), Box::new(AnsiDialect {})], + dialects: vec![ + Box::new(GenericDialect::default()), + Box::new(AnsiDialect::default()), + ], options: None, }; test_parse_data_type!( @@ -12232,7 +12250,10 @@ mod tests { fn test_ansii_exact_numeric_types() { // Exact numeric types: let dialect = TestedDialects { - dialects: vec![Box::new(GenericDialect {}), Box::new(AnsiDialect {})], + dialects: vec![ + Box::new(GenericDialect::default()), + Box::new(AnsiDialect::default()), + ], options: None, }; @@ -12283,7 +12304,10 @@ mod tests { fn test_ansii_date_type() { // Datetime types: let dialect = TestedDialects { - dialects: vec![Box::new(GenericDialect {}), Box::new(AnsiDialect {})], + dialects: vec![ + Box::new(GenericDialect::default()), + Box::new(AnsiDialect::default()), + ], options: None, }; @@ -12395,7 +12419,10 @@ mod tests { } let dialect = TestedDialects { - dialects: vec![Box::new(GenericDialect {}), Box::new(MySqlDialect {})], + dialects: vec![ + Box::new(GenericDialect::default()), + Box::new(MySqlDialect::default()), + ], options: None, }; @@ -12480,7 +12507,8 @@ mod tests { #[test] fn test_tokenizer_error_loc() { let sql = "foo '"; - let ast = Parser::parse_sql(&GenericDialect, sql); + let dialect = GenericDialect::default(); + let ast = Parser::parse_sql(&dialect, sql); assert_eq!( ast, Err(ParserError::TokenizerError( @@ -12492,7 +12520,8 @@ mod tests { #[test] fn test_parser_error_loc() { let sql = "SELECT this is a syntax error"; - let ast = Parser::parse_sql(&GenericDialect, sql); + let dialect = GenericDialect::default(); + let ast = Parser::parse_sql(&dialect, sql); assert_eq!( ast, Err(ParserError::ParserError( @@ -12505,7 +12534,8 @@ mod tests { #[test] fn test_nested_explain_error() { let sql = "EXPLAIN EXPLAIN SELECT 1"; - let ast = Parser::parse_sql(&GenericDialect, sql); + let dialect = GenericDialect::default(); + let ast = Parser::parse_sql(&dialect, sql); assert_eq!( ast, Err(ParserError::ParserError( @@ -12517,7 +12547,7 @@ mod tests { #[test] fn test_parse_multipart_identifier_positive() { let dialect = TestedDialects { - dialects: vec![Box::new(GenericDialect {})], + dialects: vec![Box::new(GenericDialect::default())], options: None, }; @@ -12600,7 +12630,7 @@ mod tests { let sql = "SELECT * FROM employees PARTITION (p0, p2)"; let expected = vec!["p0", "p2"]; - let ast: Vec = Parser::parse_sql(&MySqlDialect {}, sql).unwrap(); + let ast: Vec = Parser::parse_sql(&MySqlDialect::default(), sql).unwrap(); assert_eq!(ast.len(), 1); if let Statement::Query(v) = &ast[0] { if let SetExpr::Select(select) = &*v.body { @@ -12624,7 +12654,7 @@ mod tests { fn test_replace_into_placeholders() { let sql = "REPLACE INTO t (a) VALUES (&a)"; - assert!(Parser::parse_sql(&GenericDialect {}, sql).is_err()); + assert!(Parser::parse_sql(&GenericDialect::default(), sql).is_err()); } #[test] @@ -12634,20 +12664,20 @@ mod tests { // https://dev.mysql.com/doc/refman/8.3/en/insert.html let sql = "REPLACE INTO t SET a='1'"; - assert!(Parser::parse_sql(&MySqlDialect {}, sql).is_err()); + assert!(Parser::parse_sql(&MySqlDialect::default(), sql).is_err()); } #[test] fn test_replace_into_set_placeholder() { let sql = "REPLACE INTO t SET ?"; - assert!(Parser::parse_sql(&GenericDialect {}, sql).is_err()); + assert!(Parser::parse_sql(&GenericDialect::default(), sql).is_err()); } #[test] fn test_replace_incomplete() { let sql = r#"REPLACE"#; - assert!(Parser::parse_sql(&MySqlDialect {}, sql).is_err()); + assert!(Parser::parse_sql(&MySqlDialect::default(), sql).is_err()); } } diff --git a/src/test_utils.rs b/src/test_utils.rs index 5c05ec996..1658ac0fe 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -207,18 +207,18 @@ impl TestedDialects { /// Returns all available dialects. pub fn all_dialects() -> TestedDialects { let all_dialects = vec![ - Box::new(GenericDialect {}) as Box, - Box::new(PostgreSqlDialect {}) as Box, - Box::new(MsSqlDialect {}) as Box, - Box::new(AnsiDialect {}) as Box, - Box::new(SnowflakeDialect {}) as Box, - Box::new(HiveDialect {}) as Box, - Box::new(RedshiftSqlDialect {}) as Box, - Box::new(MySqlDialect {}) as Box, - Box::new(BigQueryDialect {}) as Box, - Box::new(SQLiteDialect {}) as Box, - Box::new(DuckDbDialect {}) as Box, - Box::new(DatabricksDialect {}) as Box, + Box::new(GenericDialect::default()) as Box, + Box::new(PostgreSqlDialect::default()) as Box, + Box::new(MsSqlDialect::default()) as Box, + Box::new(AnsiDialect::default()) as Box, + Box::new(SnowflakeDialect::default()) as Box, + Box::new(HiveDialect::default()) as Box, + Box::new(RedshiftSqlDialect::default()) as Box, + Box::new(MySqlDialect::default()) as Box, + Box::new(BigQueryDialect::default()) as Box, + Box::new(SQLiteDialect::default()) as Box, + Box::new(DuckDbDialect::default()) as Box, + Box::new(DatabricksDialect::default()) as Box, ]; TestedDialects { dialects: all_dialects, diff --git a/src/tokenizer.rs b/src/tokenizer.rs index be11a3140..ea4e4116e 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -569,7 +569,7 @@ impl<'a> Tokenizer<'a> { /// ``` /// # use sqlparser::tokenizer::{Token, Whitespace, Tokenizer}; /// # use sqlparser::dialect::GenericDialect; - /// # let dialect = GenericDialect{}; + /// # let dialect = GenericDialect::default(); /// let query = r#"SELECT 'foo'"#; /// /// // Parsing the query @@ -603,7 +603,7 @@ impl<'a> Tokenizer<'a> { /// ``` /// # use sqlparser::tokenizer::{Token, Tokenizer}; /// # use sqlparser::dialect::GenericDialect; - /// # let dialect = GenericDialect{}; + /// # let dialect = GenericDialect::default(); /// let query = r#""Foo "" Bar""#; /// let unescaped = Token::make_word(r#"Foo " Bar"#, Some('"')); /// let original = Token::make_word(r#"Foo "" Bar"#, Some('"')); @@ -703,7 +703,7 @@ impl<'a> Tokenizer<'a> { chars.next(); // consume match chars.peek() { Some('\'') => { - if self.dialect.supports_triple_quoted_string() { + if self.dialect.flags().supports_triple_quoted_string { return self .tokenize_single_or_triple_quoted_string:: Token>( chars, @@ -717,7 +717,7 @@ impl<'a> Tokenizer<'a> { Ok(Some(Token::SingleQuotedByteStringLiteral(s))) } Some('\"') => { - if self.dialect.supports_triple_quoted_string() { + if self.dialect.flags().supports_triple_quoted_string { return self .tokenize_single_or_triple_quoted_string:: Token>( chars, @@ -798,7 +798,7 @@ impl<'a> Tokenizer<'a> { } } // Unicode string literals like U&'first \000A second' are supported in some dialects, including PostgreSQL - x @ 'u' | x @ 'U' if self.dialect.supports_unicode_string_literal() => { + x @ 'u' | x @ 'U' if self.dialect.flags().supports_unicode_string_literal => { chars.next(); // consume, to check the next char if chars.peek() == Some(&'&') { // we cannot advance the iterator here, as we need to consume the '&' later if the 'u' was an identifier @@ -833,12 +833,14 @@ impl<'a> Tokenizer<'a> { } // single quoted string '\'' => { - if self.dialect.supports_triple_quoted_string() { + if self.dialect.flags().supports_triple_quoted_string { return self .tokenize_single_or_triple_quoted_string:: Token>( chars, '\'', - self.dialect.supports_string_literal_backslash_escape(), + self.dialect + .flags() + .supports_string_literal_backslash_escape, Token::SingleQuotedString, Token::TripleSingleQuotedString, ); @@ -846,7 +848,9 @@ impl<'a> Tokenizer<'a> { let s = self.tokenize_single_quoted_string( chars, '\'', - self.dialect.supports_string_literal_backslash_escape(), + self.dialect + .flags() + .supports_string_literal_backslash_escape, )?; Ok(Some(Token::SingleQuotedString(s))) @@ -855,12 +859,14 @@ impl<'a> Tokenizer<'a> { '\"' if !self.dialect.is_delimited_identifier_start(ch) && !self.dialect.is_identifier_start(ch) => { - if self.dialect.supports_triple_quoted_string() { + if self.dialect.flags().supports_triple_quoted_string { return self .tokenize_single_or_triple_quoted_string:: Token>( chars, '"', - self.dialect.supports_string_literal_backslash_escape(), + self.dialect + .flags() + .supports_string_literal_backslash_escape, Token::DoubleQuotedString, Token::TripleDoubleQuotedString, ); @@ -868,7 +874,9 @@ impl<'a> Tokenizer<'a> { let s = self.tokenize_single_quoted_string( chars, '"', - self.dialect.supports_string_literal_backslash_escape(), + self.dialect + .flags() + .supports_string_literal_backslash_escape, )?; Ok(Some(Token::DoubleQuotedString(s))) @@ -949,7 +957,7 @@ impl<'a> Tokenizer<'a> { // mysql dialect supports identifiers that start with a numeric prefix, // as long as they aren't an exponent number. - if self.dialect.supports_numeric_prefix() && exponent_part.is_empty() { + if self.dialect.flags().supports_numeric_prefix && exponent_part.is_empty() { let word = peeking_take_while(chars, |ch| self.dialect.is_identifier_part(ch)); @@ -1879,7 +1887,7 @@ fn take_char_from_hex_digits( mod tests { use super::*; use crate::dialect::{ - BigQueryDialect, ClickHouseDialect, HiveDialect, MsSqlDialect, MySqlDialect, + BigQueryDialect, ClickHouseDialect, DialectFlags, HiveDialect, MsSqlDialect, MySqlDialect, }; use core::fmt::Debug; @@ -1900,7 +1908,7 @@ mod tests { #[test] fn tokenize_select_1() { let sql = String::from("SELECT 1"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ @@ -1915,7 +1923,7 @@ mod tests { #[test] fn tokenize_select_float() { let sql = String::from("SELECT .1"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ @@ -1930,7 +1938,7 @@ mod tests { #[test] fn tokenize_clickhouse_double_equal() { let sql = String::from("SELECT foo=='1'"); - let dialect = ClickHouseDialect {}; + let dialect = ClickHouseDialect::default(); let mut tokenizer = Tokenizer::new(&dialect, &sql); let tokens = tokenizer.tokenize().unwrap(); @@ -1952,7 +1960,7 @@ mod tests { #[test] fn tokenize_select_exponent() { let sql = String::from("SELECT 1e10, 1e-10, 1e+10, 1ea, 1e-10a, 1e-10-10"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ @@ -1986,7 +1994,7 @@ mod tests { #[test] fn tokenize_scalar_function() { let sql = String::from("SELECT sqrt(1)"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ @@ -2004,7 +2012,7 @@ mod tests { #[test] fn tokenize_string_string_concat() { let sql = String::from("SELECT 'a' || 'b'"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ @@ -2022,7 +2030,7 @@ mod tests { #[test] fn tokenize_bitwise_op() { let sql = String::from("SELECT one | two ^ three"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ @@ -2045,7 +2053,7 @@ mod tests { fn tokenize_logical_xor() { let sql = String::from("SELECT true XOR true, false XOR false, true XOR false, false XOR true"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ @@ -2084,7 +2092,7 @@ mod tests { #[test] fn tokenize_simple_select() { let sql = String::from("SELECT * FROM customer WHERE id = 1 LIMIT 5"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ @@ -2115,7 +2123,7 @@ mod tests { #[test] fn tokenize_explain_select() { let sql = String::from("EXPLAIN SELECT * FROM customer WHERE id = 1"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ @@ -2144,7 +2152,7 @@ mod tests { #[test] fn tokenize_explain_analyze_select() { let sql = String::from("EXPLAIN ANALYZE SELECT * FROM customer WHERE id = 1"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ @@ -2175,7 +2183,7 @@ mod tests { #[test] fn tokenize_string_predicate() { let sql = String::from("SELECT * FROM customer WHERE salary != 'Not Provided'"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ @@ -2203,7 +2211,7 @@ mod tests { fn tokenize_invalid_string() { let sql = String::from("\n💝مصطفىh"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); // println!("tokens: {:#?}", tokens); let expected = vec![ @@ -2218,7 +2226,7 @@ mod tests { fn tokenize_newline_in_string_literal() { let sql = String::from("'foo\r\nbar\nbaz'"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![Token::SingleQuotedString("foo\r\nbar\nbaz".to_string())]; compare(expected, tokens); @@ -2228,7 +2236,7 @@ mod tests { fn tokenize_unterminated_string_literal() { let sql = String::from("select 'foo"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let mut tokenizer = Tokenizer::new(&dialect, &sql); assert_eq!( tokenizer.tokenize(), @@ -2243,7 +2251,7 @@ mod tests { fn tokenize_unterminated_string_literal_utf8() { let sql = String::from("SELECT \"なにか\" FROM Y WHERE \"なにか\" = 'test;"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let mut tokenizer = Tokenizer::new(&dialect, &sql); assert_eq!( tokenizer.tokenize(), @@ -2261,7 +2269,7 @@ mod tests { fn tokenize_invalid_string_cols() { let sql = String::from("\n\nSELECT * FROM table\t💝مصطفىh"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); // println!("tokens: {:#?}", tokens); let expected = vec![ @@ -2286,7 +2294,7 @@ mod tests { let sql = String::from( "SELECT $tag$dollar '$' quoted strings have $tags like this$ or like this $$$tag$", ); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ Token::make_keyword("SELECT"), @@ -2302,7 +2310,7 @@ mod tests { #[test] fn tokenize_dollar_quoted_string_tagged_unterminated() { let sql = String::from("SELECT $tag$dollar '$' quoted strings have $tags like this$ or like this $$$different tag$"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); assert_eq!( Tokenizer::new(&dialect, &sql).tokenize(), Err(TokenizerError { @@ -2319,7 +2327,7 @@ mod tests { fn tokenize_dollar_quoted_string_untagged() { let sql = String::from("SELECT $$within dollar '$' quoted strings have $tags like this$ $$"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ Token::make_keyword("SELECT"), @@ -2337,7 +2345,7 @@ mod tests { let sql = String::from( "SELECT $$dollar '$' quoted strings have $tags like this$ or like this $different tag$", ); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); assert_eq!( Tokenizer::new(&dialect, &sql).tokenize(), Err(TokenizerError { @@ -2353,7 +2361,7 @@ mod tests { #[test] fn tokenize_right_arrow() { let sql = String::from("FUNCTION(key=>value)"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ Token::make_word("FUNCTION", None), @@ -2369,7 +2377,7 @@ mod tests { #[test] fn tokenize_is_null() { let sql = String::from("a IS NULL"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ @@ -2387,7 +2395,7 @@ mod tests { fn tokenize_comment() { let sql = String::from("0--this is a comment\n1"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ Token::Number("0".to_string(), false), @@ -2404,7 +2412,7 @@ mod tests { fn tokenize_comment_at_eof() { let sql = String::from("--this is a comment"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![Token::Whitespace(Whitespace::SingleLineComment { prefix: "--".to_string(), @@ -2417,7 +2425,7 @@ mod tests { fn tokenize_multiline_comment() { let sql = String::from("0/*multi-line\n* /comment*/1"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ Token::Number("0".to_string(), false), @@ -2433,7 +2441,7 @@ mod tests { fn tokenize_nested_multiline_comment() { let sql = String::from("0/*multi-line\n* \n/* comment \n /*comment*/*/ */ /comment*/1"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ Token::Number("0".to_string(), false), @@ -2449,7 +2457,7 @@ mod tests { fn tokenize_multiline_comment_with_even_asterisks() { let sql = String::from("\n/** Comment **/\n"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ Token::Whitespace(Whitespace::Newline), @@ -2463,7 +2471,7 @@ mod tests { fn tokenize_unicode_whitespace() { let sql = String::from(" \u{2003}\n"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ Token::Whitespace(Whitespace::Space), @@ -2477,7 +2485,7 @@ mod tests { fn tokenize_mismatched_quotes() { let sql = String::from("\"foo"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let mut tokenizer = Tokenizer::new(&dialect, &sql); assert_eq!( tokenizer.tokenize(), @@ -2492,7 +2500,7 @@ mod tests { fn tokenize_newlines() { let sql = String::from("line1\nline2\rline3\r\nline4\r"); - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, &sql).tokenize().unwrap(); let expected = vec![ Token::make_word("line1", None), @@ -2510,7 +2518,7 @@ mod tests { #[test] fn tokenize_mssql_top() { let sql = "SELECT TOP 5 [bar] FROM foo"; - let dialect = MsSqlDialect {}; + let dialect = MsSqlDialect::default(); let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap(); let expected = vec![ Token::make_keyword("SELECT"), @@ -2531,7 +2539,7 @@ mod tests { #[test] fn tokenize_pg_regex_match() { let sql = "SELECT col ~ '^a', col ~* '^a', col !~ '^a', col !~* '^a'"; - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap(); let expected = vec![ Token::make_keyword("SELECT"), @@ -2569,7 +2577,7 @@ mod tests { #[test] fn tokenize_pg_like_match() { let sql = "SELECT col ~~ '_a%', col ~~* '_a%', col !~~ '_a%', col !~~* '_a%'"; - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap(); let expected = vec![ Token::make_keyword("SELECT"), @@ -2607,7 +2615,7 @@ mod tests { #[test] fn tokenize_quoted_identifier() { let sql = r#" "a "" b" "a """ "c """"" "#; - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap(); let expected = vec![ Token::Whitespace(Whitespace::Space), @@ -2624,7 +2632,7 @@ mod tests { #[test] fn tokenize_snowflake_div() { let sql = r#"field/1000"#; - let dialect = SnowflakeDialect {}; + let dialect = SnowflakeDialect::default(); let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap(); let expected = vec![ Token::make_word(r#"field"#, None), @@ -2637,7 +2645,7 @@ mod tests { #[test] fn tokenize_quoted_identifier_with_no_escape() { let sql = r#" "a "" b" "a """ "c """"" "#; - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, sql) .with_unescape(false) .tokenize() @@ -2657,7 +2665,7 @@ mod tests { #[test] fn tokenize_with_location() { let sql = "SELECT a,\n b"; - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, sql) .tokenize_with_location() .unwrap(); @@ -2754,9 +2762,22 @@ mod tests { #[test] fn tokenize_numeric_prefix_trait() { #[derive(Debug)] - struct NumericPrefixDialect; + struct NumericPrefixDialect(DialectFlags); + + impl Default for NumericPrefixDialect { + fn default() -> Self { + NumericPrefixDialect(DialectFlags { + supports_numeric_prefix: true, + ..Default::default() + }) + } + } impl Dialect for NumericPrefixDialect { + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_identifier_start(&self, ch: char) -> bool { ch.is_ascii_lowercase() || ch.is_ascii_uppercase() @@ -2773,15 +2794,11 @@ mod tests { || ch == '{' || ch == '}' } - - fn supports_numeric_prefix(&self) -> bool { - true - } } - tokenize_numeric_prefix_inner(&NumericPrefixDialect {}); - tokenize_numeric_prefix_inner(&HiveDialect {}); - tokenize_numeric_prefix_inner(&MySqlDialect {}); + tokenize_numeric_prefix_inner(&NumericPrefixDialect::default()); + tokenize_numeric_prefix_inner(&HiveDialect::default()); + tokenize_numeric_prefix_inner(&MySqlDialect::default()); } fn tokenize_numeric_prefix_inner(dialect: &dyn Dialect) { @@ -2801,7 +2818,7 @@ mod tests { #[test] fn tokenize_quoted_string_escape() { - let dialect = SnowflakeDialect {}; + let dialect = SnowflakeDialect::default(); for (sql, expected, expected_unescaped) in [ (r#"'%a\'%b'"#, r#"%a\'%b"#, r#"%a'%b"#), (r#"'a\'\'b\'c\'d'"#, r#"a\'\'b\'c\'d"#, r#"a''b'c'd"#), @@ -2841,7 +2858,7 @@ mod tests { // Non-escape dialect for (sql, expected) in [(r#"'\'"#, r#"\"#), (r#"'ab\'"#, r#"ab\"#)] { - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap(); let expected = vec![Token::SingleQuotedString(expected.to_string())]; @@ -2859,7 +2876,7 @@ mod tests { ) where F: Fn(String) -> Token, { - let dialect = BigQueryDialect {}; + let dialect = BigQueryDialect::default(); for (sql, expected, expected_unescaped) in [ // Empty string @@ -2925,7 +2942,7 @@ mod tests { format!(r#"{q}{q}{q}abc{q}{q}"#), format!(r#"{q}{q}{q}abc"#), ] { - let dialect = BigQueryDialect {}; + let dialect = BigQueryDialect::default(); let mut tokenizer = Tokenizer::new(&dialect, sql.as_str()); assert_eq!( "Unterminated string literal", @@ -2938,7 +2955,7 @@ mod tests { check('\'', '"', Token::TripleSingleQuotedString); - let dialect = BigQueryDialect {}; + let dialect = BigQueryDialect::default(); let sql = r#"""''"#; let tokens = Tokenizer::new(&dialect, sql) @@ -2963,7 +2980,7 @@ mod tests { compare(expected, tokens); // Non-triple quoted string dialect - let dialect = SnowflakeDialect {}; + let dialect = SnowflakeDialect::default(); let sql = r#"''''''"#; let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap(); let expected = vec![Token::SingleQuotedString("''".to_string())]; diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index e051baa8b..1f0e05f4b 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -36,7 +36,7 @@ fn parse_literal_string() { r#""""triple-double"unescaped""""#, ); let dialect = TestedDialects { - dialects: vec![Box::new(BigQueryDialect {})], + dialects: vec![Box::new(BigQueryDialect::default())], options: Some(ParserOptions::new().with_unescape(false)), }; let select = dialect.verified_only_select(sql); @@ -1932,14 +1932,17 @@ fn parse_big_query_declare() { fn bigquery() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(BigQueryDialect {})], + dialects: vec![Box::new(BigQueryDialect::default())], options: None, } } fn bigquery_and_generic() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(BigQueryDialect {}), Box::new(GenericDialect {})], + dialects: vec![ + Box::new(BigQueryDialect::default()), + Box::new(GenericDialect::default()), + ], options: None, } } diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index f89da7ee9..e0666e1d1 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -1609,14 +1609,17 @@ fn parse_explain_table() { fn clickhouse() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(ClickHouseDialect {})], + dialects: vec![Box::new(ClickHouseDialect::default())], options: None, } } fn clickhouse_and_generic() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(ClickHouseDialect {}), Box::new(GenericDialect {})], + dialects: vec![ + Box::new(ClickHouseDialect::default()), + Box::new(GenericDialect::default()), + ], options: None, } } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index ff45d7b6e..8207c1aba 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -112,7 +112,7 @@ fn parse_insert_values() { #[test] fn parse_replace_into() { - let dialect = PostgreSqlDialect {}; + let dialect = PostgreSqlDialect::default(); let sql = "REPLACE INTO public.customer (id, name, active) VALUES (1, 2, 3)"; assert_eq!( @@ -247,7 +247,7 @@ fn parse_returning_as_column_alias() { #[test] fn parse_insert_sqlite() { - let dialect = SQLiteDialect {}; + let dialect = SQLiteDialect::default(); let check = |sql: &str, expected_action: Option| match Parser::parse_sql( &dialect, sql, @@ -337,14 +337,14 @@ fn parse_update_set_from() { let sql = "UPDATE t1 SET name = t2.name FROM (SELECT name, id FROM t1 GROUP BY id) AS t2 WHERE t1.id = t2.id"; let dialects = TestedDialects { dialects: vec![ - Box::new(GenericDialect {}), - Box::new(DuckDbDialect {}), - Box::new(PostgreSqlDialect {}), - Box::new(BigQueryDialect {}), - Box::new(SnowflakeDialect {}), - Box::new(RedshiftSqlDialect {}), - Box::new(MsSqlDialect {}), - Box::new(SQLiteDialect {}), + Box::new(GenericDialect::default()), + Box::new(DuckDbDialect::default()), + Box::new(PostgreSqlDialect::default()), + Box::new(BigQueryDialect::default()), + Box::new(SnowflakeDialect::default()), + Box::new(RedshiftSqlDialect::default()), + Box::new(MsSqlDialect::default()), + Box::new(SQLiteDialect::default()), ], options: None, }; @@ -1046,7 +1046,7 @@ fn test_eof_after_as() { #[test] fn test_no_infix_error() { let dialects = TestedDialects { - dialects: vec![Box::new(ClickHouseDialect {})], + dialects: vec![Box::new(ClickHouseDialect::default())], options: None, }; @@ -1178,18 +1178,18 @@ fn parse_exponent_in_select() -> Result<(), ParserError> { // all except Hive, as it allows numbers to start an identifier let dialects = TestedDialects { dialects: vec![ - Box::new(AnsiDialect {}), - Box::new(BigQueryDialect {}), - Box::new(ClickHouseDialect {}), - Box::new(DuckDbDialect {}), - Box::new(GenericDialect {}), - // Box::new(HiveDialect {}), - Box::new(MsSqlDialect {}), - Box::new(MySqlDialect {}), - Box::new(PostgreSqlDialect {}), - Box::new(RedshiftSqlDialect {}), - Box::new(SnowflakeDialect {}), - Box::new(SQLiteDialect {}), + Box::new(AnsiDialect::default()), + Box::new(BigQueryDialect::default()), + Box::new(ClickHouseDialect::default()), + Box::new(DuckDbDialect::default()), + Box::new(GenericDialect::default()), + // Box::new(HiveDialect::default()), + Box::new(MsSqlDialect::default()), + Box::new(MySqlDialect::default()), + Box::new(PostgreSqlDialect::default()), + Box::new(RedshiftSqlDialect::default()), + Box::new(SnowflakeDialect::default()), + Box::new(SQLiteDialect::default()), ], options: None, }; @@ -1266,7 +1266,7 @@ fn parse_escaped_single_quote_string_predicate_with_no_escape() { WHERE salary <> 'Jim''s salary'"; let ast = TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: Some( ParserOptions::new() .with_trailing_commas(true) @@ -1395,7 +1395,10 @@ fn parse_mod() { fn pg_and_generic() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(PostgreSqlDialect {}), Box::new(GenericDialect {})], + dialects: vec![ + Box::new(PostgreSqlDialect::default()), + Box::new(GenericDialect::default()), + ], options: None, } } @@ -1407,7 +1410,7 @@ fn parse_json_ops_without_colon() { ( "->", Arrow, - all_dialects_except(|d| d.supports_lambda_functions()), + all_dialects_except(|d| d.flags().supports_lambda_functions), ), ("->>", LongArrow, all_dialects()), ("#>", HashArrow, pg_and_generic()), @@ -2474,7 +2477,7 @@ fn parse_extract() { verified_stmt("SELECT EXTRACT(TIMEZONE_REGION FROM d)"); verified_stmt("SELECT EXTRACT(TIME FROM d)"); - let dialects = all_dialects_except(|d| d.allow_extract_custom()); + let dialects = all_dialects_except(|d| d.flags().allow_extract_custom); let res = dialects.parse_sql_statements("SELECT EXTRACT(JIFFY FROM d)"); assert_eq!( ParserError::ParserError("Expected: date/time field, found: JIFFY".to_string()), @@ -2573,7 +2576,7 @@ fn parse_ceil_datetime() { verified_stmt("SELECT CEIL(d TO SECOND) FROM df"); verified_stmt("SELECT CEIL(d TO MILLISECOND) FROM df"); - let dialects = all_dialects_except(|d| d.allow_extract_custom()); + let dialects = all_dialects_except(|d| d.flags().allow_extract_custom); let res = dialects.parse_sql_statements("SELECT CEIL(d TO JIFFY) FROM df"); assert_eq!( ParserError::ParserError("Expected: date/time field, found: JIFFY".to_string()), @@ -2600,7 +2603,7 @@ fn parse_floor_datetime() { verified_stmt("SELECT FLOOR(d TO SECOND) FROM df"); verified_stmt("SELECT FLOOR(d TO MILLISECOND) FROM df"); - let dialects = all_dialects_except(|d| d.allow_extract_custom()); + let dialects = all_dialects_except(|d| d.flags().allow_extract_custom); let res = dialects.parse_sql_statements("SELECT FLOOR(d TO JIFFY) FROM df"); assert_eq!( ParserError::ParserError("Expected: date/time field, found: JIFFY".to_string()), @@ -2677,12 +2680,12 @@ fn parse_listagg() { fn parse_array_agg_func() { let supported_dialects = TestedDialects { dialects: vec![ - Box::new(GenericDialect {}), - Box::new(DuckDbDialect {}), - Box::new(PostgreSqlDialect {}), - Box::new(MsSqlDialect {}), - Box::new(AnsiDialect {}), - Box::new(HiveDialect {}), + Box::new(GenericDialect::default()), + Box::new(DuckDbDialect::default()), + Box::new(PostgreSqlDialect::default()), + Box::new(MsSqlDialect::default()), + Box::new(AnsiDialect::default()), + Box::new(HiveDialect::default()), ], options: None, }; @@ -2702,11 +2705,11 @@ fn parse_array_agg_func() { fn parse_agg_with_order_by() { let supported_dialects = TestedDialects { dialects: vec![ - Box::new(GenericDialect {}), - Box::new(PostgreSqlDialect {}), - Box::new(MsSqlDialect {}), - Box::new(AnsiDialect {}), - Box::new(HiveDialect {}), + Box::new(GenericDialect::default()), + Box::new(PostgreSqlDialect::default()), + Box::new(MsSqlDialect::default()), + Box::new(AnsiDialect::default()), + Box::new(HiveDialect::default()), ], options: None, }; @@ -2725,12 +2728,12 @@ fn parse_agg_with_order_by() { fn parse_window_rank_function() { let supported_dialects = TestedDialects { dialects: vec![ - Box::new(GenericDialect {}), - Box::new(PostgreSqlDialect {}), - Box::new(MsSqlDialect {}), - Box::new(AnsiDialect {}), - Box::new(HiveDialect {}), - Box::new(SnowflakeDialect {}), + Box::new(GenericDialect::default()), + Box::new(PostgreSqlDialect::default()), + Box::new(MsSqlDialect::default()), + Box::new(AnsiDialect::default()), + Box::new(HiveDialect::default()), + Box::new(SnowflakeDialect::default()), ], options: None, }; @@ -2746,7 +2749,10 @@ fn parse_window_rank_function() { } let supported_dialects_nulls = TestedDialects { - dialects: vec![Box::new(MsSqlDialect {}), Box::new(SnowflakeDialect {})], + dialects: vec![ + Box::new(MsSqlDialect::default()), + Box::new(SnowflakeDialect::default()), + ], options: None, }; @@ -2762,7 +2768,7 @@ fn parse_window_rank_function() { #[test] fn parse_window_function_null_treatment_arg() { - let dialects = all_dialects_where(|d| d.supports_window_function_null_treatment_arg()); + let dialects = all_dialects_where(|d| d.flags().supports_window_function_null_treatment_arg); let sql = "SELECT \ FIRST_VALUE(a IGNORE NULLS) OVER (), \ FIRST_VALUE(b RESPECT NULLS) OVER () \ @@ -2812,7 +2818,7 @@ fn parse_window_function_null_treatment_arg() { let sql = "SELECT LAG(1 IGNORE NULLS) IGNORE NULLS OVER () FROM t1"; assert_eq!( - all_dialects_where(|d| !d.supports_window_function_null_treatment_arg()) + all_dialects_except(|d| d.flags().supports_window_function_null_treatment_arg) .parse_sql_statements(sql) .unwrap_err(), ParserError::ParserError("Expected: ), found: IGNORE".to_string()) @@ -3282,13 +3288,13 @@ fn parse_create_table_hive_array() { // Parsing [] type arrays does not work in MsSql since [ is used in is_delimited_identifier_start for (dialects, angle_bracket_syntax) in [ ( - vec![Box::new(PostgreSqlDialect {}) as Box], + vec![Box::new(PostgreSqlDialect::default()) as Box], false, ), ( vec![ - Box::new(HiveDialect {}) as Box, - Box::new(BigQueryDialect {}) as Box, + Box::new(HiveDialect::default()) as Box, + Box::new(BigQueryDialect::default()) as Box, ], true, ), @@ -3348,9 +3354,9 @@ fn parse_create_table_hive_array() { // SnowflakeDialect using array different let dialects = TestedDialects { dialects: vec![ - Box::new(PostgreSqlDialect {}), - Box::new(HiveDialect {}), - Box::new(MySqlDialect {}), + Box::new(PostgreSqlDialect::default()), + Box::new(HiveDialect::default()), + Box::new(MySqlDialect::default()), ], options: None, }; @@ -3559,7 +3565,7 @@ fn parse_create_table_as_table() { #[test] fn parse_create_table_on_cluster() { let generic = TestedDialects { - dialects: vec![Box::new(GenericDialect {})], + dialects: vec![Box::new(GenericDialect::default())], options: None, }; @@ -3628,7 +3634,7 @@ fn parse_create_table_with_on_delete_on_update_2in_any_order() -> Result<(), Par #[test] fn parse_create_table_with_options() { let generic = TestedDialects { - dialects: vec![Box::new(GenericDialect {})], + dialects: vec![Box::new(GenericDialect::default())], options: None, }; @@ -3668,7 +3674,7 @@ fn parse_create_table_clone() { #[test] fn parse_create_table_trailing_comma() { let dialect = TestedDialects { - dialects: vec![Box::new(DuckDbDialect {})], + dialects: vec![Box::new(DuckDbDialect::default())], options: None, }; @@ -4014,10 +4020,10 @@ fn parse_alter_table_add_column() { fn parse_alter_table_add_column_if_not_exists() { let dialects = TestedDialects { dialects: vec![ - Box::new(PostgreSqlDialect {}), - Box::new(BigQueryDialect {}), - Box::new(GenericDialect {}), - Box::new(DuckDbDialect {}), + Box::new(PostgreSqlDialect::default()), + Box::new(BigQueryDialect::default()), + Box::new(GenericDialect::default()), + Box::new(DuckDbDialect::default()), ], options: None, }; @@ -4164,7 +4170,7 @@ fn parse_alter_table_alter_column_type() { } let dialect = TestedDialects { - dialects: vec![Box::new(GenericDialect {})], + dialects: vec![Box::new(GenericDialect::default())], options: None, }; @@ -4402,7 +4408,7 @@ fn parse_named_argument_function() { fn parse_named_argument_function_with_eq_operator() { let sql = "SELECT FUN(a = '1', b = '2') FROM foo"; - let select = all_dialects_where(|d| d.supports_named_fn_args_with_eq_operator()) + let select = all_dialects_where(|d| d.flags().supports_named_fn_args_with_eq_operator) .verified_only_select(sql); assert_eq!( &Expr::Function(Function { @@ -4439,7 +4445,7 @@ fn parse_named_argument_function_with_eq_operator() { // Ensure that bar = 42 in a function argument parses as an equality binop // rather than a named function argument. assert_eq!( - all_dialects_except(|d| d.supports_named_fn_args_with_eq_operator()) + all_dialects_except(|d| d.flags().supports_named_fn_args_with_eq_operator) .verified_expr("foo(bar = 42)"), call( "foo", @@ -4452,7 +4458,7 @@ fn parse_named_argument_function_with_eq_operator() { ); // TODO: should this parse for all dialects? - all_dialects_except(|d| d.supports_named_fn_args_with_eq_operator()) + all_dialects_except(|d| d.flags().supports_named_fn_args_with_eq_operator) .verified_expr("iff(1 = 1, 1, 0)"); } @@ -4472,7 +4478,7 @@ fn parse_window_functions() { sum(qux) OVER (ORDER BY a \ GROUPS BETWEEN 1 PRECEDING AND 1 FOLLOWING) \ FROM foo"; - let dialects = all_dialects_except(|d| d.require_interval_qualifier()); + let dialects = all_dialects_except(|d| d.flags().require_interval_qualifier); let select = dialects.verified_only_select(sql); const EXPECTED_PROJ_QTY: usize = 7; @@ -4523,10 +4529,10 @@ fn parse_window_functions() { fn parse_named_window_functions() { let supported_dialects = TestedDialects { dialects: vec![ - Box::new(GenericDialect {}), - Box::new(PostgreSqlDialect {}), - Box::new(MySqlDialect {}), - Box::new(BigQueryDialect {}), + Box::new(GenericDialect::default()), + Box::new(PostgreSqlDialect::default()), + Box::new(MySqlDialect::default()), + Box::new(BigQueryDialect::default()), ], options: None, }; @@ -4760,7 +4766,7 @@ fn parse_window_and_qualify_clause() { fn parse_window_clause_named_window() { let sql = "SELECT * FROM mytable WINDOW window1 AS window2"; let Select { named_window, .. } = - all_dialects_where(|d| d.supports_window_clause_named_window_reference()) + all_dialects_where(|d| d.flags().supports_window_clause_named_window_reference) .verified_only_select(sql); assert_eq!( vec![NamedWindowDefinition( @@ -5032,7 +5038,7 @@ fn parse_interval_all() { #[test] fn parse_interval_dont_require_unit() { - let dialects = all_dialects_except(|d| d.require_interval_qualifier()); + let dialects = all_dialects_except(|d| d.flags().require_interval_qualifier); let sql = "SELECT INTERVAL '1 DAY'"; let select = dialects.verified_only_select(sql); @@ -5058,7 +5064,7 @@ fn parse_interval_dont_require_unit() { #[test] fn parse_interval_require_unit() { - let dialects = all_dialects_where(|d| d.require_interval_qualifier()); + let dialects = all_dialects_where(|d| d.flags().require_interval_qualifier); let sql = "SELECT INTERVAL '1 DAY'"; let err = dialects.parse_sql_statements(sql).unwrap_err(); @@ -5070,7 +5076,7 @@ fn parse_interval_require_unit() { #[test] fn parse_interval_require_qualifier() { - let dialects = all_dialects_where(|d| d.require_interval_qualifier()); + let dialects = all_dialects_where(|d| d.flags().require_interval_qualifier); let sql = "SELECT INTERVAL 1 + 1 DAY"; let select = dialects.verified_only_select(sql); @@ -5130,7 +5136,7 @@ fn parse_interval_require_qualifier() { #[test] fn parse_interval_disallow_interval_expr() { - let dialects = all_dialects_except(|d| d.require_interval_qualifier()); + let dialects = all_dialects_except(|d| d.flags().require_interval_qualifier); let sql = "SELECT INTERVAL '1 DAY'"; let select = dialects.verified_only_select(sql); @@ -5184,7 +5190,7 @@ fn parse_interval_disallow_interval_expr() { #[test] fn interval_disallow_interval_expr_gt() { - let dialects = all_dialects_except(|d| d.require_interval_qualifier()); + let dialects = all_dialects_except(|d| d.flags().require_interval_qualifier); let expr = dialects.verified_expr("INTERVAL '1 second' > x"); assert_eq!( expr, @@ -5209,7 +5215,7 @@ fn interval_disallow_interval_expr_gt() { #[test] fn interval_disallow_interval_expr_double_colon() { - let dialects = all_dialects_except(|d| d.require_interval_qualifier()); + let dialects = all_dialects_except(|d| d.flags().require_interval_qualifier); let expr = dialects.verified_expr("INTERVAL '1 second'::TEXT"); assert_eq!( expr, @@ -5236,7 +5242,7 @@ fn parse_interval_and_or_xor() { WHERE d3_date > d1_date + INTERVAL '5 days' \ AND d2_date > d1_date + INTERVAL '3 days'"; - let dialects = all_dialects_except(|d| d.require_interval_qualifier()); + let dialects = all_dialects_except(|d| d.flags().require_interval_qualifier); let actual_ast = dialects.parse_sql_statements(sql).unwrap(); let expected_ast = vec![Statement::Query(Box::new(Query { @@ -5595,7 +5601,10 @@ fn parse_unnest_in_from_clause() { assert_eq!(select.from, want); } let dialects = TestedDialects { - dialects: vec![Box::new(BigQueryDialect {}), Box::new(GenericDialect {})], + dialects: vec![ + Box::new(BigQueryDialect::default()), + Box::new(GenericDialect::default()), + ], options: None, }; // 1. both Alias and WITH OFFSET clauses. @@ -6582,17 +6591,17 @@ fn parse_trim() { //keep Snowflake/BigQuery TRIM syntax failing let all_expected_snowflake = TestedDialects { dialects: vec![ - //Box::new(GenericDialect {}), - Box::new(PostgreSqlDialect {}), - Box::new(MsSqlDialect {}), - Box::new(AnsiDialect {}), - //Box::new(SnowflakeDialect {}), - Box::new(HiveDialect {}), - Box::new(RedshiftSqlDialect {}), - Box::new(MySqlDialect {}), - //Box::new(BigQueryDialect {}), - Box::new(SQLiteDialect {}), - Box::new(DuckDbDialect {}), + //Box::new(GenericDialect::default()), + Box::new(PostgreSqlDialect::default()), + Box::new(MsSqlDialect::default()), + Box::new(AnsiDialect::default()), + //Box::new(SnowflakeDialect::default()), + Box::new(HiveDialect::default()), + Box::new(RedshiftSqlDialect::default()), + Box::new(MySqlDialect::default()), + //Box::new(BigQueryDialect::default()), + Box::new(SQLiteDialect::default()), + Box::new(DuckDbDialect::default()), ], options: None, }; @@ -7459,7 +7468,8 @@ fn parse_set_variable() { _ => unreachable!(), } - let multi_variable_dialects = all_dialects_where(|d| d.supports_parenthesized_set_variables()); + let multi_variable_dialects = + all_dialects_where(|d| d.flags().supports_parenthesized_set_variables); let sql = r#"SET (a, b, c) = (1, 2, 3)"#; match multi_variable_dialects.verified_stmt(sql) { Statement::SetVariable { @@ -7681,7 +7691,7 @@ fn parse_rollback() { } #[test] -#[should_panic(expected = "Parse results with GenericDialect are different from PostgreSqlDialect")] +#[should_panic(expected = r"Parse results with GenericDialect(DialectFlags")] fn ensure_multiple_dialects_are_tested() { // The SQL here must be parsed differently by different dialects. // At the time of writing, `@foo` is accepted as a valid identifier @@ -7787,7 +7797,7 @@ fn test_create_index_with_with_clause() { }, Expr::Identifier(Ident::new("single_param")), ]; - let dialects = all_dialects_where(|d| d.supports_create_index_with_clause()); + let dialects = all_dialects_where(|d| d.flags().supports_create_index_with_clause); match dialects.verified_stmt(sql) { Statement::CreateIndex(CreateIndex { name: Some(name), @@ -8431,15 +8441,15 @@ fn test_lock_nonblock() { fn test_placeholder() { let dialects = TestedDialects { dialects: vec![ - Box::new(GenericDialect {}), - Box::new(DuckDbDialect {}), - Box::new(PostgreSqlDialect {}), - Box::new(MsSqlDialect {}), - Box::new(AnsiDialect {}), - Box::new(BigQueryDialect {}), - Box::new(SnowflakeDialect {}), + Box::new(GenericDialect::default()), + Box::new(DuckDbDialect::default()), + Box::new(PostgreSqlDialect::default()), + Box::new(MsSqlDialect::default()), + Box::new(AnsiDialect::default()), + Box::new(BigQueryDialect::default()), + Box::new(SnowflakeDialect::default()), // Note: `$` is the starting word for the HiveDialect identifier - // Box::new(sqlparser::dialect::HiveDialect {}), + // Box::new(sqlparser::dialect::HiveDialect::default()), ], options: None, }; @@ -8470,16 +8480,16 @@ fn test_placeholder() { let dialects = TestedDialects { dialects: vec![ - Box::new(GenericDialect {}), - Box::new(DuckDbDialect {}), + Box::new(GenericDialect::default()), + Box::new(DuckDbDialect::default()), // Note: `?` is for jsonb operators in PostgreSqlDialect - // Box::new(PostgreSqlDialect {}), - Box::new(MsSqlDialect {}), - Box::new(AnsiDialect {}), - Box::new(BigQueryDialect {}), - Box::new(SnowflakeDialect {}), + // Box::new(PostgreSqlDialect::default()), + Box::new(MsSqlDialect::default()), + Box::new(AnsiDialect::default()), + Box::new(BigQueryDialect::default()), + Box::new(SnowflakeDialect::default()), // Note: `$` is the starting word for the HiveDialect identifier - // Box::new(sqlparser::dialect::HiveDialect {}), + // Box::new(sqlparser::dialect::HiveDialect::default()), ], options: None, }; @@ -9016,7 +9026,7 @@ fn parse_deeply_nested_unary_op_hits_recursion_limits() { #[test] fn parse_deeply_nested_expr_hits_recursion_limits() { - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let where_clause = make_where_clause(100); let sql = format!("SELECT id, user_id FROM test WHERE {where_clause}"); @@ -9031,7 +9041,7 @@ fn parse_deeply_nested_expr_hits_recursion_limits() { #[test] fn parse_deeply_nested_subquery_expr_hits_recursion_limits() { - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let where_clause = make_where_clause(100); let sql = format!("SELECT id, user_id where id IN (select id from t WHERE {where_clause})"); @@ -9046,7 +9056,7 @@ fn parse_deeply_nested_subquery_expr_hits_recursion_limits() { #[test] fn parse_with_recursion_limit() { - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let where_clause = make_where_clause(20); let sql = format!("SELECT id, user_id FROM test WHERE {where_clause}"); @@ -9083,9 +9093,9 @@ fn parse_escaped_string_with_unescape() { fn assert_mysql_query_value(sql: &str, quoted: &str) { let stmt = TestedDialects { dialects: vec![ - Box::new(MySqlDialect {}), - Box::new(BigQueryDialect {}), - Box::new(SnowflakeDialect {}), + Box::new(MySqlDialect::default()), + Box::new(BigQueryDialect::default()), + Box::new(SnowflakeDialect::default()), ], options: None, } @@ -9123,9 +9133,9 @@ fn parse_escaped_string_without_unescape() { fn assert_mysql_query_value(sql: &str, quoted: &str) { let stmt = TestedDialects { dialects: vec![ - Box::new(MySqlDialect {}), - Box::new(BigQueryDialect {}), - Box::new(SnowflakeDialect {}), + Box::new(MySqlDialect::default()), + Box::new(BigQueryDialect::default()), + Box::new(SnowflakeDialect::default()), ], options: Some(ParserOptions::new().with_unescape(false)), } @@ -9398,12 +9408,12 @@ fn make_where_clause(num: usize) -> String { fn parse_non_latin_identifiers() { let supported_dialects = TestedDialects { dialects: vec![ - Box::new(GenericDialect {}), - Box::new(DuckDbDialect {}), - Box::new(PostgreSqlDialect {}), - Box::new(MsSqlDialect {}), - Box::new(RedshiftSqlDialect {}), - Box::new(MySqlDialect {}), + Box::new(GenericDialect::default()), + Box::new(DuckDbDialect::default()), + Box::new(PostgreSqlDialect::default()), + Box::new(MsSqlDialect::default()), + Box::new(RedshiftSqlDialect::default()), + Box::new(MySqlDialect::default()), ], options: None, }; @@ -9421,7 +9431,7 @@ fn parse_trailing_comma() { // At the moment, DuckDB is the only dialect that allows // trailing commas anywhere in the query let trailing_commas = TestedDialects { - dialects: vec![Box::new(DuckDbDialect {})], + dialects: vec![Box::new(DuckDbDialect::default())], options: None, }; @@ -9463,7 +9473,7 @@ fn parse_trailing_comma() { // doesn't allow any trailing commas let trailing_commas = TestedDialects { - dialects: vec![Box::new(GenericDialect {})], + dialects: vec![Box::new(GenericDialect::default())], options: None, }; @@ -9495,7 +9505,10 @@ fn parse_trailing_comma() { fn parse_projection_trailing_comma() { // Some dialects allow trailing commas only in the projection let trailing_commas = TestedDialects { - dialects: vec![Box::new(SnowflakeDialect {}), Box::new(BigQueryDialect {})], + dialects: vec![ + Box::new(SnowflakeDialect::default()), + Box::new(BigQueryDialect::default()), + ], options: None, }; @@ -9731,7 +9744,10 @@ fn test_release_savepoint() { #[test] fn test_comment_hash_syntax() { let dialects = TestedDialects { - dialects: vec![Box::new(BigQueryDialect {}), Box::new(SnowflakeDialect {})], + dialects: vec![ + Box::new(BigQueryDialect::default()), + Box::new(SnowflakeDialect::default()), + ], options: None, }; let sql = r#" @@ -9748,7 +9764,7 @@ fn test_comment_hash_syntax() { #[test] fn test_buffer_reuse() { - let d = GenericDialect {}; + let d = GenericDialect::default(); let q = "INSERT INTO customer WITH foo AS (SELECT 1) SELECT * FROM foo UNION VALUES (1)"; let mut buf = Vec::new(); Tokenizer::new(&d, q) @@ -9763,7 +9779,10 @@ fn test_buffer_reuse() { fn parse_map_access_expr() { let sql = "users[-1][safe_offset(2)]"; let dialects = TestedDialects { - dialects: vec![Box::new(BigQueryDialect {}), Box::new(ClickHouseDialect {})], + dialects: vec![ + Box::new(BigQueryDialect::default()), + Box::new(ClickHouseDialect::default()), + ], options: None, }; let expr = dialects.verified_expr(sql); @@ -9851,7 +9870,7 @@ fn parse_connect_by() { ); assert_eq!( - all_dialects_where(|d| d.supports_connect_by()).verified_only_select(connect_by_1), + all_dialects_where(|d| d.flags().supports_connect_by).verified_only_select(connect_by_1), expect_query ); @@ -9863,7 +9882,7 @@ fn parse_connect_by() { "ORDER BY employee_id" ); assert_eq!( - all_dialects_where(|d| d.supports_connect_by()) + all_dialects_where(|d| d.flags().supports_connect_by) .verified_only_select_with_canonical(connect_by_2, connect_by_1), expect_query ); @@ -9877,7 +9896,7 @@ fn parse_connect_by() { "ORDER BY employee_id" ); assert_eq!( - all_dialects_where(|d| d.supports_connect_by()).verified_only_select(connect_by_3), + all_dialects_where(|d| d.flags().supports_connect_by).verified_only_select(connect_by_3), Select { distinct: None, top: None, @@ -9941,7 +9960,7 @@ fn parse_connect_by() { "WHERE employee_id <> 42 ", "ORDER BY employee_id" ); - all_dialects_where(|d| d.supports_connect_by()) + all_dialects_where(|d| d.flags().supports_connect_by) .parse_sql_statements(connect_by_4) .expect_err("should have failed"); @@ -9966,7 +9985,7 @@ fn test_selective_aggregation() { "FROM region" ); assert_eq!( - all_dialects_where(|d| d.supports_filter_during_aggregation()) + all_dialects_where(|d| d.flags().supports_filter_during_aggregation) .verified_only_select(sql) .projection, vec![ @@ -10023,7 +10042,7 @@ fn test_group_by_grouping_sets() { "ORDER BY city", ); assert_eq!( - all_dialects_where(|d| d.supports_group_by_expr()) + all_dialects_where(|d| d.flags().supports_group_by_expr) .verified_only_select(sql) .group_by, GroupByExpr::Expressions( @@ -10058,9 +10077,10 @@ fn test_match_recognize() { }; fn check(options: &str, expect: TableFactor) { - let select = all_dialects_where(|d| d.supports_match_recognize()).verified_only_select( - &format!("SELECT * FROM my_table MATCH_RECOGNIZE({options})"), - ); + let select = all_dialects_where(|d| d.flags().supports_match_recognize) + .verified_only_select(&format!( + "SELECT * FROM my_table MATCH_RECOGNIZE({options})" + )); assert_eq!(&select.from[0].relation, &expect); } @@ -10187,7 +10207,7 @@ fn test_match_recognize() { ]; for sql in examples { - all_dialects_where(|d| d.supports_match_recognize()).verified_query(sql); + all_dialects_where(|d| d.flags().supports_match_recognize).verified_query(sql); } } @@ -10198,8 +10218,8 @@ fn test_match_recognize_patterns() { use RepetitionQuantifier::*; fn check(pattern: &str, expect: MatchRecognizePattern) { - let select = - all_dialects_where(|d| d.supports_match_recognize()).verified_only_select(&format!( + let select = all_dialects_where(|d| d.flags().supports_match_recognize) + .verified_only_select(&format!( "SELECT * FROM my_table MATCH_RECOGNIZE(PATTERN ({pattern}) DEFINE DUMMY AS true)" // "select * from my_table match_recognize (" )); let TableFactor::MatchRecognize { @@ -10341,11 +10361,11 @@ fn test_select_wildcard_with_replace() { let sql = r#"SELECT * REPLACE (lower(city) AS city) FROM addresses"#; let dialects = TestedDialects { dialects: vec![ - Box::new(GenericDialect {}), - Box::new(BigQueryDialect {}), - Box::new(ClickHouseDialect {}), - Box::new(SnowflakeDialect {}), - Box::new(DuckDbDialect {}), + Box::new(GenericDialect::default()), + Box::new(BigQueryDialect::default()), + Box::new(ClickHouseDialect::default()), + Box::new(SnowflakeDialect::default()), + Box::new(DuckDbDialect::default()), ], options: None, }; @@ -10407,9 +10427,9 @@ fn test_select_wildcard_with_replace() { fn parse_sized_list() { let dialects = TestedDialects { dialects: vec![ - Box::new(GenericDialect {}), - Box::new(PostgreSqlDialect {}), - Box::new(DuckDbDialect {}), + Box::new(GenericDialect::default()), + Box::new(PostgreSqlDialect::default()), + Box::new(DuckDbDialect::default()), ], options: None, }; @@ -10425,9 +10445,9 @@ fn parse_sized_list() { fn insert_into_with_parentheses() { let dialects = TestedDialects { dialects: vec![ - Box::new(SnowflakeDialect {}), - Box::new(RedshiftSqlDialect {}), - Box::new(GenericDialect {}), + Box::new(SnowflakeDialect::default()), + Box::new(RedshiftSqlDialect::default()), + Box::new(GenericDialect::default()), ], options: None, }; @@ -10438,7 +10458,7 @@ fn insert_into_with_parentheses() { fn test_dictionary_syntax() { fn check(sql: &str, expect: Expr) { assert_eq!( - all_dialects_where(|d| d.supports_dictionary_syntax()).verified_expr(sql), + all_dialects_where(|d| d.flags().supports_dictionary_syntax).verified_expr(sql), expect ); } @@ -10494,7 +10514,7 @@ fn test_dictionary_syntax() { fn test_map_syntax() { fn check(sql: &str, expect: Expr) { assert_eq!( - all_dialects_where(|d| d.support_map_literal_syntax()).verified_expr(sql), + all_dialects_where(|d| d.flags().support_map_literal_syntax).verified_expr(sql), expect ); } @@ -10600,9 +10620,9 @@ fn parse_within_group() { fn tests_select_values_without_parens() { let dialects = TestedDialects { dialects: vec![ - Box::new(GenericDialect {}), - Box::new(SnowflakeDialect {}), - Box::new(DatabricksDialect {}), + Box::new(GenericDialect::default()), + Box::new(SnowflakeDialect::default()), + Box::new(DatabricksDialect::default()), ], options: None, }; @@ -10615,9 +10635,9 @@ fn tests_select_values_without_parens() { fn tests_select_values_without_parens_and_set_op() { let dialects = TestedDialects { dialects: vec![ - Box::new(GenericDialect {}), - Box::new(SnowflakeDialect {}), - Box::new(DatabricksDialect {}), + Box::new(GenericDialect::default()), + Box::new(SnowflakeDialect::default()), + Box::new(DatabricksDialect::default()), ], options: None, }; @@ -10647,7 +10667,7 @@ fn tests_select_values_without_parens_and_set_op() { #[test] fn parse_select_wildcard_with_except() { - let dialects = all_dialects_where(|d| d.supports_select_wildcard_except()); + let dialects = all_dialects_where(|d| d.flags().supports_select_wildcard_except); let select = dialects.verified_only_select("SELECT * EXCEPT (col_a) FROM data"); let expected = SelectItem::Wildcard(WildcardAdditionalOptions { @@ -10681,7 +10701,7 @@ fn parse_select_wildcard_with_except() { #[test] fn parse_auto_increment_too_large() { - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let u64_max = u64::MAX; let sql = format!("CREATE TABLE foo (bar INT NOT NULL AUTO_INCREMENT) AUTO_INCREMENT=1{u64_max}"); @@ -10696,7 +10716,7 @@ fn parse_auto_increment_too_large() { #[test] fn test_group_by_nothing() { - let Select { group_by, .. } = all_dialects_where(|d| d.supports_group_by_expr()) + let Select { group_by, .. } = all_dialects_where(|d| d.flags().supports_group_by_expr) .verified_only_select("SELECT count(1) FROM t GROUP BY ()"); { std::assert_eq!( @@ -10705,7 +10725,7 @@ fn test_group_by_nothing() { ); } - let Select { group_by, .. } = all_dialects_where(|d| d.supports_group_by_expr()) + let Select { group_by, .. } = all_dialects_where(|d| d.flags().supports_group_by_expr) .verified_only_select("SELECT name, count(1) FROM t GROUP BY name, ()"); { std::assert_eq!( @@ -10723,7 +10743,7 @@ fn test_group_by_nothing() { #[test] fn test_extract_seconds_ok() { - let dialects = all_dialects_where(|d| d.allow_extract_custom()); + let dialects = all_dialects_where(|d| d.flags().allow_extract_custom); let stmt = dialects.verified_expr("EXTRACT(seconds FROM '2 seconds'::INTERVAL)"); assert_eq!( @@ -10748,7 +10768,7 @@ fn test_extract_seconds_ok() { #[test] fn test_extract_seconds_single_quote_ok() { - let dialects = all_dialects_where(|d| d.allow_extract_custom()); + let dialects = all_dialects_where(|d| d.flags().allow_extract_custom); let stmt = dialects.verified_expr(r#"EXTRACT('seconds' FROM '2 seconds'::INTERVAL)"#); assert_eq!( @@ -10774,7 +10794,7 @@ fn test_extract_seconds_single_quote_ok() { #[test] fn test_extract_seconds_err() { let sql = "SELECT EXTRACT(seconds FROM '2 seconds'::INTERVAL)"; - let dialects = all_dialects_except(|d| d.allow_extract_custom()); + let dialects = all_dialects_except(|d| d.flags().allow_extract_custom); let err = dialects.parse_sql_statements(sql).unwrap_err(); assert_eq!( err.to_string(), @@ -10785,7 +10805,7 @@ fn test_extract_seconds_err() { #[test] fn test_extract_seconds_single_quote_err() { let sql = r#"SELECT EXTRACT('seconds' FROM '2 seconds'::INTERVAL)"#; - let dialects = all_dialects_except(|d| d.allow_extract_single_quotes()); + let dialects = all_dialects_except(|d| d.flags().allow_extract_single_quotes); let err = dialects.parse_sql_statements(sql).unwrap_err(); assert_eq!( err.to_string(), diff --git a/tests/sqlparser_custom_dialect.rs b/tests/sqlparser_custom_dialect.rs index 5b29047a4..a938721c3 100644 --- a/tests/sqlparser_custom_dialect.rs +++ b/tests/sqlparser_custom_dialect.rs @@ -12,6 +12,7 @@ //! Test the ability for dialects to override parsing +use sqlparser::dialect::DialectFlags; use sqlparser::{ ast::{BinaryOperator, Expr, Statement, Value}, dialect::Dialect, @@ -22,10 +23,14 @@ use sqlparser::{ #[test] fn custom_prefix_parser() -> Result<(), ParserError> { - #[derive(Debug)] - struct MyDialect {} + #[derive(Debug, Default)] + struct MyDialect(DialectFlags); impl Dialect for MyDialect { + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_identifier_start(&self, ch: char) -> bool { is_identifier_start(ch) } @@ -43,7 +48,7 @@ fn custom_prefix_parser() -> Result<(), ParserError> { } } - let dialect = MyDialect {}; + let dialect = MyDialect::default(); let sql = "SELECT 1 + 2"; let ast = Parser::parse_sql(&dialect, sql)?; let query = &ast[0]; @@ -53,10 +58,14 @@ fn custom_prefix_parser() -> Result<(), ParserError> { #[test] fn custom_infix_parser() -> Result<(), ParserError> { - #[derive(Debug)] - struct MyDialect {} + #[derive(Debug, Default)] + struct MyDialect(DialectFlags); impl Dialect for MyDialect { + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_identifier_start(&self, ch: char) -> bool { is_identifier_start(ch) } @@ -83,7 +92,7 @@ fn custom_infix_parser() -> Result<(), ParserError> { } } - let dialect = MyDialect {}; + let dialect = MyDialect::default(); let sql = "SELECT 1 + 2"; let ast = Parser::parse_sql(&dialect, sql)?; let query = &ast[0]; @@ -93,10 +102,14 @@ fn custom_infix_parser() -> Result<(), ParserError> { #[test] fn custom_statement_parser() -> Result<(), ParserError> { - #[derive(Debug)] - struct MyDialect {} + #[derive(Debug, Default)] + struct MyDialect(DialectFlags); impl Dialect for MyDialect { + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_identifier_start(&self, ch: char) -> bool { is_identifier_start(ch) } @@ -117,7 +130,7 @@ fn custom_statement_parser() -> Result<(), ParserError> { } } - let dialect = MyDialect {}; + let dialect = MyDialect::default(); let sql = "SELECT 1 + 2"; let ast = Parser::parse_sql(&dialect, sql)?; let query = &ast[0]; @@ -127,10 +140,14 @@ fn custom_statement_parser() -> Result<(), ParserError> { #[test] fn test_map_syntax_not_support_default() -> Result<(), ParserError> { - #[derive(Debug)] - struct MyDialect {} + #[derive(Debug, Default)] + struct MyDialect(DialectFlags); impl Dialect for MyDialect { + fn flags(&self) -> &DialectFlags { + &self.0 + } + fn is_identifier_start(&self, ch: char) -> bool { is_identifier_start(ch) } @@ -140,7 +157,7 @@ fn test_map_syntax_not_support_default() -> Result<(), ParserError> { } } - let dialect = MyDialect {}; + let dialect = MyDialect::default(); let sql = "SELECT MAP {1: 2}"; let ast = Parser::parse_sql(&dialect, sql); assert!(ast.is_err()); diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs index ee0cf2d7d..5860b46dc 100644 --- a/tests/sqlparser_databricks.rs +++ b/tests/sqlparser_databricks.rs @@ -8,14 +8,17 @@ mod test_utils; fn databricks() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(DatabricksDialect {})], + dialects: vec![Box::new(DatabricksDialect::default())], options: None, } } fn databricks_and_generic() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(DatabricksDialect {}), Box::new(GenericDialect {})], + dialects: vec![ + Box::new(DatabricksDialect::default()), + Box::new(GenericDialect::default()), + ], options: None, } } diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index 12368a88c..dcb60accf 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -20,14 +20,17 @@ use sqlparser::dialect::{DuckDbDialect, GenericDialect}; fn duckdb() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(DuckDbDialect {})], + dialects: vec![Box::new(DuckDbDialect::default())], options: None, } } fn duckdb_and_generic() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(DuckDbDialect {}), Box::new(GenericDialect {})], + dialects: vec![ + Box::new(DuckDbDialect::default()), + Box::new(GenericDialect::default()), + ], options: None, } } diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index 5426dfbc4..129c668a0 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs @@ -414,7 +414,7 @@ fn parse_create_function() { // Test error in dialect that doesn't support parsing CREATE FUNCTION let unsupported_dialects = TestedDialects { - dialects: vec![Box::new(MsSqlDialect {})], + dialects: vec![Box::new(MsSqlDialect::default())], options: None, }; @@ -534,14 +534,17 @@ fn parse_use() { fn hive() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(HiveDialect {})], + dialects: vec![Box::new(HiveDialect::default())], options: None, } } fn hive_and_generic() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(HiveDialect {}), Box::new(GenericDialect {})], + dialects: vec![ + Box::new(HiveDialect::default()), + Box::new(GenericDialect::default()), + ], options: None, } } diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 0ab160f56..a293fbe2d 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -566,7 +566,7 @@ fn parse_substring_in_select() { #[test] fn parse_mssql_declare() { let sql = "DECLARE @foo CURSOR, @bar INT, @baz AS TEXT = 'foobar';"; - let ast = Parser::parse_sql(&MsSqlDialect {}, sql).unwrap(); + let ast = Parser::parse_sql(&MsSqlDialect::default(), sql).unwrap(); assert_eq!( vec![Statement::Declare { @@ -910,13 +910,16 @@ fn parse_create_table_with_invalid_options() { fn ms() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(MsSqlDialect {})], + dialects: vec![Box::new(MsSqlDialect::default())], options: None, } } fn ms_and_generic() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(MsSqlDialect {}), Box::new(GenericDialect {})], + dialects: vec![ + Box::new(MsSqlDialect::default()), + Box::new(GenericDialect::default()), + ], options: None, } } diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 33587c35a..cb51263d4 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -940,7 +940,7 @@ fn parse_escaped_quote_identifiers_with_escape() { let sql = "SELECT `quoted `` identifier`"; assert_eq!( TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: None, } .verified_stmt(sql), @@ -987,7 +987,7 @@ fn parse_escaped_quote_identifiers_with_no_escape() { let sql = "SELECT `quoted `` identifier`"; assert_eq!( TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: Some(ParserOptions { trailing_commas: false, unescape: false, @@ -1037,7 +1037,7 @@ fn parse_escaped_backticks_with_escape() { let sql = "SELECT ```quoted identifier```"; assert_eq!( TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: None, } .verified_stmt(sql), @@ -1084,7 +1084,7 @@ fn parse_escaped_backticks_with_no_escape() { let sql = "SELECT ```quoted identifier```"; assert_eq!( TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: Some(ParserOptions::new().with_unescape(false)), } .verified_stmt(sql), @@ -1142,49 +1142,49 @@ fn check_roundtrip_of_escaped_string() { let options = Some(ParserOptions::new().with_unescape(false)); TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: options.clone(), } .verified_stmt(r"SELECT 'I\'m fine'"); TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: options.clone(), } .verified_stmt(r#"SELECT 'I''m fine'"#); TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: options.clone(), } .verified_stmt(r"SELECT 'I\\\'m fine'"); TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: options.clone(), } .verified_stmt(r"SELECT 'I\\\'m fine'"); TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: options.clone(), } .verified_stmt(r#"SELECT "I\"m fine""#); TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: options.clone(), } .verified_stmt(r#"SELECT "I""m fine""#); TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: options.clone(), } .verified_stmt(r#"SELECT "I\\\"m fine""#); TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: options.clone(), } .verified_stmt(r#"SELECT "I\\\"m fine""#); TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options, } .verified_stmt(r#"SELECT "I'm ''fine''""#); @@ -2620,14 +2620,17 @@ fn parse_create_table_with_fulltext_definition_should_not_accept_constraint_name fn mysql() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(MySqlDialect {})], + dialects: vec![Box::new(MySqlDialect::default())], options: None, } } fn mysql_and_generic() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(MySqlDialect {}), Box::new(GenericDialect {})], + dialects: vec![ + Box::new(MySqlDialect::default()), + Box::new(GenericDialect::default()), + ], options: None, } } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index ec1311f2c..9e6346ad3 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -1846,10 +1846,12 @@ fn parse_pg_returning() { } fn test_operator(operator: &str, dialect: &TestedDialects, expected: BinaryOperator) { - let operator_tokens = - sqlparser::tokenizer::Tokenizer::new(&PostgreSqlDialect {}, &format!("a{operator}b")) - .tokenize() - .unwrap(); + let operator_tokens = sqlparser::tokenizer::Tokenizer::new( + &PostgreSqlDialect::default(), + &format!("a{operator}b"), + ) + .tokenize() + .unwrap(); assert_eq!( operator_tokens.len(), 3, @@ -2934,14 +2936,17 @@ fn parse_on_commit() { fn pg() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(PostgreSqlDialect {})], + dialects: vec![Box::new(PostgreSqlDialect::default())], options: None, } } fn pg_and_generic() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(PostgreSqlDialect {}), Box::new(GenericDialect {})], + dialects: vec![ + Box::new(PostgreSqlDialect::default()), + Box::new(GenericDialect::default()), + ], options: None, } } diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index 440116e02..575438b0d 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -167,14 +167,17 @@ fn parse_delimited_identifiers() { fn redshift() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(RedshiftSqlDialect {})], + dialects: vec![Box::new(RedshiftSqlDialect::default())], options: None, } } fn redshift_and_generic() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(RedshiftSqlDialect {}), Box::new(GenericDialect {})], + dialects: vec![ + Box::new(RedshiftSqlDialect::default()), + Box::new(GenericDialect::default()), + ], options: None, } } diff --git a/tests/sqlparser_regression.rs b/tests/sqlparser_regression.rs index e869e0932..fbd6edc94 100644 --- a/tests/sqlparser_regression.rs +++ b/tests/sqlparser_regression.rs @@ -24,7 +24,7 @@ macro_rules! tpch_tests { #[test] fn $name() { - let dialect = GenericDialect {}; + let dialect = GenericDialect::default(); let res = Parser::parse_sql(&dialect, QUERIES[$value -1]); assert!(res.is_ok()); } diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index d0876fc50..912c60b4d 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -486,7 +486,7 @@ fn test_snowflake_create_table_incomplete_statement() { #[test] fn test_snowflake_single_line_tokenize() { let sql = "CREATE TABLE# this is a comment \ntable_1"; - let dialect = SnowflakeDialect {}; + let dialect = SnowflakeDialect::default(); let tokens = Tokenizer::new(&dialect, sql).tokenize().unwrap(); let expected = vec![ @@ -535,7 +535,7 @@ fn parse_sf_create_or_replace_view_with_comment_missing_equal() { fn parse_sf_create_or_replace_with_comment_for_snowflake() { let sql = "CREATE OR REPLACE VIEW v COMMENT = 'hello, world' AS SELECT 1"; let dialect = test_utils::TestedDialects { - dialects: vec![Box::new(SnowflakeDialect {}) as Box], + dialects: vec![Box::new(SnowflakeDialect::default()) as Box], options: None, }; @@ -931,21 +931,24 @@ fn test_array_agg_func() { fn snowflake() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(SnowflakeDialect {})], + dialects: vec![Box::new(SnowflakeDialect::default())], options: None, } } fn snowflake_without_unescape() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(SnowflakeDialect {})], + dialects: vec![Box::new(SnowflakeDialect::default())], options: Some(ParserOptions::new().with_unescape(false)), } } fn snowflake_and_generic() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(SnowflakeDialect {}), Box::new(GenericDialect {})], + dialects: vec![ + Box::new(SnowflakeDialect::default()), + Box::new(GenericDialect::default()), + ], options: None, } } diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index ce11b015a..1cb13ac50 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -489,21 +489,24 @@ fn test_dollar_identifier_as_placeholder() { fn sqlite() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(SQLiteDialect {})], + dialects: vec![Box::new(SQLiteDialect::default())], options: None, } } fn sqlite_with_options(options: ParserOptions) -> TestedDialects { TestedDialects { - dialects: vec![Box::new(SQLiteDialect {})], + dialects: vec![Box::new(SQLiteDialect::default())], options: Some(options), } } fn sqlite_and_generic() -> TestedDialects { TestedDialects { - dialects: vec![Box::new(SQLiteDialect {}), Box::new(GenericDialect {})], + dialects: vec![ + Box::new(SQLiteDialect::default()), + Box::new(GenericDialect::default()), + ], options: None, } }