From 7ff24bee75007d4c19dcd6ed7cc2683b9644e95d Mon Sep 17 00:00:00 2001 From: finchxxia <13153363548@163.com> Date: Thu, 18 Jun 2026 12:53:44 +0800 Subject: [PATCH 1/6] Doris: Add dialect skeleton --- examples/cli.rs | 1 + src/dialect/doris.rs | 53 ++++++++++++++++++++++++++++++++++++++ src/dialect/mod.rs | 5 ++++ src/test_utils.rs | 1 + tests/sqlparser_doris.rs | 55 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 115 insertions(+) create mode 100644 src/dialect/doris.rs create mode 100644 tests/sqlparser_doris.rs diff --git a/examples/cli.rs b/examples/cli.rs index 3c4299b209..e5643dc0be 100644 --- a/examples/cli.rs +++ b/examples/cli.rs @@ -52,6 +52,7 @@ $ cargo run --example cli - [--dialectname] "--postgres" => Box::new(PostgreSqlDialect {}), "--ms" => Box::new(MsSqlDialect {}), "--mysql" => Box::new(MySqlDialect {}), + "--doris" => Box::new(DorisDialect {}), "--snowflake" => Box::new(SnowflakeDialect {}), "--hive" => Box::new(HiveDialect {}), "--redshift" => Box::new(RedshiftSqlDialect {}), diff --git a/src/dialect/doris.rs b/src/dialect/doris.rs new file mode 100644 index 0000000000..17911fd7ab --- /dev/null +++ b/src/dialect/doris.rs @@ -0,0 +1,53 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use crate::dialect::Dialect; + +/// A [`Dialect`] for [Apache Doris](https://doris.apache.org/). +#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct DorisDialect {} + +impl Dialect for DorisDialect { + fn is_delimited_identifier_start(&self, ch: char) -> bool { + ch == '`' + } + + fn identifier_quote_style(&self, _identifier: &str) -> Option { + Some('`') + } + + fn is_identifier_start(&self, ch: char) -> bool { + ch.is_ascii_alphabetic() || ch == '_' || !ch.is_ascii() + } + + 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 ignores_wildcard_escapes(&self) -> bool { + true + } + + fn supports_numeric_prefix(&self) -> bool { + true + } +} diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index efabfeb1f4..4b6d3673ad 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -19,6 +19,7 @@ mod ansi; mod bigquery; mod clickhouse; mod databricks; +mod doris; mod duckdb; mod generic; mod hive; @@ -43,6 +44,7 @@ pub use self::ansi::AnsiDialect; pub use self::bigquery::BigQueryDialect; pub use self::clickhouse::ClickHouseDialect; pub use self::databricks::DatabricksDialect; +pub use self::doris::DorisDialect; pub use self::duckdb::DuckDbDialect; pub use self::generic::GenericDialect; pub use self::hive::HiveDialect; @@ -1908,6 +1910,7 @@ pub fn dialect_from_str(dialect_name: impl AsRef) -> Option Some(Box::new(BigQueryDialect)), "ansi" => Some(Box::new(AnsiDialect {})), "duckdb" => Some(Box::new(DuckDbDialect {})), + "doris" => Some(Box::new(DorisDialect {})), "databricks" => Some(Box::new(DatabricksDialect {})), "spark" | "sparksql" => Some(Box::new(SparkSqlDialect {})), "oracle" => Some(Box::new(OracleDialect {})), @@ -1963,6 +1966,8 @@ mod tests { assert!(parse_dialect("ANSI").is::()); assert!(parse_dialect("duckdb").is::()); assert!(parse_dialect("DuckDb").is::()); + assert!(parse_dialect("doris").is::()); + assert!(parse_dialect("Doris").is::()); assert!(parse_dialect("DataBricks").is::()); assert!(parse_dialect("databricks").is::()); assert!(parse_dialect("teradata").is::()); diff --git a/src/test_utils.rs b/src/test_utils.rs index c4d1d0db2e..e19f28d202 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -286,6 +286,7 @@ pub fn all_dialects() -> TestedDialects { Box::new(HiveDialect {}), Box::new(RedshiftSqlDialect {}), Box::new(MySqlDialect {}), + Box::new(DorisDialect {}), Box::new(BigQueryDialect {}), Box::new(SQLiteDialect {}), Box::new(DuckDbDialect {}), diff --git a/tests/sqlparser_doris.rs b/tests/sqlparser_doris.rs new file mode 100644 index 0000000000..31ed99864f --- /dev/null +++ b/tests/sqlparser_doris.rs @@ -0,0 +1,55 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +#![warn(clippy::all)] +//! Test SQL syntax specific to Apache Doris. + +#[macro_use] +mod test_utils; + +use sqlparser::dialect::{Dialect, DorisDialect, GenericDialect}; +use test_utils::*; + +fn doris() -> TestedDialects { + TestedDialects::new(vec![Box::new(DorisDialect {})]) +} + +fn doris_and_generic() -> TestedDialects { + TestedDialects::new(vec![Box::new(DorisDialect {}), Box::new(GenericDialect {})]) +} + +#[test] +fn doris_identifier_and_string_literal_gates() { + let dialect = DorisDialect {}; + assert_eq!(dialect.identifier_quote_style("identifier"), Some('`')); + assert!(dialect.is_delimited_identifier_start('`')); + assert!(dialect.supports_string_literal_backslash_escape()); + assert!(dialect.ignores_wildcard_escapes()); + assert!(dialect.supports_numeric_prefix()); +} + +#[test] +fn parse_doris_strings_and_identifiers() { + doris().verified_stmt( + r#"SELECT "double quoted string", 'single quoted string', `select` FROM `db`.`table`"#, + ); +} + +#[test] +fn doris_and_generic_parse_common_sql_identically() { + doris_and_generic().verified_stmt("SELECT 1 AS properties FROM t"); +} From d29d0270a2b29ec17084b5c3a93f22004f215145 Mon Sep 17 00:00:00 2001 From: finchxxia <13153363548@163.com> Date: Wed, 12 Aug 2026 15:11:07 +0800 Subject: [PATCH 2/6] Doris: Delegate MySQL-compatible query syntax --- src/dialect/doris.rs | 39 ++++++++++++++++++++++++++++++--------- tests/sqlparser_doris.rs | 17 ++++++++++++++++- 2 files changed, 46 insertions(+), 10 deletions(-) diff --git a/src/dialect/doris.rs b/src/dialect/doris.rs index 17911fd7ab..3636423e1f 100644 --- a/src/dialect/doris.rs +++ b/src/dialect/doris.rs @@ -15,7 +15,11 @@ // specific language governing permissions and limitations // under the License. -use crate::dialect::Dialect; +use crate::{ + ast::Expr, + dialect::{Dialect, MySqlDialect}, + parser::{Parser, ParserError}, +}; /// A [`Dialect`] for [Apache Doris](https://doris.apache.org/). #[derive(Debug, Default, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -24,30 +28,47 @@ pub struct DorisDialect {} impl Dialect for DorisDialect { fn is_delimited_identifier_start(&self, ch: char) -> bool { - ch == '`' + MySqlDialect {}.is_delimited_identifier_start(ch) } - fn identifier_quote_style(&self, _identifier: &str) -> Option { - Some('`') + fn identifier_quote_style(&self, identifier: &str) -> Option { + MySqlDialect {}.identifier_quote_style(identifier) } fn is_identifier_start(&self, ch: char) -> bool { - ch.is_ascii_alphabetic() || ch == '_' || !ch.is_ascii() + MySqlDialect {}.is_identifier_start(ch) } fn is_identifier_part(&self, ch: char) -> bool { - self.is_identifier_start(ch) || ch.is_ascii_digit() + MySqlDialect {}.is_identifier_part(ch) } fn supports_string_literal_backslash_escape(&self) -> bool { - true + MySqlDialect {}.supports_string_literal_backslash_escape() } fn ignores_wildcard_escapes(&self) -> bool { - true + MySqlDialect {}.ignores_wildcard_escapes() } fn supports_numeric_prefix(&self) -> bool { - true + MySqlDialect {}.supports_numeric_prefix() + } + + fn supports_limit_comma(&self) -> bool { + MySqlDialect {}.supports_limit_comma() + } + + fn parse_infix( + &self, + parser: &mut Parser, + expr: &Expr, + precedence: u8, + ) -> Option> { + MySqlDialect {}.parse_infix(parser, expr, precedence) + } + + fn supports_group_by_with_modifier(&self) -> bool { + MySqlDialect {}.supports_group_by_with_modifier() } } diff --git a/tests/sqlparser_doris.rs b/tests/sqlparser_doris.rs index 31ed99864f..e3477d6098 100644 --- a/tests/sqlparser_doris.rs +++ b/tests/sqlparser_doris.rs @@ -44,7 +44,7 @@ fn doris_identifier_and_string_literal_gates() { #[test] fn parse_doris_strings_and_identifiers() { - doris().verified_stmt( + doris().verified_only_select( r#"SELECT "double quoted string", 'single quoted string', `select` FROM `db`.`table`"#, ); } @@ -53,3 +53,18 @@ fn parse_doris_strings_and_identifiers() { fn doris_and_generic_parse_common_sql_identically() { doris_and_generic().verified_stmt("SELECT 1 AS properties FROM t"); } + +#[test] +fn parse_doris_limit_comma() { + doris().verified_only_select("SELECT * FROM t LIMIT 5, 10"); +} + +#[test] +fn parse_doris_div_infix() { + doris().verified_only_select("SELECT 5 DIV 2"); +} + +#[test] +fn parse_doris_group_by_with_rollup() { + doris().verified_only_select("SELECT * FROM t GROUP BY col1, col2 WITH ROLLUP"); +} From 3390f3f638468b8ed201cbec9b650702c1c698a4 Mon Sep 17 00:00:00 2001 From: finchxxia <13153363548@163.com> Date: Wed, 12 Aug 2026 15:11:21 +0800 Subject: [PATCH 3/6] Doris: Refine dialect tests --- tests/sqlparser_doris.rs | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/tests/sqlparser_doris.rs b/tests/sqlparser_doris.rs index e3477d6098..33e0617fe7 100644 --- a/tests/sqlparser_doris.rs +++ b/tests/sqlparser_doris.rs @@ -21,27 +21,13 @@ #[macro_use] mod test_utils; -use sqlparser::dialect::{Dialect, DorisDialect, GenericDialect}; +use sqlparser::dialect::DorisDialect; use test_utils::*; fn doris() -> TestedDialects { TestedDialects::new(vec![Box::new(DorisDialect {})]) } -fn doris_and_generic() -> TestedDialects { - TestedDialects::new(vec![Box::new(DorisDialect {}), Box::new(GenericDialect {})]) -} - -#[test] -fn doris_identifier_and_string_literal_gates() { - let dialect = DorisDialect {}; - assert_eq!(dialect.identifier_quote_style("identifier"), Some('`')); - assert!(dialect.is_delimited_identifier_start('`')); - assert!(dialect.supports_string_literal_backslash_escape()); - assert!(dialect.ignores_wildcard_escapes()); - assert!(dialect.supports_numeric_prefix()); -} - #[test] fn parse_doris_strings_and_identifiers() { doris().verified_only_select( @@ -49,11 +35,6 @@ fn parse_doris_strings_and_identifiers() { ); } -#[test] -fn doris_and_generic_parse_common_sql_identically() { - doris_and_generic().verified_stmt("SELECT 1 AS properties FROM t"); -} - #[test] fn parse_doris_limit_comma() { doris().verified_only_select("SELECT * FROM t LIMIT 5, 10"); From 282819b89e5e2b15127e24bf17355707029c453a Mon Sep 17 00:00:00 2001 From: finchxxia <13153363548@163.com> Date: Mon, 28 Sep 2026 08:58:18 +0800 Subject: [PATCH 4/6] Update tests/sqlparser_doris.rs Co-authored-by: Luca Cappelletti --- tests/sqlparser_doris.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/tests/sqlparser_doris.rs b/tests/sqlparser_doris.rs index 33e0617fe7..1beb64968c 100644 --- a/tests/sqlparser_doris.rs +++ b/tests/sqlparser_doris.rs @@ -49,3 +49,8 @@ fn parse_doris_div_infix() { fn parse_doris_group_by_with_rollup() { doris().verified_only_select("SELECT * FROM t GROUP BY col1, col2 WITH ROLLUP"); } + +#[test] +fn parse_doris_nested_comments() { + doris().one_statement_parses_to("SELECT 1 /* a /* b */ c */, 2", "SELECT 1, 2"); +} From ca70ec2f8b2729434fd399ef74550a24839c466a Mon Sep 17 00:00:00 2001 From: finchxxia <13153363548@163.com> Date: Mon, 28 Sep 2026 08:58:32 +0800 Subject: [PATCH 5/6] Update src/dialect/doris.rs Co-authored-by: Luca Cappelletti --- src/dialect/doris.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/dialect/doris.rs b/src/dialect/doris.rs index 3636423e1f..bdddb16d68 100644 --- a/src/dialect/doris.rs +++ b/src/dialect/doris.rs @@ -27,6 +27,10 @@ use crate::{ pub struct DorisDialect {} impl Dialect for DorisDialect { + fn supports_nested_comments(&self) -> bool { + true + } + fn is_delimited_identifier_start(&self, ch: char) -> bool { MySqlDialect {}.is_delimited_identifier_start(ch) } From e559954216a842bfacb46885964dbd18dfdfce3f Mon Sep 17 00:00:00 2001 From: finchxxia <13153363548@163.com> Date: Mon, 28 Sep 2026 09:13:32 +0800 Subject: [PATCH 6/6] chore: add Doris to fuzz test --- .github/workflows/labeler/label_dialects.js | 1 + fuzz/fuzz_targets/fuzz_parse_roundtrip.rs | 10 ++++++---- fuzz/fuzz_targets/fuzz_parse_sql.rs | 10 ++++++---- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/.github/workflows/labeler/label_dialects.js b/.github/workflows/labeler/label_dialects.js index ef752ef2dd..3b93979478 100644 --- a/.github/workflows/labeler/label_dialects.js +++ b/.github/workflows/labeler/label_dialects.js @@ -19,6 +19,7 @@ const DIALECTS = [ { label: "BigQuery", stems: ["bigquery"], pattern: /\bbig\s?query\b/i }, { label: "ClickHouse", stems: ["clickhouse"], pattern: /\bclick\s?house\b/i }, { label: "Databricks", stems: ["databricks"], pattern: /\bdatabricks\b/i }, + { label: "Doris", stems: ["doris"], pattern: /\bdoris\b/i }, { label: "DuckDB", stems: ["duckdb"], pattern: /\bduck\s?db\b/i }, { label: "Hive", stems: ["hive"], pattern: /\bhive\b/i }, { label: "MySQL", stems: ["mysql"], pattern: /\b(mysql|maria\s?db)\b/i }, diff --git a/fuzz/fuzz_targets/fuzz_parse_roundtrip.rs b/fuzz/fuzz_targets/fuzz_parse_roundtrip.rs index f1b27d654a..2d340dfe64 100644 --- a/fuzz/fuzz_targets/fuzz_parse_roundtrip.rs +++ b/fuzz/fuzz_targets/fuzz_parse_roundtrip.rs @@ -19,18 +19,20 @@ use libfuzzer_sys::fuzz_target; use sqlparser::dialect::{ - AnsiDialect, BigQueryDialect, ClickHouseDialect, DatabricksDialect, Dialect, DuckDbDialect, - GenericDialect, HiveDialect, MsSqlDialect, MySqlDialect, OracleDialect, PostgreSqlDialect, - RedshiftSqlDialect, SQLiteDialect, SnowflakeDialect, SparkSqlDialect, TeradataDialect, + AnsiDialect, BigQueryDialect, ClickHouseDialect, DatabricksDialect, Dialect, DorisDialect, + DuckDbDialect, GenericDialect, HiveDialect, MsSqlDialect, MySqlDialect, OracleDialect, + PostgreSqlDialect, RedshiftSqlDialect, SQLiteDialect, SnowflakeDialect, SparkSqlDialect, + TeradataDialect, }; use sqlparser::parser::Parser; fuzz_target!(|sql: &str| { - let dialects: [(&str, &dyn Dialect); 16] = [ + let dialects: [(&str, &dyn Dialect); 17] = [ ("ansi", &AnsiDialect {}), ("bigquery", &BigQueryDialect {}), ("clickhouse", &ClickHouseDialect {}), ("databricks", &DatabricksDialect {}), + ("doris", &DorisDialect {}), ("duckdb", &DuckDbDialect {}), ("generic", &GenericDialect {}), ("hive", &HiveDialect {}), diff --git a/fuzz/fuzz_targets/fuzz_parse_sql.rs b/fuzz/fuzz_targets/fuzz_parse_sql.rs index cc59455987..70831b879c 100644 --- a/fuzz/fuzz_targets/fuzz_parse_sql.rs +++ b/fuzz/fuzz_targets/fuzz_parse_sql.rs @@ -19,17 +19,19 @@ use libfuzzer_sys::fuzz_target; use sqlparser::dialect::{ - AnsiDialect, BigQueryDialect, ClickHouseDialect, DatabricksDialect, Dialect, DuckDbDialect, - GenericDialect, HiveDialect, MsSqlDialect, MySqlDialect, OracleDialect, PostgreSqlDialect, - RedshiftSqlDialect, SQLiteDialect, SnowflakeDialect, SparkSqlDialect, TeradataDialect, + AnsiDialect, BigQueryDialect, ClickHouseDialect, DatabricksDialect, Dialect, DorisDialect, + DuckDbDialect, GenericDialect, HiveDialect, MsSqlDialect, MySqlDialect, OracleDialect, + PostgreSqlDialect, RedshiftSqlDialect, SQLiteDialect, SnowflakeDialect, SparkSqlDialect, + TeradataDialect, }; use sqlparser::parser::Parser; fuzz_target!(|sql: &str| { - let dialects: [&dyn Dialect; 16] = [ + let dialects: [&dyn Dialect; 17] = [ &AnsiDialect {}, &BigQueryDialect {}, &ClickHouseDialect {}, &DatabricksDialect {}, + &DorisDialect {}, &DuckDbDialect {}, &GenericDialect {}, &HiveDialect {},