From 1bff427f7d00b36422da5f88e1efa086c3d548d3 Mon Sep 17 00:00:00 2001 From: Andy Grove Date: Fri, 23 Jan 2026 07:51:50 -0700 Subject: [PATCH] Refactor: replace more `dialect_of!` checks with `Dialect` trait methods Continues the work from #2171 by replacing 12 more `dialect_of!` macro usages with proper `Dialect` trait methods. This allows user-defined dialects to customize parsing behavior and makes the differences between dialects more clearly documented in the trait. New trait methods added: - `supports_select_wildcard_replace` - SELECT * REPLACE syntax - `supports_select_wildcard_ilike` - SELECT * ILIKE syntax - `supports_select_wildcard_rename` - SELECT * RENAME syntax - `supports_optimize_table` - OPTIMIZE TABLE statement - `supports_install` - INSTALL statement - `supports_detach` - DETACH statement - `supports_prewhere` - PREWHERE clause - `supports_with_fill` - WITH FILL in ORDER BY - `supports_limit_by` - LIMIT BY clause - `supports_interpolate` - INTERPOLATE clause - `supports_settings` - SETTINGS clause - `supports_select_format` - FORMAT clause in SELECT Co-Authored-By: Claude Opus 4.5 --- src/dialect/bigquery.rs | 5 ++ src/dialect/clickhouse.rs | 40 ++++++++++ src/dialect/duckdb.rs | 15 ++++ src/dialect/generic.rs | 48 ++++++++++++ src/dialect/mod.rs | 153 ++++++++++++++++++++++++++++++++++++++ src/dialect/snowflake.rs | 15 ++++ src/parser/mod.rs | 50 ++++++------- 7 files changed, 297 insertions(+), 29 deletions(-) diff --git a/src/dialect/bigquery.rs b/src/dialect/bigquery.rs index 6ad8a5089..5563d1335 100644 --- a/src/dialect/bigquery.rs +++ b/src/dialect/bigquery.rs @@ -156,4 +156,9 @@ impl Dialect for BigQueryDialect { fn supports_create_table_multi_schema_info_sources(&self) -> bool { true } + + /// See + fn supports_select_wildcard_replace(&self) -> bool { + true + } } diff --git a/src/dialect/clickhouse.rs b/src/dialect/clickhouse.rs index 39e8a0b30..041b94ecd 100644 --- a/src/dialect/clickhouse.rs +++ b/src/dialect/clickhouse.rs @@ -100,4 +100,44 @@ impl Dialect for ClickHouseDialect { fn supports_nested_comments(&self) -> bool { true } + + /// See + fn supports_optimize_table(&self) -> bool { + true + } + + /// See + fn supports_prewhere(&self) -> bool { + true + } + + /// See + fn supports_with_fill(&self) -> bool { + true + } + + /// See + fn supports_limit_by(&self) -> bool { + true + } + + /// See + fn supports_interpolate(&self) -> bool { + true + } + + /// See + fn supports_settings(&self) -> bool { + true + } + + /// See + fn supports_select_format(&self) -> bool { + true + } + + /// See + fn supports_select_wildcard_replace(&self) -> bool { + true + } } diff --git a/src/dialect/duckdb.rs b/src/dialect/duckdb.rs index ea0990131..b3803aee3 100644 --- a/src/dialect/duckdb.rs +++ b/src/dialect/duckdb.rs @@ -113,4 +113,19 @@ impl Dialect for DuckDbDialect { fn supports_notnull_operator(&self) -> bool { true } + + /// See + fn supports_install(&self) -> bool { + true + } + + /// See + fn supports_detach(&self) -> bool { + true + } + + /// See + fn supports_select_wildcard_replace(&self) -> bool { + true + } } diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index 42510e2f0..d460c5237 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -223,4 +223,52 @@ impl Dialect for GenericDialect { fn supports_lambda_functions(&self) -> bool { true } + + fn supports_select_wildcard_replace(&self) -> bool { + true + } + + fn supports_select_wildcard_ilike(&self) -> bool { + true + } + + fn supports_select_wildcard_rename(&self) -> bool { + true + } + + fn supports_optimize_table(&self) -> bool { + true + } + + fn supports_install(&self) -> bool { + true + } + + fn supports_detach(&self) -> bool { + true + } + + fn supports_prewhere(&self) -> bool { + true + } + + fn supports_with_fill(&self) -> bool { + true + } + + fn supports_limit_by(&self) -> bool { + true + } + + fn supports_interpolate(&self) -> bool { + true + } + + fn supports_settings(&self) -> bool { + true + } + + fn supports_select_format(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 284fc4172..98ec93da4 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1332,6 +1332,159 @@ pub trait Dialect: Debug + Any { fn supports_binary_kw_as_cast(&self) -> bool { false } + + /// Returns true if this dialect supports the `REPLACE` option in a + /// `SELECT *` wildcard expression. + /// + /// Example: + /// ```sql + /// SELECT * REPLACE (col1 AS col1_alias) FROM table; + /// ``` + /// + /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/query-syntax#select_replace) + /// [ClickHouse](https://clickhouse.com/docs/sql-reference/statements/select#replace) + /// [DuckDB](https://duckdb.org/docs/sql/query_syntax/select#replace-clause) + /// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/select#parameters) + fn supports_select_wildcard_replace(&self) -> bool { + false + } + + /// Returns true if this dialect supports the `ILIKE` option in a + /// `SELECT *` wildcard expression. + /// + /// Example: + /// ```sql + /// SELECT * ILIKE '%pattern%' FROM table; + /// ``` + /// + /// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/select#parameters) + fn supports_select_wildcard_ilike(&self) -> bool { + false + } + + /// Returns true if this dialect supports the `RENAME` option in a + /// `SELECT *` wildcard expression. + /// + /// Example: + /// ```sql + /// SELECT * RENAME col1 AS col1_alias FROM table; + /// ``` + /// + /// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/select#parameters) + fn supports_select_wildcard_rename(&self) -> bool { + false + } + + /// Returns true if this dialect supports the `OPTIMIZE TABLE` statement. + /// + /// Example: + /// ```sql + /// OPTIMIZE TABLE table_name; + /// ``` + /// + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/optimize) + fn supports_optimize_table(&self) -> bool { + false + } + + /// Returns true if this dialect supports the `INSTALL` statement. + /// + /// Example: + /// ```sql + /// INSTALL extension_name; + /// ``` + /// + /// [DuckDB](https://duckdb.org/docs/extensions/overview) + fn supports_install(&self) -> bool { + false + } + + /// Returns true if this dialect supports the `DETACH` statement. + /// + /// Example: + /// ```sql + /// DETACH DATABASE db_name; + /// ``` + /// + /// [DuckDB](https://duckdb.org/docs/sql/statements/attach#detach-syntax) + fn supports_detach(&self) -> bool { + false + } + + /// Returns true if this dialect supports the `PREWHERE` clause + /// in `SELECT` statements. + /// + /// Example: + /// ```sql + /// SELECT * FROM table PREWHERE col > 0 WHERE col < 100; + /// ``` + /// + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/select/prewhere) + fn supports_prewhere(&self) -> bool { + false + } + + /// Returns true if this dialect supports the `WITH FILL` clause + /// in `ORDER BY` expressions. + /// + /// Example: + /// ```sql + /// SELECT * FROM table ORDER BY col WITH FILL FROM 1 TO 10 STEP 1; + /// ``` + /// + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier) + fn supports_with_fill(&self) -> bool { + false + } + + /// Returns true if this dialect supports the `LIMIT BY` clause. + /// + /// Example: + /// ```sql + /// SELECT * FROM table LIMIT 10 BY col; + /// ``` + /// + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/select/limit-by) + fn supports_limit_by(&self) -> bool { + false + } + + /// Returns true if this dialect supports the `INTERPOLATE` clause + /// in `ORDER BY` expressions. + /// + /// Example: + /// ```sql + /// SELECT * FROM table ORDER BY col WITH FILL INTERPOLATE (col2 AS col2 + 1); + /// ``` + /// + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/select/order-by#order-by-expr-with-fill-modifier) + fn supports_interpolate(&self) -> bool { + false + } + + /// Returns true if this dialect supports the `SETTINGS` clause. + /// + /// Example: + /// ```sql + /// SELECT * FROM table SETTINGS max_threads = 4; + /// ``` + /// + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/select#settings-in-select-query) + fn supports_settings(&self) -> bool { + false + } + + /// Returns true if this dialect supports the `FORMAT` clause in `SELECT` statements. + /// + /// Example: + /// ```sql + /// SELECT * FROM table FORMAT JSON; + /// ``` + /// + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/select/format) + fn supports_select_format(&self) -> bool { + false + } } /// Operators for which precedence must be defined. diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index d768f7a21..e2d8cb2e1 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -616,6 +616,21 @@ impl Dialect for SnowflakeDialect { fn supports_semantic_view_table_factor(&self) -> bool { true } + + /// See + fn supports_select_wildcard_replace(&self) -> bool { + true + } + + /// See + fn supports_select_wildcard_ilike(&self) -> bool { + true + } + + /// See + fn supports_select_wildcard_rename(&self) -> bool { + true + } } // Peeks ahead to identify tokens that are expected after diff --git a/src/parser/mod.rs b/src/parser/mod.rs index cfc173d76..78e156852 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -633,7 +633,7 @@ impl<'a> Parser<'a> { self.parse_attach_database() } } - Keyword::DETACH if dialect_of!(self is DuckDbDialect | GenericDialect) => { + Keyword::DETACH if self.dialect.supports_detach() => { self.parse_detach_duckdb_database() } Keyword::MSCK => self.parse_msck().map(Into::into), @@ -693,12 +693,10 @@ impl<'a> Parser<'a> { } Keyword::RENAME => self.parse_rename(), // `INSTALL` is duckdb specific https://duckdb.org/docs/extensions/overview - Keyword::INSTALL if dialect_of!(self is DuckDbDialect | GenericDialect) => { - self.parse_install() - } + Keyword::INSTALL if self.dialect.supports_install() => self.parse_install(), Keyword::LOAD => self.parse_load(), // `OPTIMIZE` is clickhouse specific https://clickhouse.tech/docs/en/sql-reference/statements/optimize/ - Keyword::OPTIMIZE if dialect_of!(self is ClickHouseDialect | GenericDialect) => { + Keyword::OPTIMIZE if self.dialect.supports_optimize_table() => { self.parse_optimize_table() } // `COMMENT` is snowflake specific https://docs.snowflake.com/en/sql-reference/sql/comment @@ -12203,7 +12201,7 @@ impl<'a> Parser<'a> { } } else { let exprs = self.parse_comma_separated(Parser::parse_order_by_expr)?; - let interpolate = if dialect_of!(self is ClickHouseDialect | GenericDialect) { + let interpolate = if self.dialect.supports_interpolate() { self.parse_interpolations()? } else { None @@ -12245,9 +12243,7 @@ impl<'a> Parser<'a> { })); } - let limit_by = if dialect_of!(self is ClickHouseDialect | GenericDialect) - && self.parse_keyword(Keyword::BY) - { + let limit_by = if self.dialect.supports_limit_by() && self.parse_keyword(Keyword::BY) { Some(self.parse_comma_separated(Parser::parse_expr)?) } else { None @@ -13258,18 +13254,17 @@ impl<'a> Parser<'a> { locks.push(self.parse_lock()?); } } - let format_clause = if dialect_of!(self is ClickHouseDialect | GenericDialect) - && self.parse_keyword(Keyword::FORMAT) - { - if self.parse_keyword(Keyword::NULL) { - Some(FormatClause::Null) + let format_clause = + if self.dialect.supports_select_format() && self.parse_keyword(Keyword::FORMAT) { + if self.parse_keyword(Keyword::NULL) { + Some(FormatClause::Null) + } else { + let ident = self.parse_identifier()?; + Some(FormatClause::Identifier(ident)) + } } else { - let ident = self.parse_identifier()?; - Some(FormatClause::Identifier(ident)) - } - } else { - None - }; + None + }; let pipe_operators = if self.dialect.supports_pipe_operator() { self.parse_pipe_operators()? @@ -13513,8 +13508,7 @@ impl<'a> Parser<'a> { } fn parse_settings(&mut self) -> Result>, ParserError> { - let settings = if dialect_of!(self is ClickHouseDialect|GenericDialect) - && self.parse_keyword(Keyword::SETTINGS) + let settings = if self.dialect.supports_settings() && self.parse_keyword(Keyword::SETTINGS) { let key_values = self.parse_comma_separated(|p| { let key = p.parse_identifier()?; @@ -13924,8 +13918,7 @@ impl<'a> Parser<'a> { } } - let prewhere = if dialect_of!(self is ClickHouseDialect|GenericDialect) - && self.parse_keyword(Keyword::PREWHERE) + let prewhere = if self.dialect.supports_prewhere() && self.parse_keyword(Keyword::PREWHERE) { Some(self.parse_expr()?) } else { @@ -17344,7 +17337,7 @@ impl<'a> Parser<'a> { &mut self, wildcard_token: TokenWithSpan, ) -> Result { - let opt_ilike = if dialect_of!(self is GenericDialect | SnowflakeDialect) { + let opt_ilike = if self.dialect.supports_select_wildcard_ilike() { self.parse_optional_select_item_ilike()? } else { None @@ -17360,13 +17353,12 @@ impl<'a> Parser<'a> { } else { None }; - let opt_replace = if dialect_of!(self is GenericDialect | BigQueryDialect | ClickHouseDialect | DuckDbDialect | SnowflakeDialect) - { + let opt_replace = if self.dialect.supports_select_wildcard_replace() { self.parse_optional_select_item_replace()? } else { None }; - let opt_rename = if dialect_of!(self is GenericDialect | SnowflakeDialect) { + let opt_rename = if self.dialect.supports_select_wildcard_rename() { self.parse_optional_select_item_rename()? } else { None @@ -17563,7 +17555,7 @@ impl<'a> Parser<'a> { let options = self.parse_order_by_options()?; - let with_fill = if dialect_of!(self is ClickHouseDialect | GenericDialect) + let with_fill = if self.dialect.supports_with_fill() && self.parse_keywords(&[Keyword::WITH, Keyword::FILL]) { Some(self.parse_with_fill()?)