From b2ab0061c172aa55e06ece592309a96083d2220d Mon Sep 17 00:00:00 2001 From: Elia Perantoni Date: Wed, 25 Jun 2025 12:21:59 +0200 Subject: [PATCH 1/6] Fix `impl Ord for Ident` (#1893) --- src/ast/mod.rs | 48 ++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 46 insertions(+), 2 deletions(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 17464f4a..ef0c4dc9 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -28,6 +28,7 @@ use helpers::{ stmt_data_loading::{FileStagingCommand, StageLoadSelectItemKind}, }; +use core::cmp::Ordering; use core::ops::Deref; use core::{ fmt::{self, Display}, @@ -172,7 +173,7 @@ fn format_statement_list(f: &mut fmt::Formatter, statements: &[Statement]) -> fm } /// An identifier, decomposed into its value or character data and the quote style. -#[derive(Debug, Clone, PartialOrd, Ord)] +#[derive(Debug, Clone)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct Ident { @@ -214,6 +215,35 @@ impl core::hash::Hash for Ident { impl Eq for Ident {} +impl PartialOrd for Ident { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl Ord for Ident { + fn cmp(&self, other: &Self) -> Ordering { + let Ident { + value, + quote_style, + // exhaustiveness check; we ignore spans in ordering + span: _, + } = self; + + let Ident { + value: other_value, + quote_style: other_quote_style, + // exhaustiveness check; we ignore spans in ordering + span: _, + } = other; + + // First compare by value, then by quote_style + value + .cmp(other_value) + .then_with(|| quote_style.cmp(other_quote_style)) + } +} + impl Ident { /// Create a new identifier with the given value and no quotes and an empty span. pub fn new(value: S) -> Self @@ -4214,7 +4244,7 @@ pub enum Statement { /// ```sql /// NOTIFY channel [ , payload ] /// ``` - /// send a notification event together with an optional “payload” string to channel + /// send a notification event together with an optional "payload" string to channel /// /// See Postgres NOTIFY { @@ -9771,6 +9801,8 @@ impl fmt::Display for NullInclusion { #[cfg(test)] mod tests { + use crate::tokenizer::Location; + use super::*; #[test] @@ -10066,4 +10098,16 @@ mod tests { test_steps(OneOrManyWithParens::Many(vec![2]), vec![2], 3); test_steps(OneOrManyWithParens::Many(vec![3, 4]), vec![3, 4], 4); } + + // Tests that the position in the code of an `Ident` does not affect its + // ordering. + #[test] + fn test_ident_ord() { + let mut a = Ident::with_span(Span::new(Location::new(1, 1), Location::new(1, 1)), "a"); + let mut b = Ident::with_span(Span::new(Location::new(2, 2), Location::new(2, 2)), "b"); + + assert!(a < b); + std::mem::swap(&mut a.span, &mut b.span); + assert!(a < b); + } } From 1bbc05cdff7952182e2cdafe55091de0f1f7b3ce Mon Sep 17 00:00:00 2001 From: Elia Perantoni Date: Wed, 25 Jun 2025 16:10:01 +0200 Subject: [PATCH 2/6] Snowflake: support multiple column options in `CREATE VIEW` (#1891) --- src/ast/ddl.rs | 28 ++++++++++++++++++++++++++-- src/ast/mod.rs | 15 ++++++++------- src/ast/spans.rs | 17 +++++++++++------ src/dialect/mod.rs | 4 ++++ src/dialect/snowflake.rs | 4 ++++ src/parser/mod.rs | 31 ++++++++++++++++++++----------- tests/sqlparser_bigquery.rs | 18 ++++++++++-------- tests/sqlparser_clickhouse.rs | 4 ++-- tests/sqlparser_common.rs | 2 +- tests/sqlparser_snowflake.rs | 15 ++++++++++++--- 10 files changed, 98 insertions(+), 40 deletions(-) diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index f81c6fc2..d2863c3a 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -1426,7 +1426,24 @@ impl fmt::Display for ColumnDef { pub struct ViewColumnDef { pub name: Ident, pub data_type: Option, - pub options: Option>, + pub options: Option, +} + +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum ColumnOptions { + CommaSeparated(Vec), + SpaceSeparated(Vec), +} + +impl ColumnOptions { + pub fn as_slice(&self) -> &[ColumnOption] { + match self { + ColumnOptions::CommaSeparated(options) => options.as_slice(), + ColumnOptions::SpaceSeparated(options) => options.as_slice(), + } + } } impl fmt::Display for ViewColumnDef { @@ -1436,7 +1453,14 @@ impl fmt::Display for ViewColumnDef { write!(f, " {}", data_type)?; } if let Some(options) = self.options.as_ref() { - write!(f, " {}", display_comma_separated(options.as_slice()))?; + match options { + ColumnOptions::CommaSeparated(column_options) => { + write!(f, " {}", display_comma_separated(column_options.as_slice()))?; + } + ColumnOptions::SpaceSeparated(column_options) => { + write!(f, " {}", display_separated(column_options.as_slice(), " "))? + } + } } Ok(()) } diff --git a/src/ast/mod.rs b/src/ast/mod.rs index ef0c4dc9..bfed91fb 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -61,13 +61,14 @@ pub use self::ddl::{ AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, AlterPolicyOperation, AlterTableAlgorithm, AlterTableLock, AlterTableOperation, AlterType, AlterTypeAddValue, AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, - ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnPolicy, ColumnPolicyProperty, - ConstraintCharacteristics, CreateConnector, CreateDomain, CreateFunction, Deduplicate, - DeferrableInitial, DropBehavior, GeneratedAs, GeneratedExpressionMode, IdentityParameters, - IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, IdentityPropertyOrder, - IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner, Partition, - ProcedureParam, ReferentialAction, ReplicaIdentity, TableConstraint, TagsColumnOption, - UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef, + ClusteredBy, ColumnDef, ColumnOption, ColumnOptionDef, ColumnOptions, ColumnPolicy, + ColumnPolicyProperty, ConstraintCharacteristics, CreateConnector, CreateDomain, CreateFunction, + Deduplicate, DeferrableInitial, DropBehavior, GeneratedAs, GeneratedExpressionMode, + IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, + IdentityPropertyOrder, IndexOption, IndexType, KeyOrIndexDisplay, NullsDistinctOption, Owner, + Partition, ProcedureParam, ReferentialAction, ReplicaIdentity, TableConstraint, + TagsColumnOption, UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, + ViewColumnDef, }; pub use self::dml::{CreateIndex, CreateTable, Delete, IndexColumn, Insert}; pub use self::operator::{BinaryOperator, UnaryOperator}; diff --git a/src/ast/spans.rs b/src/ast/spans.rs index ca321cc2..78ed772b 100644 --- a/src/ast/spans.rs +++ b/src/ast/spans.rs @@ -15,7 +15,7 @@ // specific language governing permissions and limitations // under the License. -use crate::ast::query::SelectItemQualifiedWildcardKind; +use crate::ast::{query::SelectItemQualifiedWildcardKind, ColumnOptions}; use core::iter; use crate::tokenizer::Span; @@ -991,10 +991,13 @@ impl Spanned for ViewColumnDef { options, } = self; - union_spans( - core::iter::once(name.span) - .chain(options.iter().flat_map(|i| i.iter().map(|k| k.span()))), - ) + name.span.union_opt(&options.as_ref().map(|o| o.span())) + } +} + +impl Spanned for ColumnOptions { + fn span(&self) -> Span { + union_spans(self.as_slice().iter().map(|i| i.span())) } } @@ -1055,7 +1058,9 @@ impl Spanned for CreateTableOptions { match self { CreateTableOptions::None => Span::empty(), CreateTableOptions::With(vec) => union_spans(vec.iter().map(|i| i.span())), - CreateTableOptions::Options(vec) => union_spans(vec.iter().map(|i| i.span())), + CreateTableOptions::Options(vec) => { + union_spans(vec.as_slice().iter().map(|i| i.span())) + } CreateTableOptions::Plain(vec) => union_spans(vec.iter().map(|i| i.span())), CreateTableOptions::TableProperties(vec) => union_spans(vec.iter().map(|i| i.span())), } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index a4c899e6..bc92948d 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1028,6 +1028,10 @@ pub trait Dialect: Debug + Any { fn supports_set_names(&self) -> bool { false } + + fn supports_space_separated_column_options(&self) -> bool { + false + } } /// This represents the operators for which precedence must be defined diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 66e04ac2..5ebb7e37 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -356,6 +356,10 @@ impl Dialect for SnowflakeDialect { fn get_reserved_keywords_for_select_item_operator(&self) -> &[Keyword] { &RESERVED_KEYWORDS_FOR_SELECT_ITEM_OPERATOR } + + fn supports_space_separated_column_options(&self) -> bool { + true + } } fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index ca658d7d..44ba0bb7 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -10579,17 +10579,7 @@ impl<'a> Parser<'a> { /// Parses a column definition within a view. fn parse_view_column(&mut self) -> Result { let name = self.parse_identifier()?; - let options = if (dialect_of!(self is BigQueryDialect | GenericDialect) - && self.parse_keyword(Keyword::OPTIONS)) - || (dialect_of!(self is SnowflakeDialect | GenericDialect) - && self.parse_keyword(Keyword::COMMENT)) - { - self.prev_token(); - self.parse_optional_column_option()? - .map(|option| vec![option]) - } else { - None - }; + let options = self.parse_view_column_options()?; let data_type = if dialect_of!(self is ClickHouseDialect) { Some(self.parse_data_type()?) } else { @@ -10602,6 +10592,25 @@ impl<'a> Parser<'a> { }) } + fn parse_view_column_options(&mut self) -> Result, ParserError> { + let mut options = Vec::new(); + loop { + let option = self.parse_optional_column_option()?; + if let Some(option) = option { + options.push(option); + } else { + break; + } + } + if options.is_empty() { + Ok(None) + } else if self.dialect.supports_space_separated_column_options() { + Ok(Some(ColumnOptions::SpaceSeparated(options))) + } else { + Ok(Some(ColumnOptions::CommaSeparated(options))) + } + } + /// Parses a parenthesized comma-separated list of unqualified, possibly quoted identifiers. /// For example: `(col1, "col 2", ...)` pub fn parse_parenthesized_column_list( diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 0de0b12b..2bcdb4e5 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -355,14 +355,16 @@ fn parse_create_view_with_options() { ViewColumnDef { name: Ident::new("age"), data_type: None, - options: Some(vec![ColumnOption::Options(vec![SqlOption::KeyValue { - key: Ident::new("description"), - value: Expr::Value( - Value::DoubleQuotedString("field age".to_string()).with_span( - Span::new(Location::new(1, 42), Location::new(1, 52)) - ) - ), - }])]), + options: Some(ColumnOptions::CommaSeparated(vec![ColumnOption::Options( + vec![SqlOption::KeyValue { + key: Ident::new("description"), + value: Expr::Value( + Value::DoubleQuotedString("field age".to_string()).with_span( + Span::new(Location::new(1, 42), Location::new(1, 52)) + ) + ), + }] + )])), }, ], columns diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 93b4c4f5..ed5d7ad2 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -914,7 +914,7 @@ fn parse_create_view_with_fields_data_types() { }]), vec![] )), - options: None + options: None, }, ViewColumnDef { name: "f".into(), @@ -926,7 +926,7 @@ fn parse_create_view_with_fields_data_types() { }]), vec![] )), - options: None + options: None, }, ] ); diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index d45da69a..3c20f82a 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -7990,7 +7990,7 @@ fn parse_create_view_with_columns() { .map(|name| ViewColumnDef { name, data_type: None, - options: None + options: None, }) .collect::>() ); diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 7dc00f9a..8b3988d9 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -3124,7 +3124,7 @@ fn view_comment_option_should_be_after_column_list() { "CREATE OR REPLACE VIEW v (a COMMENT 'a comment', b, c COMMENT 'c comment') COMMENT = 'Comment' AS SELECT a FROM t", "CREATE OR REPLACE VIEW v (a COMMENT 'a comment', b, c COMMENT 'c comment') WITH (foo = bar) COMMENT = 'Comment' AS SELECT a FROM t", ] { - snowflake_and_generic() + snowflake() .verified_stmt(sql); } } @@ -3133,7 +3133,7 @@ fn view_comment_option_should_be_after_column_list() { fn parse_view_column_descriptions() { let sql = "CREATE OR REPLACE VIEW v (a COMMENT 'Comment', b) AS SELECT a, b FROM table1"; - match snowflake_and_generic().verified_stmt(sql) { + match snowflake().verified_stmt(sql) { Statement::CreateView { name, columns, .. } => { assert_eq!(name.to_string(), "v"); assert_eq!( @@ -3142,7 +3142,9 @@ fn parse_view_column_descriptions() { ViewColumnDef { name: Ident::new("a"), data_type: None, - options: Some(vec![ColumnOption::Comment("Comment".to_string())]), + options: Some(ColumnOptions::SpaceSeparated(vec![ColumnOption::Comment( + "Comment".to_string() + )])), }, ViewColumnDef { name: Ident::new("b"), @@ -4165,3 +4167,10 @@ fn test_snowflake_fetch_clause_syntax() { canonical, ); } + +#[test] +fn test_snowflake_create_view_with_multiple_column_options() { + let create_view_with_tag = + r#"CREATE VIEW X (COL WITH TAG (pii='email') COMMENT 'foobar') AS SELECT * FROM Y"#; + snowflake().verified_stmt(create_view_with_tag); +} From 95d16e3b2d08ba74ada44dbcfe4ece3400b2d89b Mon Sep 17 00:00:00 2001 From: ZacJW Date: Fri, 27 Jun 2025 17:22:21 +0100 Subject: [PATCH 3/6] Add support for `LANGUAGE` clause in `CREATE PROCEDURE` (#1903) --- src/ast/mod.rs | 6 ++++++ src/parser/mod.rs | 8 ++++++++ tests/sqlparser_common.rs | 30 ++++++++++++++++++++++++++++++ tests/sqlparser_mssql.rs | 3 ++- 4 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index bfed91fb..1ea0b5c2 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -3945,6 +3945,7 @@ pub enum Statement { or_alter: bool, name: ObjectName, params: Option>, + language: Option, body: ConditionalStatements, }, /// ```sql @@ -4848,6 +4849,7 @@ impl fmt::Display for Statement { name, or_alter, params, + language, body, } => { write!( @@ -4863,6 +4865,10 @@ impl fmt::Display for Statement { } } + if let Some(language) = language { + write!(f, " LANGUAGE {language}")?; + } + write!(f, " AS {body}") } Statement::CreateMacro { diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 44ba0bb7..e09b7e33 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -15802,6 +15802,13 @@ impl<'a> Parser<'a> { pub fn parse_create_procedure(&mut self, or_alter: bool) -> Result { let name = self.parse_object_name(false)?; let params = self.parse_optional_procedure_parameters()?; + + let language = if self.parse_keyword(Keyword::LANGUAGE) { + Some(self.parse_identifier()?) + } else { + None + }; + self.expect_keyword_is(Keyword::AS)?; let body = self.parse_conditional_statements(&[Keyword::END])?; @@ -15810,6 +15817,7 @@ impl<'a> Parser<'a> { name, or_alter, params, + language, body, }) } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 3c20f82a..93cc89e5 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -15357,6 +15357,36 @@ fn check_enforced() { ); } +#[test] +fn parse_create_procedure_with_language() { + let sql = r#"CREATE PROCEDURE test_proc LANGUAGE sql AS BEGIN SELECT 1; END"#; + match verified_stmt(sql) { + Statement::CreateProcedure { + or_alter, + name, + params, + language, + .. + } => { + assert_eq!(or_alter, false); + assert_eq!(name.to_string(), "test_proc"); + assert_eq!(params, Some(vec![])); + assert_eq!( + language, + Some(Ident { + value: "sql".into(), + quote_style: None, + span: Span { + start: Location::empty(), + end: Location::empty() + } + }) + ); + } + _ => unreachable!(), + } +} + #[test] fn parse_create_procedure_with_parameter_modes() { let sql = r#"CREATE PROCEDURE test_proc (IN a INTEGER, OUT b TEXT, INOUT c TIMESTAMP, d BOOL) AS BEGIN SELECT 1; END"#; diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 8edb100a..9ec28f42 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -173,7 +173,8 @@ fn parse_create_procedure() { value: "test".into(), quote_style: None, span: Span::empty(), - }]) + }]), + language: None, } ) } From 5f2b5fe7beb0698663f9ba9572c711c6bee9ef6e Mon Sep 17 00:00:00 2001 From: Ifeanyi Ubah Date: Fri, 27 Jun 2025 20:21:17 +0200 Subject: [PATCH 4/6] Fix clippy lints on 1.88.0 (#1910) --- examples/cli.rs | 2 +- sqlparser_bench/benches/sqlparser_bench.rs | 11 ++- src/ast/data_type.rs | 16 ++-- src/ast/dcl.rs | 18 ++-- src/ast/ddl.rs | 28 +++---- src/ast/dml.rs | 12 +-- src/ast/helpers/key_value_options.rs | 2 +- src/ast/mod.rs | 96 +++++++++++----------- src/ast/query.rs | 76 ++++++++--------- src/ast/value.rs | 6 +- src/ast/visitor.rs | 4 +- src/dialect/mod.rs | 2 +- src/dialect/postgresql.rs | 2 +- src/parser/mod.rs | 13 ++- src/test_utils.rs | 2 +- src/tokenizer.rs | 10 +-- tests/sqlparser_clickhouse.rs | 6 +- tests/sqlparser_common.rs | 20 ++--- tests/sqlparser_databricks.rs | 12 +-- tests/sqlparser_duckdb.rs | 10 ++- tests/sqlparser_hive.rs | 4 +- tests/sqlparser_mssql.rs | 10 +-- tests/sqlparser_mysql.rs | 47 ++++++----- tests/sqlparser_postgres.rs | 11 ++- tests/sqlparser_snowflake.rs | 32 ++++---- tests/sqlparser_sqlite.rs | 6 +- 26 files changed, 226 insertions(+), 232 deletions(-) diff --git a/examples/cli.rs b/examples/cli.rs index 0252fca7..08a40a6d 100644 --- a/examples/cli.rs +++ b/examples/cli.rs @@ -63,7 +63,7 @@ $ cargo run --example cli - [--dialectname] }; let contents = if filename == "-" { - println!("Parsing from stdin using {:?}", dialect); + println!("Parsing from stdin using {dialect:?}"); let mut buf = Vec::new(); stdin() .read_to_end(&mut buf) diff --git a/sqlparser_bench/benches/sqlparser_bench.rs b/sqlparser_bench/benches/sqlparser_bench.rs index 24c59c07..6132ee43 100644 --- a/sqlparser_bench/benches/sqlparser_bench.rs +++ b/sqlparser_bench/benches/sqlparser_bench.rs @@ -45,25 +45,24 @@ fn basic_queries(c: &mut Criterion) { let large_statement = { let expressions = (0..1000) - .map(|n| format!("FN_{}(COL_{})", n, n)) + .map(|n| format!("FN_{n}(COL_{n})")) .collect::>() .join(", "); let tables = (0..1000) - .map(|n| format!("TABLE_{}", n)) + .map(|n| format!("TABLE_{n}")) .collect::>() .join(" JOIN "); let where_condition = (0..1000) - .map(|n| format!("COL_{} = {}", n, n)) + .map(|n| format!("COL_{n} = {n}")) .collect::>() .join(" OR "); let order_condition = (0..1000) - .map(|n| format!("COL_{} DESC", n)) + .map(|n| format!("COL_{n} DESC")) .collect::>() .join(", "); format!( - "SELECT {} FROM {} WHERE {} ORDER BY {}", - expressions, tables, where_condition, order_condition + "SELECT {expressions} FROM {tables} WHERE {where_condition} ORDER BY {order_condition}" ) }; diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index ef04b7e4..0897f2db 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -666,7 +666,7 @@ impl fmt::Display for DataType { } DataType::Enum(vals, bits) => { match bits { - Some(bits) => write!(f, "ENUM{}", bits), + Some(bits) => write!(f, "ENUM{bits}"), None => write!(f, "ENUM"), }?; write!(f, "(")?; @@ -714,16 +714,16 @@ impl fmt::Display for DataType { } // ClickHouse DataType::Nullable(data_type) => { - write!(f, "Nullable({})", data_type) + write!(f, "Nullable({data_type})") } DataType::FixedString(character_length) => { - write!(f, "FixedString({})", character_length) + write!(f, "FixedString({character_length})") } DataType::LowCardinality(data_type) => { - write!(f, "LowCardinality({})", data_type) + write!(f, "LowCardinality({data_type})") } DataType::Map(key_data_type, value_data_type) => { - write!(f, "Map({}, {})", key_data_type, value_data_type) + write!(f, "Map({key_data_type}, {value_data_type})") } DataType::Tuple(fields) => { write!(f, "Tuple({})", display_comma_separated(fields)) @@ -745,7 +745,7 @@ impl fmt::Display for DataType { DataType::NamedTable { name, columns } => { write!(f, "{} TABLE ({})", name, display_comma_separated(columns)) } - DataType::GeometricType(kind) => write!(f, "{}", kind), + DataType::GeometricType(kind) => write!(f, "{kind}"), DataType::TsVector => write!(f, "TSVECTOR"), DataType::TsQuery => write!(f, "TSQUERY"), } @@ -942,7 +942,7 @@ impl fmt::Display for CharacterLength { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { CharacterLength::IntegerLength { length, unit } => { - write!(f, "{}", length)?; + write!(f, "{length}")?; if let Some(unit) = unit { write!(f, " {unit}")?; } @@ -997,7 +997,7 @@ impl fmt::Display for BinaryLength { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { BinaryLength::IntegerLength { length } => { - write!(f, "{}", length)?; + write!(f, "{length}")?; } BinaryLength::Max => { write!(f, "MAX")?; diff --git a/src/ast/dcl.rs b/src/ast/dcl.rs index 735ab0cc..07989407 100644 --- a/src/ast/dcl.rs +++ b/src/ast/dcl.rs @@ -173,7 +173,7 @@ impl fmt::Display for AlterRoleOperation { in_database, } => { if let Some(database_name) = in_database { - write!(f, "IN DATABASE {} ", database_name)?; + write!(f, "IN DATABASE {database_name} ")?; } match config_value { @@ -187,7 +187,7 @@ impl fmt::Display for AlterRoleOperation { in_database, } => { if let Some(database_name) = in_database { - write!(f, "IN DATABASE {} ", database_name)?; + write!(f, "IN DATABASE {database_name} ")?; } match config_name { @@ -218,15 +218,15 @@ impl fmt::Display for Use { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { f.write_str("USE ")?; match self { - Use::Catalog(name) => write!(f, "CATALOG {}", name), - Use::Schema(name) => write!(f, "SCHEMA {}", name), - Use::Database(name) => write!(f, "DATABASE {}", name), - Use::Warehouse(name) => write!(f, "WAREHOUSE {}", name), - Use::Role(name) => write!(f, "ROLE {}", name), + Use::Catalog(name) => write!(f, "CATALOG {name}"), + Use::Schema(name) => write!(f, "SCHEMA {name}"), + Use::Database(name) => write!(f, "DATABASE {name}"), + Use::Warehouse(name) => write!(f, "WAREHOUSE {name}"), + Use::Role(name) => write!(f, "ROLE {name}"), Use::SecondaryRoles(secondary_roles) => { - write!(f, "SECONDARY ROLES {}", secondary_roles) + write!(f, "SECONDARY ROLES {secondary_roles}") } - Use::Object(name) => write!(f, "{}", name), + Use::Object(name) => write!(f, "{name}"), Use::Default => write!(f, "DEFAULT"), } } diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index d2863c3a..afe9f228 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -57,7 +57,7 @@ impl fmt::Display for ReplicaIdentity { ReplicaIdentity::None => f.write_str("NONE"), ReplicaIdentity::Full => f.write_str("FULL"), ReplicaIdentity::Default => f.write_str("DEFAULT"), - ReplicaIdentity::Index(idx) => write!(f, "USING INDEX {}", idx), + ReplicaIdentity::Index(idx) => write!(f, "USING INDEX {idx}"), } } } @@ -450,7 +450,7 @@ pub enum Owner { impl fmt::Display for Owner { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Owner::Ident(ident) => write!(f, "{}", ident), + Owner::Ident(ident) => write!(f, "{ident}"), Owner::CurrentRole => write!(f, "CURRENT_ROLE"), Owner::CurrentUser => write!(f, "CURRENT_USER"), Owner::SessionUser => write!(f, "SESSION_USER"), @@ -525,7 +525,7 @@ impl fmt::Display for AlterTableOperation { if *if_not_exists { write!(f, " IF NOT EXISTS")?; } - write!(f, " {} ({})", name, query) + write!(f, " {name} ({query})") } AlterTableOperation::Algorithm { equals, algorithm } => { write!( @@ -540,7 +540,7 @@ impl fmt::Display for AlterTableOperation { if *if_exists { write!(f, " IF EXISTS")?; } - write!(f, " {}", name) + write!(f, " {name}") } AlterTableOperation::MaterializeProjection { if_exists, @@ -551,9 +551,9 @@ impl fmt::Display for AlterTableOperation { if *if_exists { write!(f, " IF EXISTS")?; } - write!(f, " {}", name)?; + write!(f, " {name}")?; if let Some(partition) = partition { - write!(f, " IN PARTITION {}", partition)?; + write!(f, " IN PARTITION {partition}")?; } Ok(()) } @@ -566,9 +566,9 @@ impl fmt::Display for AlterTableOperation { if *if_exists { write!(f, " IF EXISTS")?; } - write!(f, " {}", name)?; + write!(f, " {name}")?; if let Some(partition) = partition { - write!(f, " IN PARTITION {}", partition)?; + write!(f, " IN PARTITION {partition}")?; } Ok(()) } @@ -1168,7 +1168,7 @@ impl fmt::Display for TableConstraint { write!(f, " ON UPDATE {action}")?; } if let Some(characteristics) = characteristics { - write!(f, " {}", characteristics)?; + write!(f, " {characteristics}")?; } Ok(()) } @@ -1308,7 +1308,7 @@ impl fmt::Display for IndexType { Self::SPGiST => write!(f, "SPGIST"), Self::BRIN => write!(f, "BRIN"), Self::Bloom => write!(f, "BLOOM"), - Self::Custom(name) => write!(f, "{}", name), + Self::Custom(name) => write!(f, "{name}"), } } } @@ -1450,7 +1450,7 @@ impl fmt::Display for ViewColumnDef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.name)?; if let Some(data_type) = self.data_type.as_ref() { - write!(f, " {}", data_type)?; + write!(f, " {data_type}")?; } if let Some(options) = self.options.as_ref() { match options { @@ -1845,7 +1845,7 @@ impl fmt::Display for ColumnOption { } => { write!(f, "{}", if *is_primary { "PRIMARY KEY" } else { "UNIQUE" })?; if let Some(characteristics) = characteristics { - write!(f, " {}", characteristics)?; + write!(f, " {characteristics}")?; } Ok(()) } @@ -1867,7 +1867,7 @@ impl fmt::Display for ColumnOption { write!(f, " ON UPDATE {action}")?; } if let Some(characteristics) = characteristics { - write!(f, " {}", characteristics)?; + write!(f, " {characteristics}")?; } Ok(()) } @@ -1927,7 +1927,7 @@ impl fmt::Display for ColumnOption { write!(f, "{parameters}") } OnConflict(keyword) => { - write!(f, "ON CONFLICT {:?}", keyword)?; + write!(f, "ON CONFLICT {keyword:?}")?; Ok(()) } Policy(parameters) => { diff --git a/src/ast/dml.rs b/src/ast/dml.rs index 292650c8..e179f5d7 100644 --- a/src/ast/dml.rs +++ b/src/ast/dml.rs @@ -55,7 +55,7 @@ impl Display for IndexColumn { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.column)?; if let Some(operator_class) = &self.operator_class { - write!(f, " {}", operator_class)?; + write!(f, " {operator_class}")?; } Ok(()) } @@ -266,7 +266,7 @@ impl Display for CreateTable { name = self.name, )?; if let Some(on_cluster) = &self.on_cluster { - write!(f, " ON CLUSTER {}", on_cluster)?; + write!(f, " ON CLUSTER {on_cluster}")?; } if !self.columns.is_empty() || !self.constraints.is_empty() { f.write_str(" (")?; @@ -383,15 +383,15 @@ impl Display for CreateTable { match &self.table_options { options @ CreateTableOptions::With(_) | options @ CreateTableOptions::Plain(_) - | options @ CreateTableOptions::TableProperties(_) => write!(f, " {}", options)?, + | options @ CreateTableOptions::TableProperties(_) => write!(f, " {options}")?, _ => (), } if let Some(primary_key) = &self.primary_key { - write!(f, " PRIMARY KEY {}", primary_key)?; + write!(f, " PRIMARY KEY {primary_key}")?; } if let Some(order_by) = &self.order_by { - write!(f, " ORDER BY {}", order_by)?; + write!(f, " ORDER BY {order_by}")?; } if let Some(inherits) = &self.inherits { write!(f, " INHERITS ({})", display_comma_separated(inherits))?; @@ -403,7 +403,7 @@ impl Display for CreateTable { write!(f, " CLUSTER BY {cluster_by}")?; } if let options @ CreateTableOptions::Options(_) = &self.table_options { - write!(f, " {}", options)?; + write!(f, " {options}")?; } if let Some(external_volume) = self.external_volume.as_ref() { write!(f, " EXTERNAL_VOLUME = '{external_volume}'")?; diff --git a/src/ast/helpers/key_value_options.rs b/src/ast/helpers/key_value_options.rs index 06f028dd..796bfd5e 100644 --- a/src/ast/helpers/key_value_options.rs +++ b/src/ast/helpers/key_value_options.rs @@ -67,7 +67,7 @@ impl fmt::Display for KeyValueOptions { } else { f.write_str(" ")?; } - write!(f, "{}", option)?; + write!(f, "{option}")?; } } Ok(()) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 1ea0b5c2..0f682062 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -357,7 +357,7 @@ impl ObjectNamePart { impl fmt::Display for ObjectNamePart { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - ObjectNamePart::Identifier(ident) => write!(f, "{}", ident), + ObjectNamePart::Identifier(ident) => write!(f, "{ident}"), } } } @@ -1210,8 +1210,8 @@ pub enum AccessExpr { impl fmt::Display for AccessExpr { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - AccessExpr::Dot(expr) => write!(f, ".{}", expr), - AccessExpr::Subscript(subscript) => write!(f, "[{}]", subscript), + AccessExpr::Dot(expr) => write!(f, ".{expr}"), + AccessExpr::Subscript(subscript) => write!(f, "[{subscript}]"), } } } @@ -1413,12 +1413,12 @@ impl fmt::Display for Expr { match self { Expr::Identifier(s) => write!(f, "{s}"), Expr::Wildcard(_) => f.write_str("*"), - Expr::QualifiedWildcard(prefix, _) => write!(f, "{}.*", prefix), + Expr::QualifiedWildcard(prefix, _) => write!(f, "{prefix}.*"), Expr::CompoundIdentifier(s) => write!(f, "{}", display_separated(s, ".")), Expr::CompoundFieldAccess { root, access_chain } => { - write!(f, "{}", root)?; + write!(f, "{root}")?; for field in access_chain { - write!(f, "{}", field)?; + write!(f, "{field}")?; } Ok(()) } @@ -1547,7 +1547,7 @@ impl fmt::Display for Expr { } => { let not_ = if *negated { "NOT " } else { "" }; if form.is_none() { - write!(f, "{} IS {}NORMALIZED", expr, not_) + write!(f, "{expr} IS {not_}NORMALIZED") } else { write!( f, @@ -1869,7 +1869,7 @@ impl fmt::Display for Expr { } } Expr::Named { expr, name } => { - write!(f, "{} AS {}", expr, name) + write!(f, "{expr} AS {name}") } Expr::Dictionary(fields) => { write!(f, "{{{}}}", display_comma_separated(fields)) @@ -2425,7 +2425,7 @@ impl fmt::Display for ConditionalStatements { } Ok(()) } - ConditionalStatements::BeginEnd(bes) => write!(f, "{}", bes), + ConditionalStatements::BeginEnd(bes) => write!(f, "{bes}"), } } } @@ -2945,9 +2945,7 @@ impl Display for Set { write!( f, "SET {modifier}ROLE {role_name}", - modifier = context_modifier - .map(|m| format!("{}", m)) - .unwrap_or_default() + modifier = context_modifier.map(|m| format!("{m}")).unwrap_or_default() ) } Self::SetSessionParam(kind) => write!(f, "SET {kind}"), @@ -2980,7 +2978,7 @@ impl Display for Set { charset_name, collation_name, } => { - write!(f, "SET NAMES {}", charset_name)?; + write!(f, "SET NAMES {charset_name}")?; if let Some(collation) = collation_name { f.write_str(" COLLATE ")?; @@ -3003,7 +3001,7 @@ impl Display for Set { write!( f, "SET {}{}{} = {}", - scope.map(|s| format!("{}", s)).unwrap_or_default(), + scope.map(|s| format!("{s}")).unwrap_or_default(), if *hivevar { "HIVEVAR:" } else { "" }, variable, display_comma_separated(values) @@ -4405,7 +4403,7 @@ impl fmt::Display for Statement { write!(f, "{describe_alias} ")?; if let Some(format) = hive_format { - write!(f, "{} ", format)?; + write!(f, "{format} ")?; } if *has_table_keyword { write!(f, "TABLE ")?; @@ -5241,7 +5239,7 @@ impl fmt::Display for Statement { if *only { write!(f, "ONLY ")?; } - write!(f, "{name} ", name = name)?; + write!(f, "{name} ")?; if let Some(cluster) = on_cluster { write!(f, "ON CLUSTER {cluster} ")?; } @@ -5319,7 +5317,7 @@ impl fmt::Display for Statement { )?; if !session_params.options.is_empty() { if *set { - write!(f, " {}", session_params)?; + write!(f, " {session_params}")?; } else { let options = session_params .options @@ -5353,7 +5351,7 @@ impl fmt::Display for Statement { if *purge { " PURGE" } else { "" }, )?; if let Some(table_name) = table.as_ref() { - write!(f, " ON {}", table_name)?; + write!(f, " ON {table_name}")?; }; Ok(()) } @@ -5608,7 +5606,7 @@ impl fmt::Display for Statement { } => { if *syntax_begin { if let Some(modifier) = *modifier { - write!(f, "BEGIN {}", modifier)?; + write!(f, "BEGIN {modifier}")?; } else { write!(f, "BEGIN")?; } @@ -5644,7 +5642,7 @@ impl fmt::Display for Statement { if *end_syntax { write!(f, "END")?; if let Some(modifier) = *modifier { - write!(f, " {}", modifier)?; + write!(f, " {modifier}")?; } if *chain { write!(f, " AND CHAIN")?; @@ -5743,7 +5741,7 @@ impl fmt::Display for Statement { write!(f, " GRANTED BY {grantor}")?; } if let Some(cascade) = cascade { - write!(f, " {}", cascade)?; + write!(f, " {cascade}")?; } Ok(()) } @@ -5922,13 +5920,13 @@ impl fmt::Display for Statement { if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" }, )?; if !directory_table_params.options.is_empty() { - write!(f, " DIRECTORY=({})", directory_table_params)?; + write!(f, " DIRECTORY=({directory_table_params})")?; } if !file_format.options.is_empty() { - write!(f, " FILE_FORMAT=({})", file_format)?; + write!(f, " FILE_FORMAT=({file_format})")?; } if !copy_options.options.is_empty() { - write!(f, " COPY_OPTIONS=({})", copy_options)?; + write!(f, " COPY_OPTIONS=({copy_options})")?; } if comment.is_some() { write!(f, " COMMENT='{}'", comment.as_ref().unwrap())?; @@ -5951,7 +5949,7 @@ impl fmt::Display for Statement { validation_mode, partition, } => { - write!(f, "COPY INTO {}", into)?; + write!(f, "COPY INTO {into}")?; if let Some(into_columns) = into_columns { write!(f, " ({})", display_comma_separated(into_columns))?; } @@ -5967,12 +5965,12 @@ impl fmt::Display for Statement { )?; } if let Some(from_obj_alias) = from_obj_alias { - write!(f, " AS {}", from_obj_alias)?; + write!(f, " AS {from_obj_alias}")?; } write!(f, ")")?; } else if let Some(from_obj) = from_obj { // Standard data load - write!(f, " FROM {}{}", from_obj, stage_params)?; + write!(f, " FROM {from_obj}{stage_params}")?; if let Some(from_obj_alias) = from_obj_alias { write!(f, " AS {from_obj_alias}")?; } @@ -5985,24 +5983,24 @@ impl fmt::Display for Statement { write!(f, " FILES = ('{}')", display_separated(files, "', '"))?; } if let Some(pattern) = pattern { - write!(f, " PATTERN = '{}'", pattern)?; + write!(f, " PATTERN = '{pattern}'")?; } if let Some(partition) = partition { write!(f, " PARTITION BY {partition}")?; } if !file_format.options.is_empty() { - write!(f, " FILE_FORMAT=({})", file_format)?; + write!(f, " FILE_FORMAT=({file_format})")?; } if !copy_options.options.is_empty() { match kind { CopyIntoSnowflakeKind::Table => { - write!(f, " COPY_OPTIONS=({})", copy_options)? + write!(f, " COPY_OPTIONS=({copy_options})")? } CopyIntoSnowflakeKind::Location => write!(f, " {copy_options}")?, } } if let Some(validation_mode) = validation_mode { - write!(f, " VALIDATION_MODE = {}", validation_mode)?; + write!(f, " VALIDATION_MODE = {validation_mode}")?; } Ok(()) } @@ -6048,10 +6046,10 @@ impl fmt::Display for Statement { } => { write!(f, "OPTIMIZE TABLE {name}")?; if let Some(on_cluster) = on_cluster { - write!(f, " ON CLUSTER {on_cluster}", on_cluster = on_cluster)?; + write!(f, " ON CLUSTER {on_cluster}")?; } if let Some(partition) = partition { - write!(f, " {partition}", partition = partition)?; + write!(f, " {partition}")?; } if *include_final { write!(f, " FINAL")?; @@ -6178,7 +6176,7 @@ impl fmt::Display for SetAssignment { write!( f, "{}{} = {}", - self.scope.map(|s| format!("{}", s)).unwrap_or_default(), + self.scope.map(|s| format!("{s}")).unwrap_or_default(), self.name, self.value ) @@ -6907,7 +6905,7 @@ impl fmt::Display for GranteeName { match self { GranteeName::ObjectName(name) => name.fmt(f), GranteeName::UserHost { user, host } => { - write!(f, "{}@{}", user, host) + write!(f, "{user}@{host}") } } } @@ -7077,7 +7075,7 @@ pub enum AssignmentTarget { impl fmt::Display for AssignmentTarget { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - AssignmentTarget::ColumnName(column) => write!(f, "{}", column), + AssignmentTarget::ColumnName(column) => write!(f, "{column}"), AssignmentTarget::Tuple(columns) => write!(f, "({})", display_comma_separated(columns)), } } @@ -7322,8 +7320,8 @@ impl fmt::Display for FunctionArguments { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { FunctionArguments::None => Ok(()), - FunctionArguments::Subquery(query) => write!(f, "({})", query), - FunctionArguments::List(args) => write!(f, "({})", args), + FunctionArguments::Subquery(query) => write!(f, "({query})"), + FunctionArguments::List(args) => write!(f, "({args})"), } } } @@ -7344,7 +7342,7 @@ pub struct FunctionArgumentList { impl fmt::Display for FunctionArgumentList { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { if let Some(duplicate_treatment) = self.duplicate_treatment { - write!(f, "{} ", duplicate_treatment)?; + write!(f, "{duplicate_treatment} ")?; } write!(f, "{}", display_comma_separated(&self.args))?; if !self.clauses.is_empty() { @@ -7404,7 +7402,7 @@ impl fmt::Display for FunctionArgumentClause { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { FunctionArgumentClause::IgnoreOrRespectNulls(null_treatment) => { - write!(f, "{}", null_treatment) + write!(f, "{null_treatment}") } FunctionArgumentClause::OrderBy(order_by) => { write!(f, "ORDER BY {}", display_comma_separated(order_by)) @@ -7860,12 +7858,12 @@ pub enum SqlOption { impl fmt::Display for SqlOption { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - SqlOption::Clustered(c) => write!(f, "{}", c), + SqlOption::Clustered(c) => write!(f, "{c}"), SqlOption::Ident(ident) => { - write!(f, "{}", ident) + write!(f, "{ident}") } SqlOption::KeyValue { key: name, value } => { - write!(f, "{} = {}", name, value) + write!(f, "{name} = {value}") } SqlOption::Partition { column_name, @@ -7905,7 +7903,7 @@ impl fmt::Display for SqlOption { SqlOption::NamedParenthesizedList(value) => { write!(f, "{} = ", value.key)?; if let Some(key) = &value.name { - write!(f, "{}", key)?; + write!(f, "{key}")?; } if !value.values.is_empty() { write!(f, "({})", display_comma_separated(&value.values))? @@ -7962,7 +7960,7 @@ impl fmt::Display for AttachDuckDBDatabaseOption { AttachDuckDBDatabaseOption::ReadOnly(Some(true)) => write!(f, "READ_ONLY true"), AttachDuckDBDatabaseOption::ReadOnly(Some(false)) => write!(f, "READ_ONLY false"), AttachDuckDBDatabaseOption::ReadOnly(None) => write!(f, "READ_ONLY"), - AttachDuckDBDatabaseOption::Type(t) => write!(f, "TYPE {}", t), + AttachDuckDBDatabaseOption::Type(t) => write!(f, "TYPE {t}"), } } } @@ -9485,10 +9483,10 @@ impl fmt::Display for ShowStatementIn { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.clause)?; if let Some(parent_type) = &self.parent_type { - write!(f, " {}", parent_type)?; + write!(f, " {parent_type}")?; } if let Some(parent_name) = &self.parent_name { - write!(f, " {}", parent_name)?; + write!(f, " {parent_name}")?; } Ok(()) } @@ -9569,7 +9567,7 @@ impl fmt::Display for TableObject { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { Self::TableName(table_name) => write!(f, "{table_name}"), - Self::TableFunction(func) => write!(f, "FUNCTION {}", func), + Self::TableFunction(func) => write!(f, "FUNCTION {func}"), } } } @@ -9757,7 +9755,7 @@ pub struct ReturnStatement { impl fmt::Display for ReturnStatement { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self.value { - Some(ReturnStatementValue::Expr(expr)) => write!(f, "RETURN {}", expr), + Some(ReturnStatementValue::Expr(expr)) => write!(f, "RETURN {expr}"), None => write!(f, "RETURN"), } } diff --git a/src/ast/query.rs b/src/ast/query.rs index 1fb93b6c..c79ec110 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -1183,7 +1183,7 @@ impl fmt::Display for TableIndexHints { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{} {} ", self.hint_type, self.index_type)?; if let Some(for_clause) = &self.for_clause { - write!(f, "FOR {} ", for_clause)?; + write!(f, "FOR {for_clause} ")?; } write!(f, "({})", display_comma_separated(&self.index_names)) } @@ -1459,7 +1459,7 @@ impl fmt::Display for TableSampleQuantity { } write!(f, "{}", self.value)?; if let Some(unit) = &self.unit { - write!(f, " {}", unit)?; + write!(f, " {unit}")?; } if self.parenthesized { write!(f, ")")?; @@ -1552,7 +1552,7 @@ impl fmt::Display for TableSampleBucket { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "BUCKET {} OUT OF {}", self.bucket, self.total)?; if let Some(on) = &self.on { - write!(f, " ON {}", on)?; + write!(f, " ON {on}")?; } Ok(()) } @@ -1561,19 +1561,19 @@ impl fmt::Display for TableSample { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { write!(f, "{}", self.modifier)?; if let Some(name) = &self.name { - write!(f, " {}", name)?; + write!(f, " {name}")?; } if let Some(quantity) = &self.quantity { - write!(f, " {}", quantity)?; + write!(f, " {quantity}")?; } if let Some(seed) = &self.seed { - write!(f, " {}", seed)?; + write!(f, " {seed}")?; } if let Some(bucket) = &self.bucket { - write!(f, " ({})", bucket)?; + write!(f, " ({bucket})")?; } if let Some(offset) = &self.offset { - write!(f, " OFFSET {}", offset)?; + write!(f, " OFFSET {offset}")?; } Ok(()) } @@ -1651,7 +1651,7 @@ impl fmt::Display for RowsPerMatch { RowsPerMatch::AllRows(mode) => { write!(f, "ALL ROWS PER MATCH")?; if let Some(mode) = mode { - write!(f, " {}", mode)?; + write!(f, " {mode}")?; } Ok(()) } @@ -1777,7 +1777,7 @@ impl fmt::Display for MatchRecognizePattern { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { use MatchRecognizePattern::*; match self { - Symbol(symbol) => write!(f, "{}", symbol), + Symbol(symbol) => write!(f, "{symbol}"), Exclude(symbol) => write!(f, "{{- {symbol} -}}"), Permute(symbols) => write!(f, "PERMUTE({})", display_comma_separated(symbols)), Concat(patterns) => write!(f, "{}", display_separated(patterns, " ")), @@ -2148,7 +2148,7 @@ impl fmt::Display for TableAliasColumnDef { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.name)?; if let Some(ref data_type) = self.data_type { - write!(f, " {}", data_type)?; + write!(f, " {data_type}")?; } Ok(()) } @@ -2398,7 +2398,7 @@ impl fmt::Display for OrderBy { write!(f, " {}", display_comma_separated(exprs))?; } OrderByKind::All(all) => { - write!(f, " ALL{}", all)?; + write!(f, " ALL{all}")?; } } @@ -2429,7 +2429,7 @@ impl fmt::Display for OrderByExpr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}{}", self.expr, self.options)?; if let Some(ref with_fill) = self.with_fill { - write!(f, " {}", with_fill)? + write!(f, " {with_fill}")? } Ok(()) } @@ -2452,13 +2452,13 @@ impl fmt::Display for WithFill { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "WITH FILL")?; if let Some(ref from) = self.from { - write!(f, " FROM {}", from)?; + write!(f, " FROM {from}")?; } if let Some(ref to) = self.to { - write!(f, " TO {}", to)?; + write!(f, " TO {to}")?; } if let Some(ref step) = self.step { - write!(f, " STEP {}", step)?; + write!(f, " STEP {step}")?; } Ok(()) } @@ -2487,7 +2487,7 @@ impl fmt::Display for InterpolateExpr { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!(f, "{}", self.column)?; if let Some(ref expr) = self.expr { - write!(f, " AS {}", expr)?; + write!(f, " AS {expr}")?; } Ok(()) } @@ -2565,7 +2565,7 @@ impl fmt::Display for LimitClause { Ok(()) } LimitClause::OffsetCommaLimit { offset, limit } => { - write!(f, " LIMIT {}, {}", offset, limit) + write!(f, " LIMIT {offset}, {limit}") } } } @@ -2702,12 +2702,12 @@ impl fmt::Display for PipeOperator { write!(f, "DROP {}", display_comma_separated(columns.as_slice())) } PipeOperator::As { alias } => { - write!(f, "AS {}", alias) + write!(f, "AS {alias}") } PipeOperator::Limit { expr, offset } => { - write!(f, "LIMIT {}", expr)?; + write!(f, "LIMIT {expr}")?; if let Some(offset) = offset { - write!(f, " OFFSET {}", offset)?; + write!(f, " OFFSET {offset}")?; } Ok(()) } @@ -2730,14 +2730,14 @@ impl fmt::Display for PipeOperator { } PipeOperator::Where { expr } => { - write!(f, "WHERE {}", expr) + write!(f, "WHERE {expr}") } PipeOperator::OrderBy { exprs } => { write!(f, "ORDER BY {}", display_comma_separated(exprs.as_slice())) } PipeOperator::TableSample { sample } => { - write!(f, "{}", sample) + write!(f, "{sample}") } } } @@ -3016,7 +3016,7 @@ pub enum FormatClause { impl fmt::Display for FormatClause { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - FormatClause::Identifier(ident) => write!(f, "FORMAT {}", ident), + FormatClause::Identifier(ident) => write!(f, "FORMAT {ident}"), FormatClause::Null => write!(f, "FORMAT NULL"), } } @@ -3078,9 +3078,9 @@ impl fmt::Display for ForClause { without_array_wrapper, } => { write!(f, "FOR JSON ")?; - write!(f, "{}", for_json)?; + write!(f, "{for_json}")?; if let Some(root) = root { - write!(f, ", ROOT('{}')", root)?; + write!(f, ", ROOT('{root}')")?; } if *include_null_values { write!(f, ", INCLUDE_NULL_VALUES")?; @@ -3098,7 +3098,7 @@ impl fmt::Display for ForClause { r#type, } => { write!(f, "FOR XML ")?; - write!(f, "{}", for_xml)?; + write!(f, "{for_xml}")?; if *binary_base64 { write!(f, ", BINARY BASE64")?; } @@ -3106,7 +3106,7 @@ impl fmt::Display for ForClause { write!(f, ", TYPE")?; } if let Some(root) = root { - write!(f, ", ROOT('{}')", root)?; + write!(f, ", ROOT('{root}')")?; } if *elements { write!(f, ", ELEMENTS")?; @@ -3133,7 +3133,7 @@ impl fmt::Display for ForXml { ForXml::Raw(root) => { write!(f, "RAW")?; if let Some(root) = root { - write!(f, "('{}')", root)?; + write!(f, "('{root}')")?; } Ok(()) } @@ -3142,7 +3142,7 @@ impl fmt::Display for ForXml { ForXml::Path(root) => { write!(f, "PATH")?; if let Some(root) = root { - write!(f, "('{}')", root)?; + write!(f, "('{root}')")?; } Ok(()) } @@ -3205,7 +3205,7 @@ impl fmt::Display for JsonTableColumn { JsonTableColumn::Named(json_table_named_column) => { write!(f, "{json_table_named_column}") } - JsonTableColumn::ForOrdinality(ident) => write!(f, "{} FOR ORDINALITY", ident), + JsonTableColumn::ForOrdinality(ident) => write!(f, "{ident} FOR ORDINALITY"), JsonTableColumn::Nested(json_table_nested_column) => { write!(f, "{json_table_nested_column}") } @@ -3271,10 +3271,10 @@ impl fmt::Display for JsonTableNamedColumn { self.path )?; if let Some(on_empty) = &self.on_empty { - write!(f, " {} ON EMPTY", on_empty)?; + write!(f, " {on_empty} ON EMPTY")?; } if let Some(on_error) = &self.on_error { - write!(f, " {} ON ERROR", on_error)?; + write!(f, " {on_error} ON ERROR")?; } Ok(()) } @@ -3296,7 +3296,7 @@ impl fmt::Display for JsonTableColumnErrorHandling { match self { JsonTableColumnErrorHandling::Null => write!(f, "NULL"), JsonTableColumnErrorHandling::Default(json_string) => { - write!(f, "DEFAULT {}", json_string) + write!(f, "DEFAULT {json_string}") } JsonTableColumnErrorHandling::Error => write!(f, "ERROR"), } @@ -3429,12 +3429,12 @@ impl fmt::Display for XmlTableColumn { default, nullable, } => { - write!(f, " {}", r#type)?; + write!(f, " {type}")?; if let Some(p) = path { - write!(f, " PATH {}", p)?; + write!(f, " PATH {p}")?; } if let Some(d) = default { - write!(f, " DEFAULT {}", d)?; + write!(f, " DEFAULT {d}")?; } if !*nullable { write!(f, " NOT NULL")?; @@ -3465,7 +3465,7 @@ impl fmt::Display for XmlPassingArgument { } write!(f, "{}", self.expr)?; if let Some(alias) = &self.alias { - write!(f, " AS {}", alias)?; + write!(f, " AS {alias}")?; } Ok(()) } diff --git a/src/ast/value.rs b/src/ast/value.rs index 98616407..90dbccbf 100644 --- a/src/ast/value.rs +++ b/src/ast/value.rs @@ -551,16 +551,16 @@ impl fmt::Display for EscapeUnicodeStringLiteral<'_> { write!(f, r#"\\"#)?; } x if x.is_ascii() => { - write!(f, "{}", c)?; + write!(f, "{c}")?; } _ => { let codepoint = c as u32; // if the character fits in 32 bits, we can use the \XXXX format // otherwise, we need to use the \+XXXXXX format if codepoint <= 0xFFFF { - write!(f, "\\{:04X}", codepoint)?; + write!(f, "\\{codepoint:04X}")?; } else { - write!(f, "\\+{:06X}", codepoint)?; + write!(f, "\\+{codepoint:06X}")?; } } } diff --git a/src/ast/visitor.rs b/src/ast/visitor.rs index ab4f73aa..8e0a3139 100644 --- a/src/ast/visitor.rs +++ b/src/ast/visitor.rs @@ -926,10 +926,10 @@ mod tests { #[test] fn overflow() { let cond = (0..1000) - .map(|n| format!("X = {}", n)) + .map(|n| format!("X = {n}")) .collect::>() .join(" OR "); - let sql = format!("SELECT x where {0}", cond); + let sql = format!("SELECT x where {cond}"); let dialect = GenericDialect {}; let tokens = Tokenizer::new(&dialect, sql.as_str()).tokenize().unwrap(); diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index bc92948d..c79b4517 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -587,7 +587,7 @@ pub trait Dialect: Debug + Any { } let token = parser.peek_token(); - debug!("get_next_precedence_full() {:?}", token); + debug!("get_next_precedence_full() {token:?}"); match token.token { Token::Word(w) if w.keyword == Keyword::OR => Ok(p!(Or)), Token::Word(w) if w.keyword == Keyword::AND => Ok(p!(And)), diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index 9b08b8f3..a91ab598 100644 --- a/src/dialect/postgresql.rs +++ b/src/dialect/postgresql.rs @@ -104,7 +104,7 @@ impl Dialect for PostgreSqlDialect { fn get_next_precedence(&self, parser: &Parser) -> Option> { let token = parser.peek_token(); - debug!("get_next_precedence() {:?}", token); + debug!("get_next_precedence() {token:?}"); // we only return some custom value here when the behaviour (not merely the numeric value) differs // from the default implementation diff --git a/src/parser/mod.rs b/src/parser/mod.rs index e09b7e33..f1b09afb 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -436,7 +436,7 @@ impl<'a> Parser<'a> { /// /// See example on [`Parser::new()`] for an example pub fn try_with_sql(self, sql: &str) -> Result { - debug!("Parsing sql '{}'...", sql); + debug!("Parsing sql '{sql}'..."); let tokens = Tokenizer::new(self.dialect, sql) .with_unescape(self.options.unescape) .tokenize_with_location()?; @@ -1226,10 +1226,10 @@ impl<'a> Parser<'a> { expr = self.parse_compound_expr(expr, vec![])?; - debug!("prefix: {:?}", expr); + debug!("prefix: {expr:?}"); loop { let next_precedence = self.get_next_precedence()?; - debug!("next precedence: {:?}", next_precedence); + debug!("next precedence: {next_precedence:?}"); if precedence >= next_precedence { break; @@ -1631,8 +1631,7 @@ impl<'a> Parser<'a> { Token::QuestionPipe => UnaryOperator::QuestionPipe, _ => { return Err(ParserError::ParserError(format!( - "Unexpected token in unary operator parsing: {:?}", - tok + "Unexpected token in unary operator parsing: {tok:?}" ))) } }; @@ -13655,7 +13654,7 @@ impl<'a> Parser<'a> { let ident = self.parse_identifier()?; if let GranteeName::ObjectName(namespace) = name { name = GranteeName::ObjectName(ObjectName::from(vec![Ident::new( - format!("{}:{}", namespace, ident), + format!("{namespace}:{ident}"), )])); }; } @@ -14625,7 +14624,7 @@ impl<'a> Parser<'a> { self.dialect .get_reserved_keywords_for_select_item_operator(), ) - .map(|keyword| Ident::new(format!("{:?}", keyword))); + .map(|keyword| Ident::new(format!("{keyword:?}"))); match self.parse_wildcard_expr()? { Expr::QualifiedWildcard(prefix, token) => Ok(SelectItem::QualifiedWildcard( diff --git a/src/test_utils.rs b/src/test_utils.rs index 1af9f454..c7965c3f 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -270,7 +270,7 @@ impl TestedDialects { tokenizer = tokenizer.with_unescape(options.unescape); } let tokens = tokenizer.tokenize().unwrap(); - assert_eq!(expected, tokens, "Tokenized differently for {:?}", dialect); + assert_eq!(expected, tokens, "Tokenized differently for {dialect:?}"); }); } } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index afe1e35c..8382a534 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -1751,7 +1751,7 @@ impl<'a> Tokenizer<'a> { (None, Some(tok)) => Ok(Some(tok)), (None, None) => self.tokenizer_error( chars.location(), - format!("Expected a valid binary operator after '{}'", prefix), + format!("Expected a valid binary operator after '{prefix}'"), ), } } @@ -1809,7 +1809,7 @@ impl<'a> Tokenizer<'a> { chars.next(); let mut temp = String::new(); - let end_delimiter = format!("${}$", value); + let end_delimiter = format!("${value}$"); loop { match chars.next() { @@ -2402,13 +2402,13 @@ fn take_char_from_hex_digits( location: chars.location(), })?; let digit = next_char.to_digit(16).ok_or_else(|| TokenizerError { - message: format!("Invalid hex digit in escaped unicode string: {}", next_char), + message: format!("Invalid hex digit in escaped unicode string: {next_char}"), location: chars.location(), })?; result = result * 16 + digit; } char::from_u32(result).ok_or_else(|| TokenizerError { - message: format!("Invalid unicode character: {:x}", result), + message: format!("Invalid unicode character: {result:x}"), location: chars.location(), }) } @@ -3504,7 +3504,7 @@ mod tests { } fn check_unescape(s: &str, expected: Option<&str>) { - let s = format!("'{}'", s); + let s = format!("'{s}'"); let mut state = State { peekable: s.chars().peekable(), line: 0, diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index ed5d7ad2..0a60c9c4 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -1345,7 +1345,7 @@ fn parse_use() { for object_name in &valid_object_names { // Test single identifier without quotes assert_eq!( - clickhouse().verified_stmt(&format!("USE {}", object_name)), + clickhouse().verified_stmt(&format!("USE {object_name}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::new( object_name.to_string() )]))) @@ -1353,7 +1353,7 @@ fn parse_use() { for "e in "e_styles { // Test single identifier with different type of quotes assert_eq!( - clickhouse().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)), + clickhouse().verified_stmt(&format!("USE {quote}{object_name}{quote}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote( quote, object_name.to_string(), @@ -1367,7 +1367,7 @@ fn parse_use() { fn test_query_with_format_clause() { let format_options = vec!["TabSeparated", "JSONCompact", "NULL"]; for format in &format_options { - let sql = format!("SELECT * FROM t FORMAT {}", format); + let sql = format!("SELECT * FROM t FORMAT {format}"); match clickhouse_and_generic().verified_stmt(&sql) { Statement::Query(query) => { if *format == "NULL" { diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 93cc89e5..6c4ed08c 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -3563,7 +3563,7 @@ fn test_double_value() { for (input, expected) in test_cases { for (i, expr) in input.iter().enumerate() { if let Statement::Query(query) = - dialects.one_statement_parses_to(&format!("SELECT {}", expr), "") + dialects.one_statement_parses_to(&format!("SELECT {expr}"), "") { if let SetExpr::Select(select) = *query.body { assert_eq!(expected[i], select.projection[0]); @@ -4023,13 +4023,13 @@ fn parse_create_table_column_constraint_characteristics() { syntax }; - let sql = format!("CREATE TABLE t (a int UNIQUE {})", syntax); + let sql = format!("CREATE TABLE t (a int UNIQUE {syntax})"); let expected_clause = if syntax.is_empty() { String::new() } else { format!(" {syntax}") }; - let expected = format!("CREATE TABLE t (a INT UNIQUE{})", expected_clause); + let expected = format!("CREATE TABLE t (a INT UNIQUE{expected_clause})"); let ast = one_statement_parses_to(&sql, &expected); let expected_value = if deferrable.is_some() || initially.is_some() || enforced.is_some() { @@ -7499,7 +7499,7 @@ fn parse_cte_in_data_modification_statements() { assert_eq!(query.with.unwrap().to_string(), "WITH x AS (SELECT 1)"); assert!(matches!(*query.body, SetExpr::Update(_))); } - other => panic!("Expected: UPDATE, got: {:?}", other), + other => panic!("Expected: UPDATE, got: {other:?}"), } match verified_stmt("WITH t (x) AS (SELECT 9) DELETE FROM q WHERE id IN (SELECT x FROM t)") { @@ -7507,7 +7507,7 @@ fn parse_cte_in_data_modification_statements() { assert_eq!(query.with.unwrap().to_string(), "WITH t (x) AS (SELECT 9)"); assert!(matches!(*query.body, SetExpr::Delete(_))); } - other => panic!("Expected: DELETE, got: {:?}", other), + other => panic!("Expected: DELETE, got: {other:?}"), } match verified_stmt("WITH x AS (SELECT 42) INSERT INTO t SELECT foo FROM x") { @@ -7515,7 +7515,7 @@ fn parse_cte_in_data_modification_statements() { assert_eq!(query.with.unwrap().to_string(), "WITH x AS (SELECT 42)"); assert!(matches!(*query.body, SetExpr::Insert(_))); } - other => panic!("Expected: INSERT, got: {:?}", other), + other => panic!("Expected: INSERT, got: {other:?}"), } } @@ -10043,7 +10043,7 @@ fn parse_offset_and_limit() { #[test] fn parse_time_functions() { fn test_time_function(func_name: &'static str) { - let sql = format!("SELECT {}()", func_name); + let sql = format!("SELECT {func_name}()"); let select = verified_only_select(&sql); let select_localtime_func_call_ast = Function { name: ObjectName::from(vec![Ident::new(func_name)]), @@ -10065,7 +10065,7 @@ fn parse_time_functions() { ); // Validating Parenthesis - let sql_without_parens = format!("SELECT {}", func_name); + let sql_without_parens = format!("SELECT {func_name}"); let mut ast_without_parens = select_localtime_func_call_ast; ast_without_parens.args = FunctionArguments::None; assert_eq!( @@ -14306,7 +14306,7 @@ fn overflow() { let expr = std::iter::repeat_n("1", 1000) .collect::>() .join(" + "); - let sql = format!("SELECT {}", expr); + let sql = format!("SELECT {expr}"); let mut statements = Parser::parse_sql(&GenericDialect {}, sql.as_str()).unwrap(); let statement = statements.pop().unwrap(); @@ -14606,7 +14606,7 @@ fn test_conditional_statement_span() { else_block.unwrap().span() ); } - stmt => panic!("Unexpected statement: {:?}", stmt), + stmt => panic!("Unexpected statement: {stmt:?}"), } } diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs index 99b7eecd..baf279fa 100644 --- a/tests/sqlparser_databricks.rs +++ b/tests/sqlparser_databricks.rs @@ -213,7 +213,7 @@ fn parse_use() { for object_name in &valid_object_names { // Test single identifier without quotes assert_eq!( - databricks().verified_stmt(&format!("USE {}", object_name)), + databricks().verified_stmt(&format!("USE {object_name}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::new( object_name.to_string() )]))) @@ -221,7 +221,7 @@ fn parse_use() { for "e in "e_styles { // Test single identifier with different type of quotes assert_eq!( - databricks().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)), + databricks().verified_stmt(&format!("USE {quote}{object_name}{quote}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote( quote, object_name.to_string(), @@ -233,21 +233,21 @@ fn parse_use() { for "e in "e_styles { // Test single identifier with keyword and different type of quotes assert_eq!( - databricks().verified_stmt(&format!("USE CATALOG {0}my_catalog{0}", quote)), + databricks().verified_stmt(&format!("USE CATALOG {quote}my_catalog{quote}")), Statement::Use(Use::Catalog(ObjectName::from(vec![Ident::with_quote( quote, "my_catalog".to_string(), )]))) ); assert_eq!( - databricks().verified_stmt(&format!("USE DATABASE {0}my_database{0}", quote)), + databricks().verified_stmt(&format!("USE DATABASE {quote}my_database{quote}")), Statement::Use(Use::Database(ObjectName::from(vec![Ident::with_quote( quote, "my_database".to_string(), )]))) ); assert_eq!( - databricks().verified_stmt(&format!("USE SCHEMA {0}my_schema{0}", quote)), + databricks().verified_stmt(&format!("USE SCHEMA {quote}my_schema{quote}")), Statement::Use(Use::Schema(ObjectName::from(vec![Ident::with_quote( quote, "my_schema".to_string(), @@ -357,6 +357,6 @@ fn data_type_timestamp_ntz() { }] ); } - s => panic!("Unexpected statement: {:?}", s), + s => panic!("Unexpected statement: {s:?}"), } } diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index f503ed55..44cb22ce 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -368,7 +368,7 @@ fn test_duckdb_specific_int_types() { ("HUGEINT", DataType::HugeInt), ]; for (dtype_string, data_type) in duckdb_dtypes { - let sql = format!("SELECT 123::{}", dtype_string); + let sql = format!("SELECT 123::{dtype_string}"); let select = duckdb().verified_only_select(&sql); assert_eq!( &Expr::Cast { @@ -792,7 +792,7 @@ fn parse_use() { for object_name in &valid_object_names { // Test single identifier without quotes assert_eq!( - duckdb().verified_stmt(&format!("USE {}", object_name)), + duckdb().verified_stmt(&format!("USE {object_name}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::new( object_name.to_string() )]))) @@ -800,7 +800,7 @@ fn parse_use() { for "e in "e_styles { // Test single identifier with different type of quotes assert_eq!( - duckdb().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)), + duckdb().verified_stmt(&format!("USE {quote}{object_name}{quote}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote( quote, object_name.to_string(), @@ -812,7 +812,9 @@ fn parse_use() { for "e in "e_styles { // Test double identifier with different type of quotes assert_eq!( - duckdb().verified_stmt(&format!("USE {0}CATALOG{0}.{0}my_schema{0}", quote)), + duckdb().verified_stmt(&format!( + "USE {quote}CATALOG{quote}.{quote}my_schema{quote}" + )), Statement::Use(Use::Object(ObjectName::from(vec![ Ident::with_quote(quote, "CATALOG"), Ident::with_quote(quote, "my_schema") diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index fd52b773..56a72ec8 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs @@ -524,7 +524,7 @@ fn parse_use() { for object_name in &valid_object_names { // Test single identifier without quotes assert_eq!( - hive().verified_stmt(&format!("USE {}", object_name)), + hive().verified_stmt(&format!("USE {object_name}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::new( object_name.to_string() )]))) @@ -532,7 +532,7 @@ fn parse_use() { for "e in "e_styles { // Test single identifier with different type of quotes assert_eq!( - hive().verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)), + hive().verified_stmt(&format!("USE {quote}{object_name}{quote}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote( quote, object_name.to_string(), diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index 9ec28f42..ebbec25f 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -1673,7 +1673,7 @@ fn parse_use() { for object_name in &valid_object_names { // Test single identifier without quotes assert_eq!( - ms().verified_stmt(&format!("USE {}", object_name)), + ms().verified_stmt(&format!("USE {object_name}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::new( object_name.to_string() )]))) @@ -1681,7 +1681,7 @@ fn parse_use() { for "e in "e_styles { // Test single identifier with different type of quotes assert_eq!( - ms().verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)), + ms().verified_stmt(&format!("USE {quote}{object_name}{quote}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote( quote, object_name.to_string(), @@ -2187,7 +2187,7 @@ fn parse_mssql_if_else() { "IF 1 = 1 BEGIN SET @A = 1; END ELSE SET @A = 2;" ); } - _ => panic!("Unexpected statements: {:?}", stmts), + _ => panic!("Unexpected statements: {stmts:?}"), } } @@ -2237,7 +2237,7 @@ fn test_mssql_if_statements_span() { Span::new(Location::new(1, 21), Location::new(1, 36)) ); } - stmt => panic!("Unexpected statement: {:?}", stmt), + stmt => panic!("Unexpected statement: {stmt:?}"), } // Blocks @@ -2258,7 +2258,7 @@ fn test_mssql_if_statements_span() { Span::new(Location::new(1, 32), Location::new(1, 57)) ); } - stmt => panic!("Unexpected statement: {:?}", stmt), + stmt => panic!("Unexpected statement: {stmt:?}"), } } diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index b11d76dd..d2feee03 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -593,7 +593,7 @@ fn parse_use() { for object_name in &valid_object_names { // Test single identifier without quotes assert_eq!( - mysql_and_generic().verified_stmt(&format!("USE {}", object_name)), + mysql_and_generic().verified_stmt(&format!("USE {object_name}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::new( object_name.to_string() )]))) @@ -601,8 +601,7 @@ fn parse_use() { for "e in "e_styles { // Test single identifier with different type of quotes assert_eq!( - mysql_and_generic() - .verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)), + mysql_and_generic().verified_stmt(&format!("USE {quote}{object_name}{quote}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote( quote, object_name.to_string(), @@ -2263,11 +2262,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() { Some(SelectItem::UnnamedExpr(Expr::CompoundIdentifier(parts))) => { assert_eq!(&[Ident::new("t"), Ident::new("15to29")], &parts[..]); } - proj => panic!("Unexpected projection: {:?}", proj), + proj => panic!("Unexpected projection: {proj:?}"), }, - body => panic!("Unexpected statement body: {:?}", body), + body => panic!("Unexpected statement body: {body:?}"), }, - stmt => panic!("Unexpected statement: {:?}", stmt), + stmt => panic!("Unexpected statement: {stmt:?}"), } // Case 2: Qualified column name that starts with digits and on its own represents a number. @@ -2277,11 +2276,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() { Some(SelectItem::UnnamedExpr(Expr::CompoundIdentifier(parts))) => { assert_eq!(&[Ident::new("t"), Ident::new("15e29")], &parts[..]); } - proj => panic!("Unexpected projection: {:?}", proj), + proj => panic!("Unexpected projection: {proj:?}"), }, - body => panic!("Unexpected statement body: {:?}", body), + body => panic!("Unexpected statement body: {body:?}"), }, - stmt => panic!("Unexpected statement: {:?}", stmt), + stmt => panic!("Unexpected statement: {stmt:?}"), } // Case 3: Unqualified, the same token is parsed as a number. @@ -2295,11 +2294,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() { Some(SelectItem::UnnamedExpr(Expr::Value(ValueWithSpan { value, .. }))) => { assert_eq!(&number("15e29"), value); } - proj => panic!("Unexpected projection: {:?}", proj), + proj => panic!("Unexpected projection: {proj:?}"), }, - body => panic!("Unexpected statement body: {:?}", body), + body => panic!("Unexpected statement body: {body:?}"), }, - stmt => panic!("Unexpected statement: {:?}", stmt), + stmt => panic!("Unexpected statement: {stmt:?}"), } // Case 4: Quoted simple identifier. @@ -2309,11 +2308,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() { Some(SelectItem::UnnamedExpr(Expr::Identifier(name))) => { assert_eq!(&Ident::with_quote('`', "15e29"), name); } - proj => panic!("Unexpected projection: {:?}", proj), + proj => panic!("Unexpected projection: {proj:?}"), }, - body => panic!("Unexpected statement body: {:?}", body), + body => panic!("Unexpected statement body: {body:?}"), }, - stmt => panic!("Unexpected statement: {:?}", stmt), + stmt => panic!("Unexpected statement: {stmt:?}"), } // Case 5: Quoted compound identifier. @@ -2326,11 +2325,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() { &parts[..] ); } - proj => panic!("Unexpected projection: {:?}", proj), + proj => panic!("Unexpected projection: {proj:?}"), }, - body => panic!("Unexpected statement body: {:?}", body), + body => panic!("Unexpected statement body: {body:?}"), }, - stmt => panic!("Unexpected statement: {:?}", stmt), + stmt => panic!("Unexpected statement: {stmt:?}"), } // Case 6: Multi-level compound identifiers. @@ -2347,11 +2346,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() { &parts[..] ); } - proj => panic!("Unexpected projection: {:?}", proj), + proj => panic!("Unexpected projection: {proj:?}"), }, - body => panic!("Unexpected statement body: {:?}", body), + body => panic!("Unexpected statement body: {body:?}"), }, - stmt => panic!("Unexpected statement: {:?}", stmt), + stmt => panic!("Unexpected statement: {stmt:?}"), } // Case 7: Multi-level compound quoted identifiers. @@ -2368,11 +2367,11 @@ fn parse_qualified_identifiers_with_numeric_prefix() { &parts[..] ); } - proj => panic!("Unexpected projection: {:?}", proj), + proj => panic!("Unexpected projection: {proj:?}"), }, - body => panic!("Unexpected statement body: {:?}", body), + body => panic!("Unexpected statement body: {body:?}"), }, - stmt => panic!("Unexpected statement: {:?}", stmt), + stmt => panic!("Unexpected statement: {stmt:?}"), } } diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index b6605cf1..7b0a8c5d 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -2535,12 +2535,12 @@ fn parse_create_indices_with_operator_classes() { for expected_operator_class in &operator_classes { let single_column_sql_statement = format!( "CREATE INDEX the_index_name ON users USING {expected_index_type} (concat_users_name(first_name, last_name){})", - expected_operator_class.as_ref().map(|oc| format!(" {}", oc)) + expected_operator_class.as_ref().map(|oc| format!(" {oc}")) .unwrap_or_default() ); let multi_column_sql_statement = format!( "CREATE INDEX the_index_name ON users USING {expected_index_type} (column_name,concat_users_name(first_name, last_name){})", - expected_operator_class.as_ref().map(|oc| format!(" {}", oc)) + expected_operator_class.as_ref().map(|oc| format!(" {oc}")) .unwrap_or_default() ); @@ -3273,7 +3273,7 @@ fn test_fn_arg_with_value_operator() { assert!(matches!( &args[..], &[FunctionArg::ExprNamed { operator: FunctionArgOperator::Value, .. }] - ), "Invalid function argument: {:?}", args); + ), "Invalid function argument: {args:?}"); } other => panic!("Expected: JSON_OBJECT('name' VALUE 'value') to be parsed as a function, but got {other:?}"), } @@ -5679,7 +5679,7 @@ fn parse_drop_trigger() { "DROP TRIGGER{} check_update ON table_name{}", if if_exists { " IF EXISTS" } else { "" }, option - .map(|o| format!(" {}", o)) + .map(|o| format!(" {o}")) .unwrap_or_else(|| "".to_string()) ); assert_eq!( @@ -5773,8 +5773,7 @@ fn parse_trigger_related_functions() { // Now we parse the statements and check if they are parsed correctly. let mut statements = pg() .parse_sql_statements(&format!( - "{}{}{}{}", - sql_table_creation, sql_create_function, sql_create_trigger, sql_drop_trigger + "{sql_table_creation}{sql_create_function}{sql_create_trigger}{sql_drop_trigger}" )) .unwrap(); diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 8b3988d9..3ffd3339 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -2510,10 +2510,7 @@ fn test_snowflake_stage_object_names_into_location() { .zip(allowed_object_names.iter_mut()) { let (formatted_name, object_name) = it; - let sql = format!( - "COPY INTO {} FROM 'gcs://mybucket/./../a.csv'", - formatted_name - ); + let sql = format!("COPY INTO {formatted_name} FROM 'gcs://mybucket/./../a.csv'"); match snowflake().verified_stmt(&sql) { Statement::CopyIntoSnowflake { into, .. } => { assert_eq!(into.0, object_name.0) @@ -2536,10 +2533,7 @@ fn test_snowflake_stage_object_names_into_table() { .zip(allowed_object_names.iter_mut()) { let (formatted_name, object_name) = it; - let sql = format!( - "COPY INTO {} FROM 'gcs://mybucket/./../a.csv'", - formatted_name - ); + let sql = format!("COPY INTO {formatted_name} FROM 'gcs://mybucket/./../a.csv'"); match snowflake().verified_stmt(&sql) { Statement::CopyIntoSnowflake { into, .. } => { assert_eq!(into.0, object_name.0) @@ -3020,7 +3014,7 @@ fn parse_use() { for object_name in &valid_object_names { // Test single identifier without quotes assert_eq!( - snowflake().verified_stmt(&format!("USE {}", object_name)), + snowflake().verified_stmt(&format!("USE {object_name}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::new( object_name.to_string() )]))) @@ -3028,7 +3022,7 @@ fn parse_use() { for "e in "e_styles { // Test single identifier with different type of quotes assert_eq!( - snowflake().verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)), + snowflake().verified_stmt(&format!("USE {quote}{object_name}{quote}")), Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote( quote, object_name.to_string(), @@ -3040,7 +3034,9 @@ fn parse_use() { for "e in "e_styles { // Test double identifier with different type of quotes assert_eq!( - snowflake().verified_stmt(&format!("USE {0}CATALOG{0}.{0}my_schema{0}", quote)), + snowflake().verified_stmt(&format!( + "USE {quote}CATALOG{quote}.{quote}my_schema{quote}" + )), Statement::Use(Use::Object(ObjectName::from(vec![ Ident::with_quote(quote, "CATALOG"), Ident::with_quote(quote, "my_schema") @@ -3059,35 +3055,37 @@ fn parse_use() { for "e in "e_styles { // Test single and double identifier with keyword and different type of quotes assert_eq!( - snowflake().verified_stmt(&format!("USE DATABASE {0}my_database{0}", quote)), + snowflake().verified_stmt(&format!("USE DATABASE {quote}my_database{quote}")), Statement::Use(Use::Database(ObjectName::from(vec![Ident::with_quote( quote, "my_database".to_string(), )]))) ); assert_eq!( - snowflake().verified_stmt(&format!("USE SCHEMA {0}my_schema{0}", quote)), + snowflake().verified_stmt(&format!("USE SCHEMA {quote}my_schema{quote}")), Statement::Use(Use::Schema(ObjectName::from(vec![Ident::with_quote( quote, "my_schema".to_string(), )]))) ); assert_eq!( - snowflake().verified_stmt(&format!("USE SCHEMA {0}CATALOG{0}.{0}my_schema{0}", quote)), + snowflake().verified_stmt(&format!( + "USE SCHEMA {quote}CATALOG{quote}.{quote}my_schema{quote}" + )), Statement::Use(Use::Schema(ObjectName::from(vec![ Ident::with_quote(quote, "CATALOG"), Ident::with_quote(quote, "my_schema") ]))) ); assert_eq!( - snowflake().verified_stmt(&format!("USE ROLE {0}my_role{0}", quote)), + snowflake().verified_stmt(&format!("USE ROLE {quote}my_role{quote}")), Statement::Use(Use::Role(ObjectName::from(vec![Ident::with_quote( quote, "my_role".to_string(), )]))) ); assert_eq!( - snowflake().verified_stmt(&format!("USE WAREHOUSE {0}my_wh{0}", quote)), + snowflake().verified_stmt(&format!("USE WAREHOUSE {quote}my_wh{quote}")), Statement::Use(Use::Warehouse(ObjectName::from(vec![Ident::with_quote( quote, "my_wh".to_string(), @@ -3629,7 +3627,7 @@ fn test_alter_session_followed_by_statement() { .unwrap(); match stmts[..] { [Statement::AlterSession { .. }, Statement::Query { .. }] => {} - _ => panic!("Unexpected statements: {:?}", stmts), + _ => panic!("Unexpected statements: {stmts:?}"), } } diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index b759065f..06496f0c 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -324,7 +324,7 @@ fn parse_create_table_on_conflict_col() { Keyword::IGNORE, Keyword::REPLACE, ] { - let sql = format!("CREATE TABLE t1 (a INT, b INT ON CONFLICT {:?})", keyword); + let sql = format!("CREATE TABLE t1 (a INT, b INT ON CONFLICT {keyword:?})"); match sqlite_and_generic().verified_stmt(&sql) { Statement::CreateTable(CreateTable { columns, .. }) => { assert_eq!( @@ -410,7 +410,7 @@ fn parse_window_function_with_filter() { "count", "user_defined_function", ] { - let sql = format!("SELECT {}(x) FILTER (WHERE y) OVER () FROM t", func_name); + let sql = format!("SELECT {func_name}(x) FILTER (WHERE y) OVER () FROM t"); let select = sqlite().verified_only_select(&sql); assert_eq!(select.to_string(), sql); assert_eq!( @@ -444,7 +444,7 @@ fn parse_window_function_with_filter() { fn parse_attach_database() { let sql = "ATTACH DATABASE 'test.db' AS test"; let verified_stmt = sqlite().verified_stmt(sql); - assert_eq!(sql, format!("{}", verified_stmt)); + assert_eq!(sql, format!("{verified_stmt}")); match verified_stmt { Statement::AttachDatabase { schema_name, From 6c38cdcadb45ff5b343ea49bad23fdfeebc92cf5 Mon Sep 17 00:00:00 2001 From: Yoav Cohen <59807311+yoavcloud@users.noreply.github.com> Date: Fri, 27 Jun 2025 21:21:38 +0200 Subject: [PATCH 5/6] Snowflake: Add support for future grants (#1906) --- src/ast/mod.rs | 27 +++++++++++++++++++++++++++ src/keywords.rs | 1 + src/parser/mod.rs | 27 +++++++++++++++++++++++++++ tests/sqlparser_common.rs | 4 +++- 4 files changed, 58 insertions(+), 1 deletion(-) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 0f682062..19966d21 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -6920,6 +6920,12 @@ pub enum GrantObjects { AllSequencesInSchema { schemas: Vec }, /// Grant privileges on `ALL TABLES IN SCHEMA [, ...]` AllTablesInSchema { schemas: Vec }, + /// Grant privileges on `FUTURE SCHEMAS IN DATABASE [, ...]` + FutureSchemasInDatabase { databases: Vec }, + /// Grant privileges on `FUTURE TABLES IN SCHEMA [, ...]` + FutureTablesInSchema { schemas: Vec }, + /// Grant privileges on `FUTURE VIEWS IN SCHEMA [, ...]` + FutureViewsInSchema { schemas: Vec }, /// Grant privileges on specific databases Databases(Vec), /// Grant privileges on specific schemas @@ -6988,6 +6994,27 @@ impl fmt::Display for GrantObjects { display_comma_separated(schemas) ) } + GrantObjects::FutureSchemasInDatabase { databases } => { + write!( + f, + "FUTURE SCHEMAS IN DATABASE {}", + display_comma_separated(databases) + ) + } + GrantObjects::FutureTablesInSchema { schemas } => { + write!( + f, + "FUTURE TABLES IN SCHEMA {}", + display_comma_separated(schemas) + ) + } + GrantObjects::FutureViewsInSchema { schemas } => { + write!( + f, + "FUTURE VIEWS IN SCHEMA {}", + display_comma_separated(schemas) + ) + } GrantObjects::ResourceMonitors(objects) => { write!(f, "RESOURCE MONITOR {}", display_comma_separated(objects)) } diff --git a/src/keywords.rs b/src/keywords.rs index f56178c1..a8bbca3d 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -395,6 +395,7 @@ define_keywords!( FUNCTION, FUNCTIONS, FUSION, + FUTURE, GENERAL, GENERATE, GENERATED, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index f1b09afb..68d89a1e 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -13691,6 +13691,33 @@ impl<'a> Parser<'a> { Some(GrantObjects::AllTablesInSchema { schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, }) + } else if self.parse_keywords(&[ + Keyword::FUTURE, + Keyword::SCHEMAS, + Keyword::IN, + Keyword::DATABASE, + ]) { + Some(GrantObjects::FutureSchemasInDatabase { + databases: self.parse_comma_separated(|p| p.parse_object_name(false))?, + }) + } else if self.parse_keywords(&[ + Keyword::FUTURE, + Keyword::TABLES, + Keyword::IN, + Keyword::SCHEMA, + ]) { + Some(GrantObjects::FutureTablesInSchema { + schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, + }) + } else if self.parse_keywords(&[ + Keyword::FUTURE, + Keyword::VIEWS, + Keyword::IN, + Keyword::SCHEMA, + ]) { + Some(GrantObjects::FutureViewsInSchema { + schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, + }) } else if self.parse_keywords(&[ Keyword::ALL, Keyword::SEQUENCES, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 6c4ed08c..17b46e6f 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -9386,9 +9386,11 @@ fn parse_grant() { verified_stmt("GRANT SELECT ON VIEW view1 TO ROLE role1"); verified_stmt("GRANT EXEC ON my_sp TO runner"); verified_stmt("GRANT UPDATE ON my_table TO updater_role AS dbo"); - all_dialects_where(|d| d.identifier_quote_style("none") == Some('[')) .verified_stmt("GRANT SELECT ON [my_table] TO [public]"); + verified_stmt("GRANT SELECT ON FUTURE SCHEMAS IN DATABASE db1 TO ROLE role1"); + verified_stmt("GRANT SELECT ON FUTURE TABLES IN SCHEMA db1.sc1 TO ROLE role1"); + verified_stmt("GRANT SELECT ON FUTURE VIEWS IN SCHEMA db1.sc1 TO ROLE role1"); } #[test] From 50c605a47138b1602575731c196d04b0d280ad3d Mon Sep 17 00:00:00 2001 From: Sergey Olontsev Date: Sat, 28 Jun 2025 07:13:11 +0100 Subject: [PATCH 6/6] Support for Map values in ClickHouse settings (#1896) Co-authored-by: Ifeanyi Ubah --- src/ast/query.rs | 2 +- src/ast/value.rs | 1 - src/parser/mod.rs | 17 ++--- src/test_utils.rs | 5 ++ tests/sqlparser_clickhouse.rs | 130 +++++++++++++++++++++++++--------- 5 files changed, 112 insertions(+), 43 deletions(-) diff --git a/src/ast/query.rs b/src/ast/query.rs index c79ec110..99cd2ef2 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -1047,7 +1047,7 @@ impl fmt::Display for ConnectBy { #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct Setting { pub key: Ident, - pub value: Value, + pub value: Expr, } impl fmt::Display for Setting { diff --git a/src/ast/value.rs b/src/ast/value.rs index 90dbccbf..fdfa6a67 100644 --- a/src/ast/value.rs +++ b/src/ast/value.rs @@ -116,7 +116,6 @@ impl From for Value { derive(Visit, VisitMut), visit(with = "visit_value") )] - pub enum Value { /// Numeric literal #[cfg(not(feature = "bigdecimal"))] diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 68d89a1e..adf50a8f 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2770,7 +2770,7 @@ impl<'a> Parser<'a> { if self.dialect.supports_dictionary_syntax() { self.prev_token(); // Put back the '{' - return self.parse_duckdb_struct_literal(); + return self.parse_dictionary(); } self.expected("an expression", token) @@ -3139,7 +3139,7 @@ impl<'a> Parser<'a> { Ok(fields) } - /// DuckDB specific: Parse a duckdb [dictionary] + /// DuckDB and ClickHouse specific: Parse a duckdb [dictionary] or a clickhouse [map] setting /// /// Syntax: /// @@ -3148,18 +3148,18 @@ impl<'a> Parser<'a> { /// ``` /// /// [dictionary]: https://duckdb.org/docs/sql/data_types/struct#creating-structs - fn parse_duckdb_struct_literal(&mut self) -> Result { + /// [map]: https://clickhouse.com/docs/operations/settings/settings#additional_table_filters + fn parse_dictionary(&mut self) -> Result { self.expect_token(&Token::LBrace)?; - let fields = - self.parse_comma_separated0(Self::parse_duckdb_dictionary_field, Token::RBrace)?; + let fields = self.parse_comma_separated0(Self::parse_dictionary_field, Token::RBrace)?; self.expect_token(&Token::RBrace)?; Ok(Expr::Dictionary(fields)) } - /// Parse a field for a duckdb [dictionary] + /// Parse a field for a duckdb [dictionary] or a clickhouse [map] setting /// /// Syntax /// @@ -3168,7 +3168,8 @@ impl<'a> Parser<'a> { /// ``` /// /// [dictionary]: https://duckdb.org/docs/sql/data_types/struct#creating-structs - fn parse_duckdb_dictionary_field(&mut self) -> Result { + /// [map]: https://clickhouse.com/docs/operations/settings/settings#additional_table_filters + fn parse_dictionary_field(&mut self) -> Result { let key = self.parse_identifier()?; self.expect_token(&Token::Colon)?; @@ -11216,7 +11217,7 @@ impl<'a> Parser<'a> { let key_values = self.parse_comma_separated(|p| { let key = p.parse_identifier()?; p.expect_token(&Token::Eq)?; - let value = p.parse_value()?.value; + let value = p.parse_expr()?; Ok(Setting { key, value }) })?; Some(key_values) diff --git a/src/test_utils.rs b/src/test_utils.rs index c7965c3f..db7b3dd6 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -366,6 +366,11 @@ pub fn number(n: &str) -> Value { Value::Number(n.parse().unwrap(), false) } +/// Creates a [Value::SingleQuotedString] +pub fn single_quoted_string(s: impl Into) -> Value { + Value::SingleQuotedString(s.into()) +} + pub fn table_alias(name: impl Into) -> Option { Some(TableAlias { name: Ident::new(name), diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 0a60c9c4..0288c6d2 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -28,7 +28,7 @@ use test_utils::*; use sqlparser::ast::Expr::{BinaryOp, Identifier}; use sqlparser::ast::SelectItem::UnnamedExpr; use sqlparser::ast::TableFactor::Table; -use sqlparser::ast::Value::Number; +use sqlparser::ast::Value::Boolean; use sqlparser::ast::*; use sqlparser::dialect::ClickHouseDialect; use sqlparser::dialect::GenericDialect; @@ -965,38 +965,103 @@ fn parse_limit_by() { #[test] fn parse_settings_in_query() { - match clickhouse_and_generic() - .verified_stmt(r#"SELECT * FROM t SETTINGS max_threads = 1, max_block_size = 10000"#) - { - Statement::Query(query) => { - assert_eq!( - query.settings, - Some(vec![ - Setting { - key: Ident::new("max_threads"), - value: Number("1".parse().unwrap(), false) - }, - Setting { - key: Ident::new("max_block_size"), - value: Number("10000".parse().unwrap(), false) - }, - ]) - ); + fn check_settings(sql: &str, expected: Vec) { + match clickhouse_and_generic().verified_stmt(sql) { + Statement::Query(q) => { + assert_eq!(q.settings, Some(expected)); + } + _ => unreachable!(), } - _ => unreachable!(), + } + + for (sql, expected_settings) in [ + ( + r#"SELECT * FROM t SETTINGS max_threads = 1, max_block_size = 10000"#, + vec![ + Setting { + key: Ident::new("max_threads"), + value: Expr::value(number("1")), + }, + Setting { + key: Ident::new("max_block_size"), + value: Expr::value(number("10000")), + }, + ], + ), + ( + r#"SELECT * FROM t SETTINGS additional_table_filters = {'table_1': 'x != 2'}"#, + vec![Setting { + key: Ident::new("additional_table_filters"), + value: Expr::Dictionary(vec![DictionaryField { + key: Ident::with_quote('\'', "table_1"), + value: Expr::value(single_quoted_string("x != 2")).into(), + }]), + }], + ), + ( + r#"SELECT * FROM t SETTINGS additional_result_filter = 'x != 2', query_plan_optimize_lazy_materialization = false"#, + vec![ + Setting { + key: Ident::new("additional_result_filter"), + value: Expr::value(single_quoted_string("x != 2")), + }, + Setting { + key: Ident::new("query_plan_optimize_lazy_materialization"), + value: Expr::value(Boolean(false)), + }, + ], + ), + ] { + check_settings(sql, expected_settings); } let invalid_cases = vec![ - "SELECT * FROM t SETTINGS a", - "SELECT * FROM t SETTINGS a=", - "SELECT * FROM t SETTINGS a=1, b", - "SELECT * FROM t SETTINGS a=1, b=", - "SELECT * FROM t SETTINGS a=1, b=c", + ("SELECT * FROM t SETTINGS a", "Expected: =, found: EOF"), + ( + "SELECT * FROM t SETTINGS a=", + "Expected: an expression, found: EOF", + ), + ("SELECT * FROM t SETTINGS a=1, b", "Expected: =, found: EOF"), + ( + "SELECT * FROM t SETTINGS a=1, b=", + "Expected: an expression, found: EOF", + ), + ( + "SELECT * FROM t SETTINGS a = {", + "Expected: identifier, found: EOF", + ), + ( + "SELECT * FROM t SETTINGS a = {'b'", + "Expected: :, found: EOF", + ), + ( + "SELECT * FROM t SETTINGS a = {'b': ", + "Expected: an expression, found: EOF", + ), + ( + "SELECT * FROM t SETTINGS a = {'b': 'c',}", + "Expected: identifier, found: }", + ), + ( + "SELECT * FROM t SETTINGS a = {'b': 'c', 'd'}", + "Expected: :, found: }", + ), + ( + "SELECT * FROM t SETTINGS a = {'b': 'c', 'd': }", + "Expected: an expression, found: }", + ), + ( + "SELECT * FROM t SETTINGS a = {ANY(b)}", + "Expected: :, found: (", + ), ]; - for sql in invalid_cases { - clickhouse_and_generic() - .parse_sql_statements(sql) - .expect_err("Expected: SETTINGS key = value, found: "); + for (sql, error_msg) in invalid_cases { + assert_eq!( + clickhouse_and_generic() + .parse_sql_statements(sql) + .unwrap_err(), + ParserError(error_msg.to_string()) + ); } } #[test] @@ -1550,11 +1615,11 @@ fn parse_select_table_function_settings() { settings: Some(vec![ Setting { key: "s0".into(), - value: Value::Number("3".parse().unwrap(), false), + value: Expr::value(number("3")), }, Setting { key: "s1".into(), - value: Value::SingleQuotedString("s".into()), + value: Expr::value(single_quoted_string("s")), }, ]), }, @@ -1575,11 +1640,11 @@ fn parse_select_table_function_settings() { settings: Some(vec![ Setting { key: "s0".into(), - value: Value::Number("3".parse().unwrap(), false), + value: Expr::value(number("3")), }, Setting { key: "s1".into(), - value: Value::SingleQuotedString("s".into()), + value: Expr::value(single_quoted_string("s")), }, ]), }, @@ -1589,7 +1654,6 @@ fn parse_select_table_function_settings() { "SELECT * FROM t(SETTINGS a=)", "SELECT * FROM t(SETTINGS a=1, b)", "SELECT * FROM t(SETTINGS a=1, b=)", - "SELECT * FROM t(SETTINGS a=1, b=c)", ]; for sql in invalid_cases { clickhouse_and_generic()