From 310114aa9f4efc1e2ae84ba3be07e2ef97e3d81d Mon Sep 17 00:00:00 2001 From: Keming Date: Sun, 3 Aug 2025 19:44:12 +0800 Subject: [PATCH 1/3] fix: lint and test Signed-off-by: Keming --- examples/cli.rs | 2 +- sqlparser_bench/benches/sqlparser_bench.rs | 11 ++- src/ast/data_type.rs | 12 +-- src/ast/dcl.rs | 18 ++--- src/ast/ddl.rs | 26 +++---- src/ast/dml.rs | 6 +- src/ast/helpers/stmt_data_loading.rs | 2 +- src/ast/mod.rs | 88 +++++++++++----------- src/ast/query.rs | 57 +++++++------- src/ast/value.rs | 6 +- src/ast/visitor.rs | 9 ++- src/dialect/mod.rs | 2 +- src/dialect/postgresql.rs | 2 +- src/keywords.rs | 12 +-- src/lib.rs | 4 + src/parser/mod.rs | 23 ++---- src/tokenizer.rs | 8 +- tests/sqlparser_clickhouse.rs | 6 +- tests/sqlparser_common.rs | 35 ++++----- tests/sqlparser_databricks.rs | 10 +-- tests/sqlparser_duckdb.rs | 6 +- tests/sqlparser_hive.rs | 4 +- tests/sqlparser_mssql.rs | 4 +- tests/sqlparser_mysql.rs | 4 +- tests/sqlparser_postgres.rs | 7 +- tests/sqlparser_snowflake.rs | 19 +++-- tests/sqlparser_sqlite.rs | 6 +- 27 files changed, 190 insertions(+), 199 deletions(-) diff --git a/examples/cli.rs b/examples/cli.rs index 0252fca74..08a40a6dd 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 a7768cbc9..44d59c97e 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 49b277097..a5f2bbb7d 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -565,7 +565,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, "(")?; @@ -613,16 +613,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)) @@ -815,7 +815,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}")?; } diff --git a/src/ast/dcl.rs b/src/ast/dcl.rs index 895fd9858..1b73518fc 100644 --- a/src/ast/dcl.rs +++ b/src/ast/dcl.rs @@ -175,7 +175,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 { @@ -189,7 +189,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 { @@ -220,15 +220,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 743ca7cc0..ba7120dde 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -332,7 +332,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"), @@ -390,14 +390,14 @@ impl fmt::Display for AlterTableOperation { if *if_not_exists { write!(f, " IF NOT EXISTS")?; } - write!(f, " {} ({})", name, query) + write!(f, " {name} ({query})") } AlterTableOperation::DropProjection { if_exists, name } => { write!(f, "DROP PROJECTION")?; if *if_exists { write!(f, " IF EXISTS")?; } - write!(f, " {}", name) + write!(f, " {name}") } AlterTableOperation::MaterializeProjection { if_exists, @@ -408,9 +408,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(()) } @@ -423,9 +423,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(()) } @@ -661,7 +661,7 @@ impl fmt::Display for AlterColumnOperation { AlterColumnOperation::SetDefault { value } => { write!(f, "SET DEFAULT {value}") } - AlterColumnOperation::DropDefault {} => { + AlterColumnOperation::DropDefault => { write!(f, "DROP DEFAULT") } AlterColumnOperation::SetDataType { data_type, using } => { @@ -910,7 +910,7 @@ impl fmt::Display for TableConstraint { write!(f, " ON UPDATE {action}")?; } if let Some(characteristics) = characteristics { - write!(f, " {}", characteristics)?; + write!(f, " {characteristics}")?; } Ok(()) } @@ -1152,7 +1152,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() { write!(f, " {}", display_comma_separated(options.as_slice()))?; @@ -1532,7 +1532,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(()) } @@ -1554,7 +1554,7 @@ impl fmt::Display for ColumnOption { write!(f, " ON UPDATE {action}")?; } if let Some(characteristics) = characteristics { - write!(f, " {}", characteristics)?; + write!(f, " {characteristics}")?; } Ok(()) } @@ -1613,7 +1613,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 3a1d16fd5..d6809351f 100644 --- a/src/ast/dml.rs +++ b/src/ast/dml.rs @@ -226,7 +226,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() { write!(f, " ({}", display_comma_separated(&self.columns))?; @@ -364,10 +364,10 @@ impl Display for CreateTable { write!(f, " AUTO_INCREMENT {auto_increment_offset}")?; } 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(partition_by) = self.partition_by.as_ref() { write!(f, " PARTITION BY {partition_by}")?; diff --git a/src/ast/helpers/stmt_data_loading.rs b/src/ast/helpers/stmt_data_loading.rs index 48ab75e67..fba592b25 100644 --- a/src/ast/helpers/stmt_data_loading.rs +++ b/src/ast/helpers/stmt_data_loading.rs @@ -123,7 +123,7 @@ impl fmt::Display for DataLoadingOptions { } else { f.write_str(" ")?; } - write!(f, "{}", option)?; + write!(f, "{option}")?; } } Ok(()) diff --git a/src/ast/mod.rs b/src/ast/mod.rs index c0b6e1c6d..d2383f2af 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -643,17 +643,17 @@ pub enum Expr { /// such as maps, arrays, and lists: /// - Array /// - A 1-dim array `a[1]` will be represented like: - /// `CompoundFieldAccess(Ident('a'), vec![Subscript(1)]` + /// `CompoundFieldAccess(Ident('a'), vec![Subscript(1)]` /// - A 2-dim array `a[1][2]` will be represented like: - /// `CompoundFieldAccess(Ident('a'), vec![Subscript(1), Subscript(2)]` + /// `CompoundFieldAccess(Ident('a'), vec![Subscript(1), Subscript(2)]` /// - Map or Struct (Bracket-style) /// - A map `a['field1']` will be represented like: - /// `CompoundFieldAccess(Ident('a'), vec![Subscript('field')]` + /// `CompoundFieldAccess(Ident('a'), vec![Subscript('field')]` /// - A 2-dim map `a['field1']['field2']` will be represented like: - /// `CompoundFieldAccess(Ident('a'), vec![Subscript('field1'), Subscript('field2')]` + /// `CompoundFieldAccess(Ident('a'), vec![Subscript('field1'), Subscript('field2')]` /// - Struct (Dot-style) (only effect when the chain contains both subscript and expr) /// - A struct access `a[field1].field2` will be represented like: - /// `CompoundFieldAccess(Ident('a'), vec![Subscript('field1'), Ident('field2')]` + /// `CompoundFieldAccess(Ident('a'), vec![Subscript('field1'), Ident('field2')]` /// - If a struct access likes `a.field1.field2`, it will be represented by CompoundIdentifier([a, field1, field2]) CompoundFieldAccess { root: Box, @@ -1137,8 +1137,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}]"), } } } @@ -1385,12 +1385,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(()) } @@ -1519,7 +1519,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, @@ -1823,7 +1823,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)) @@ -1884,8 +1884,8 @@ pub enum WindowType { impl Display for WindowType { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self { - WindowType::WindowSpec(spec) => write!(f, "({})", spec), - WindowType::NamedWindow(name) => write!(f, "{}", name), + WindowType::WindowSpec(spec) => write!(f, "({spec})"), + WindowType::NamedWindow(name) => write!(f, "{name}"), } } } @@ -3640,7 +3640,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 ")?; @@ -4419,7 +4419,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} ")?; } @@ -4575,9 +4575,9 @@ impl fmt::Display for Statement { "{hivevar}{name} = {l_paren}{value}{r_paren}", hivevar = if *hivevar { "HIVEVAR:" } else { "" }, name = variables, - l_paren = parenthesized.then_some("(").unwrap_or_default(), + l_paren = if parenthesized { "(" } else { Default::default() }, value = display_comma_separated(value), - r_paren = parenthesized.then_some(")").unwrap_or_default(), + r_paren = if parenthesized { ")" } else { Default::default() }, ) } Statement::SetTimeZone { local, value } => { @@ -4747,7 +4747,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")?; } @@ -4788,7 +4788,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")?; @@ -4859,7 +4859,7 @@ impl fmt::Display for Statement { write!(f, " GRANTED BY {grantor}")?; } if let Some(cascade) = cascade { - write!(f, " {}", cascade)?; + write!(f, " {cascade}")?; } Ok(()) } @@ -5025,13 +5025,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())?; @@ -5050,10 +5050,10 @@ impl fmt::Display for Statement { copy_options, validation_mode, } => { - write!(f, "COPY INTO {}", into)?; + write!(f, "COPY INTO {into}")?; if from_transformations.is_none() { // Standard data load - write!(f, " FROM {}{}", from_stage, stage_params)?; + write!(f, " FROM {from_stage}{stage_params}")?; if from_stage_alias.as_ref().is_some() { write!(f, " AS {}", from_stage_alias.as_ref().unwrap())?; } @@ -5082,10 +5082,10 @@ impl fmt::Display for Statement { write!(f, " PATTERN = '{}'", pattern.as_ref().unwrap())?; } 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 validation_mode.is_some() { write!( @@ -5138,10 +5138,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")?; @@ -5661,7 +5661,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}") } } } @@ -5745,7 +5745,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)), } } @@ -5976,8 +5976,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})"), } } } @@ -5998,7 +5998,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() { @@ -6058,7 +6058,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)) @@ -6500,12 +6500,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, @@ -6558,7 +6558,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}"), } } } @@ -6778,7 +6778,7 @@ impl fmt::Display for CopyTarget { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { use CopyTarget::*; match self { - Stdin { .. } => write!(f, "STDIN"), + Stdin => write!(f, "STDIN"), Stdout => write!(f, "STDOUT"), File { filename } => write!(f, "'{}'", value::escape_single_quote_string(filename)), Program { command } => write!( @@ -8043,10 +8043,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(()) } @@ -8119,7 +8119,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}"), } } } diff --git a/src/ast/query.rs b/src/ast/query.rs index c016e7b14..242448cee 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -98,10 +98,10 @@ impl fmt::Display for Query { write!(f, " {}", display_separated(&self.locks, " "))?; } if let Some(ref for_clause) = self.for_clause { - write!(f, " {}", for_clause)?; + write!(f, " {for_clause}")?; } if let Some(ref format) = self.format_clause { - write!(f, " {}", format)?; + write!(f, " {format}")?; } Ok(()) } @@ -1157,7 +1157,6 @@ pub enum TableFactor { #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash, DFConvert)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] - pub enum TableSampleKind { /// Table sample located before the table alias option BeforeTableAlias(Box), @@ -1211,7 +1210,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, ")")?; @@ -1304,7 +1303,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(()) } @@ -1313,19 +1312,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(()) } @@ -1403,7 +1402,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(()) } @@ -1529,7 +1528,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, " ")), @@ -1863,7 +1862,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(()) } @@ -2118,7 +2117,7 @@ impl fmt::Display for OrderByExpr { None => (), } if let Some(ref with_fill) = self.with_fill { - write!(f, " {}", with_fill)? + write!(f, " {with_fill}")? } Ok(()) } @@ -2141,13 +2140,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(()) } @@ -2176,7 +2175,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(()) } @@ -2480,7 +2479,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"), } } @@ -2542,9 +2541,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")?; @@ -2562,7 +2561,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")?; } @@ -2570,7 +2569,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")?; @@ -2597,7 +2596,7 @@ impl fmt::Display for ForXml { ForXml::Raw(root) => { write!(f, "RAW")?; if let Some(root) = root { - write!(f, "('{}')", root)?; + write!(f, "('{root}')")?; } Ok(()) } @@ -2606,7 +2605,7 @@ impl fmt::Display for ForXml { ForXml::Path(root) => { write!(f, "PATH")?; if let Some(root) = root { - write!(f, "('{}')", root)?; + write!(f, "('{root}')")?; } Ok(()) } @@ -2669,7 +2668,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}") } @@ -2735,10 +2734,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(()) } @@ -2760,7 +2759,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"), } diff --git a/src/ast/value.rs b/src/ast/value.rs index ed3c8be77..4942a50d4 100644 --- a/src/ast/value.rs +++ b/src/ast/value.rs @@ -421,16 +421,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 ea9194553..17cb2b11b 100644 --- a/src/ast/visitor.rs +++ b/src/ast/visitor.rs @@ -729,7 +729,7 @@ mod tests { .unwrap(); let mut visitor = TestVisitor::default(); - s.visit(&mut visitor); + let _ = s.visit(&mut visitor); visitor.visited } @@ -904,10 +904,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(); @@ -917,6 +917,7 @@ mod tests { .unwrap(); let mut visitor = QuickVisitor {}; - s.visit(&mut visitor); + let flow = s.visit(&mut visitor); + assert_eq!(flow, ControlFlow::Continue(())); } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 119bb3cf7..a4f0c16a8 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -490,7 +490,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 170b0a7c9..9fc136142 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/keywords.rs b/src/keywords.rs index d5510552d..8c13bccb5 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -18,14 +18,14 @@ //! This module defines //! 1) a list of constants for every keyword //! 2) an `ALL_KEYWORDS` array with every keyword in it -//! This is not a list of *reserved* keywords: some of these can be -//! parsed as identifiers if the parser decides so. This means that -//! new keywords can be added here without affecting the parse result. +//! This is not a list of *reserved* keywords: some of these can be +//! parsed as identifiers if the parser decides so. This means that +//! new keywords can be added here without affecting the parse result. //! -//! As a matter of fact, most of these keywords are not used at all -//! and could be removed. +//! As a matter of fact, most of these keywords are not used at all +//! and could be removed. //! 3) a `RESERVED_FOR_TABLE_ALIAS` array with keywords reserved in a -//! "table alias" context. +//! "table alias" context. #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; diff --git a/src/lib.rs b/src/lib.rs index 5d72f9f0e..8d05e994d 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -128,6 +128,10 @@ #![cfg_attr(not(feature = "std"), no_std)] #![allow(clippy::upper_case_acronyms)] +// Permit large enum variants to keep a unified, expressive AST. +// Splitting complex nodes (expressions, statements, types) into separate types +// would bloat the API and hide intent. Extra memory is a worthwhile tradeoff. +#![allow(clippy::large_enum_variant)] // Allow proc-macros to find this crate extern crate self as sqlparser; diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 34cc6f4fb..152fc98d8 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -442,7 +442,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()?; @@ -1027,11 +1027,11 @@ impl<'a> Parser<'a> { debug!("parsing expr"); let mut expr = self.parse_prefix()?; - debug!("prefix: {:?}", expr); + debug!("prefix: {expr:?}"); loop { expr = self.parse_range_expr(expr)?; let next_precedence = self.get_next_precedence()?; - debug!("next precedence: {:?}", next_precedence); + debug!("next precedence: {next_precedence:?}"); if precedence >= next_precedence { break; @@ -1107,8 +1107,7 @@ impl<'a> Parser<'a> { })?; if rewrite_count == 0 { return Err(ParserError::ParserError(format!( - "Can't use the RANGE keyword in Expr {} without function", - expr + "Can't use the RANGE keyword in Expr {expr} without function" ))); } Ok(expr) @@ -2365,9 +2364,7 @@ impl<'a> Parser<'a> { self.expect_token(&Token::LParen)?; let mut trim_where = None; if let Token::Word(word) = self.peek_token().token { - if [Keyword::BOTH, Keyword::LEADING, Keyword::TRAILING] - .iter() - .any(|d| word.keyword == *d) + if [Keyword::BOTH, Keyword::LEADING, Keyword::TRAILING].contains(&word.keyword) { trim_where = Some(self.parse_trim_where()?); } @@ -10535,11 +10532,8 @@ impl<'a> Parser<'a> { projection .iter() .map(|select_item| { - match select_item { - SelectItem::UnnamedExpr(expr) => { - align_fill_validate(expr)?; - } - _ => {} + if let SelectItem::UnnamedExpr(expr) = select_item { + align_fill_validate(expr)?; } Ok(()) }) @@ -12262,8 +12256,7 @@ impl<'a> Parser<'a> { let ident = self.parse_identifier()?; if let GranteeName::ObjectName(namespace) = name { name = GranteeName::ObjectName(ObjectName(vec![Ident::new(format!( - "{}:{}", - namespace, ident + "{namespace}:{ident}" ))])); }; } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 6a8f29ad9..d9b114927 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -1577,7 +1577,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() { @@ -2147,13 +2147,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(), }) } @@ -3213,7 +3213,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 fed4308fc..66d12e449 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -1354,7 +1354,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(vec![Ident::new( object_name.to_string() )]))) @@ -1362,7 +1362,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(vec![Ident::with_quote( quote, object_name.to_string(), @@ -1376,7 +1376,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 7f4596602..f0fe8777d 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -3025,7 +3025,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]); @@ -3486,13 +3486,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() { @@ -9218,7 +9218,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(vec![Ident::new(func_name)]), @@ -9240,7 +9240,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!( @@ -13025,18 +13025,17 @@ fn test_table_sample() { dialects.verified_stmt("SELECT * FROM tbl AS t TABLESAMPLE SYSTEM (50) REPEATABLE (10)"); } -#[test] -fn overflow() { - let expr = std::iter::repeat("1") - .take(1000) - .collect::>() - .join(" + "); - let sql = format!("SELECT {}", expr); +// #[test] +// fn overflow() { +// let expr = std::iter::repeat_n("1", 1000) +// .collect::>() +// .join(" + "); +// let sql = format!("SELECT {expr}"); - let mut statements = Parser::parse_sql(&GenericDialect {}, sql.as_str()).unwrap(); - let statement = statements.pop().unwrap(); - assert_eq!(statement.to_string(), sql); -} +// let mut statements = Parser::parse_sql(&GenericDialect {}, sql.as_str()).unwrap(); +// let statement = statements.pop().unwrap(); +// assert_eq!(statement.to_string(), sql); +// } #[test] fn parse_select_without_projection() { let dialects = all_dialects_where(|d| d.supports_empty_projections()); @@ -13101,9 +13100,7 @@ fn assert_sql_err(input: &str, expected: &str) { let res_str = format!("{:?}", res.unwrap_err()); assert!( res_str.contains(expected), - "`{}` doesn't contains `{}`", - res_str, - expected + "`{res_str}` doesn't contains `{expected}`" ); } diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs index b9ca55d13..e77e12980 100644 --- a/tests/sqlparser_databricks.rs +++ b/tests/sqlparser_databricks.rs @@ -204,7 +204,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(vec![Ident::new( object_name.to_string() )]))) @@ -212,7 +212,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(vec![Ident::with_quote( quote, object_name.to_string(), @@ -224,21 +224,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(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(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(vec![Ident::with_quote( quote, "my_schema".to_string(), diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index db4ffb6f6..c9408c74c 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -758,7 +758,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(vec![Ident::new( object_name.to_string() )]))) @@ -766,7 +766,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(vec![Ident::with_quote( quote, object_name.to_string(), @@ -778,7 +778,7 @@ 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(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 5349f1207..9f608ac36 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs @@ -515,7 +515,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(vec![Ident::new( object_name.to_string() )]))) @@ -523,7 +523,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(vec![Ident::with_quote( quote, object_name.to_string(), diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index a0ac8a4d8..49e499ca5 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -1297,7 +1297,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(vec![Ident::new( object_name.to_string() )]))) @@ -1305,7 +1305,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(vec![Ident::with_quote( quote, object_name.to_string(), diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 0f21a6a65..ff1f5e41b 100644 --- a/tests/sqlparser_mysql.rs +++ b/tests/sqlparser_mysql.rs @@ -590,7 +590,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(vec![Ident::new( object_name.to_string() )]))) @@ -599,7 +599,7 @@ fn parse_use() { // Test single identifier with different type of quotes assert_eq!( mysql_and_generic() - .verified_stmt(&format!("USE {}{}{}", quote, object_name, quote)), + .verified_stmt(&format!("USE {quote}{object_name}{quote}")), Statement::Use(Use::Object(ObjectName(vec![Ident::with_quote( quote, object_name.to_string(), diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 864fb5eb3..fe938dcd1 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -2947,7 +2947,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:?}"), } @@ -4922,7 +4922,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!( @@ -5016,8 +5016,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 0c4bdf149..855d4f394 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -2235,8 +2235,7 @@ fn test_snowflake_stage_object_names() { { let (formatted_name, object_name) = it; let sql = format!( - "COPY INTO {} FROM 'gcs://mybucket/./../a.csv'", - formatted_name + "COPY INTO {formatted_name} FROM 'gcs://mybucket/./../a.csv'" ); match snowflake().verified_stmt(&sql) { Statement::CopyIntoSnowflake { into, .. } => { @@ -2695,7 +2694,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(vec![Ident::new( object_name.to_string() )]))) @@ -2703,7 +2702,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(vec![Ident::with_quote( quote, object_name.to_string(), @@ -2715,7 +2714,7 @@ 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(vec![ Ident::with_quote(quote, "CATALOG"), Ident::with_quote(quote, "my_schema") @@ -2734,35 +2733,35 @@ 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(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(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(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(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(vec![Ident::with_quote( quote, "my_wh".to_string(), diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index edd1365f4..08817c6b6 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -328,7 +328,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!( @@ -412,7 +412,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!( @@ -446,7 +446,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 48130974c2d414a6f6dc115899ae492d9ee4bc54 Mon Sep 17 00:00:00 2001 From: Keming Date: Sun, 3 Aug 2025 21:44:45 +0800 Subject: [PATCH 2/3] fix overflow Signed-off-by: Keming --- src/parser/mod.rs | 1 + tests/sqlparser_common.rs | 430 +++++++++++++++++++------------------- 2 files changed, 216 insertions(+), 215 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 152fc98d8..624b60b3d 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -14259,6 +14259,7 @@ impl Word { /// * `Ok(Some(replacement_expr))`: A replacement `Expr` is provided, use replacement `Expr`. /// * `Ok(None)`: A replacement `Expr` is not provided, use old `Expr`. /// * `Err(err)`: Any error returned. +#[cfg_attr(feature = "recursive-protection", recursive::recursive)] fn rewrite_calculation_expr( expr: &Expr, rewrite_func_expr: bool, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index f0fe8777d..b896f30e4 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -13025,17 +13025,17 @@ fn test_table_sample() { dialects.verified_stmt("SELECT * FROM tbl AS t TABLESAMPLE SYSTEM (50) REPEATABLE (10)"); } -// #[test] -// fn overflow() { -// let expr = std::iter::repeat_n("1", 1000) -// .collect::>() -// .join(" + "); -// let sql = format!("SELECT {expr}"); - -// let mut statements = Parser::parse_sql(&GenericDialect {}, sql.as_str()).unwrap(); -// let statement = statements.pop().unwrap(); -// assert_eq!(statement.to_string(), sql); -// } +#[test] +fn overflow() { + let expr = std::iter::repeat_n("1", 1000) + .collect::>() + .join(" + "); + let sql = format!("SELECT {expr}"); + + let mut statements = Parser::parse_sql(&GenericDialect {}, sql.as_str()).unwrap(); + let statement = statements.pop().unwrap(); + assert_eq!(statement.to_string(), sql); +} #[test] fn parse_select_without_projection() { let dialects = all_dialects_where(|d| d.supports_empty_projections()); @@ -13104,212 +13104,212 @@ fn assert_sql_err(input: &str, expected: &str) { ); } -#[test] -fn parse_range_select() { - // rewrite format `range_fn(func, range, fill, byc, [byv], align, to)` - // regular without by - assert_sql("SELECT rate(metrics) RANGE '5m', sum(metrics) RANGE '10m' FILL MAX, sum(metrics) RANGE '10m' FROM t ALIGN '1h' FILL NULL;", - "SELECT range_fn(rate(metrics), '5m', 'NULL', '0', '1h', ''), range_fn(sum(metrics), '10m', 'MAX', '0', '1h', ''), range_fn(sum(metrics), '10m', 'NULL', '0', '1h', '') FROM t"); - - // regular with by - assert_sql("SELECT rate(metrics) RANGE '5m', sum(metrics) RANGE '10m' FILL MAX, sum(metrics) RANGE '10m' FROM t ALIGN '1h' by ((a+1)/2, b) FILL NULL;", - "SELECT range_fn(rate(metrics), '5m', 'NULL', '2', (a + 1) / 2, b, '1h', ''), range_fn(sum(metrics), '10m', 'MAX', '2', (a + 1) / 2, b, '1h', ''), range_fn(sum(metrics), '10m', 'NULL', '2', (a + 1) / 2, b, '1h', '') FROM t GROUP BY a, b"); - - // explicit empty by - assert_sql("SELECT rate(metrics) RANGE '5m', sum(metrics) RANGE '10m' FILL MAX, sum(metrics) RANGE '10m' FROM t ALIGN '1h' by () FILL NULL;", - "SELECT range_fn(rate(metrics), '5m', 'NULL', '1', 1, '1h', ''), range_fn(sum(metrics), '10m', 'MAX', '1', 1, '1h', ''), range_fn(sum(metrics), '10m', 'NULL', '1', 1, '1h', '') FROM t"); - - // expression1 - assert_sql( - "SELECT avg(a/2 + 1) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", - "SELECT range_fn(avg(a / 2 + 1), '5m', 'NULL', '0', '1h', '') FROM t", - ); - - // expression2 - assert_sql( - "SELECT avg(a) RANGE '5m' FILL NULL + 1 FROM t ALIGN '1h' FILL NULL;", - "SELECT range_fn(avg(a), '5m', 'NULL', '0', '1h', '') + 1 FROM t", - ); - - // expression3 - assert_sql( - "SELECT ((avg(a) + sum(b))/2) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", - "SELECT ((range_fn(avg(a), '5m', 'NULL', '0', '1h', '') + range_fn(sum(b), '5m', 'NULL', '0', '1h', '')) / 2) FROM t", - ); - - // expression4 - assert_sql( - "SELECT covariance(a, b) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", - "SELECT range_fn(covariance(a, b), '5m', 'NULL', '0', '1h', '') FROM t", - ); - - // expression5 - assert_sql( - "SELECT covariance(cos(a), sin(b)) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", - "SELECT range_fn(covariance(cos(a), sin(b)), '5m', 'NULL', '0', '1h', '') FROM t", - ); - - // expression6 - assert_sql( - "SELECT ((covariance(a+1, b/2) + sum(b))/2) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", - "SELECT ((range_fn(covariance(a + 1, b / 2), '5m', 'NULL', '0', '1h', '') + range_fn(sum(b), '5m', 'NULL', '0', '1h', '')) / 2) FROM t", - ); - - // FILL... ALIGN... - assert_sql( - "SELECT sum(metrics) RANGE '10m' FROM t FILL NULL ALIGN '1h';", - "SELECT range_fn(sum(metrics), '10m', 'NULL', '0', '1h', '') FROM t", - ); - - // FILL ... FILL ... - assert_sql_err( - "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t FILL NULL FILL NULL;", - "Duplicate FILL keyword detected in SELECT clause.", - ); - - // ALIGN ... ALIGN ... - assert_sql_err( - "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t ALIGN '1h' ALIGN '1h';", - "Duplicate ALIGN keyword detected in SELECT clause.", - ); - - // FILL without RANGE - assert_sql_err( - "SELECT sum(metrics) FILL MAX FROM t FILL NULL ALIGN '1h';", - "Detect FILL keyword in SELECT Expr, but no RANGE given or RANGE after FILL", - ); - - // RANGE after FILL - assert_sql_err( - "SELECT sum(metrics) FILL MAX RANGE '10m' FROM t FILL NULL ALIGN '1h';", - "Detect FILL keyword in SELECT Expr, but no RANGE given or RANGE after FILL", - ); - - // INVALID Duration String - assert_sql_err( - "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t FILL NULL ALIGN '1ff';", - "not a valid duration string: 1ff", - ); - assert_sql_err( - "SELECT sum(metrics) RANGE '1regr' FILL MAX FROM t FILL NULL ALIGN '1h';", - "not a valid duration string: 1regr", - ); - - // omit RANGE - assert_sql_err( - "SELECT sum(metrics) FROM t ALIGN '1h' FILL NULL;", - "Illegal Range select, no RANGE keyword found in any SelectItem", - ); - - // omit ALIGN - assert_sql_err( - "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t FILL NULL;", - "ALIGN argument cannot be omitted in the range select query", - ); - - assert_sql_err( - "SELECT sum(metrics) RANGE '10m', * FROM t FILL NULL ALIGN '1h';", - "Wildcard `*` is not allowed in range select query", - ); -} - -#[test] -fn parse_range_in_expr() { - // use range in expr - assert_sql( - "SELECT rate(a) RANGE '6m' + 1 FROM t ALIGN '1h' FILL NULL;", - "SELECT range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + 1 FROM t", - ); - - assert_sql( - "SELECT sin(rate(a) RANGE '6m' + 1) FROM t ALIGN '1h' FILL NULL;", - "SELECT sin(range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + 1) FROM t", - ); - - assert_sql( - "SELECT sin(first_value(a ORDER BY b ASC NULLS LAST) RANGE '6m' + 1) FROM t ALIGN '1h' by (tag0, tag1) FILL NULL;", - "SELECT sin(range_fn(first_value(a ORDER BY b ASC NULLS LAST), '6m', 'NULL', '2', tag0, tag1, '1h', '') + 1) FROM t GROUP BY tag0, tag1", - ); - - assert_sql( - "SELECT sin(count(distinct a) RANGE '6m' + 1) FROM t ALIGN '1h' by (tag0, tag1) FILL NULL;", - "SELECT sin(range_fn(count(DISTINCT a), '6m', 'NULL', '2', tag0, tag1, '1h', '') + 1) FROM t GROUP BY tag0, tag1", - ); - - assert_sql( - "SELECT sin(rank() OVER (PARTITION BY a ORDER BY b DESC) RANGE '6m' + 1) FROM t ALIGN '1h' by (tag0, tag1) FILL NULL;", - "SELECT sin(range_fn(rank() OVER (PARTITION BY a ORDER BY b DESC), '6m', 'NULL', '2', tag0, tag1, '1h', '') + 1) FROM t GROUP BY tag0, tag1", - ); - - assert_sql( - "SELECT sin(cos(round(sin(avg(a + b) RANGE '5m' + 1)))) FROM test ALIGN '1h' by (tag_0,tag_1);", - "SELECT sin(cos(round(sin(range_fn(avg(a + b), '5m', '', '2', tag_0, tag_1, '1h', '') + 1)))) FROM test GROUP BY tag_0, tag_1", - ); - - assert_sql("SELECT rate(a) RANGE '6m' + rate(a) RANGE '5m' FROM t ALIGN '1h' FILL NULL;", - "SELECT range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + range_fn(rate(a), '5m', 'NULL', '0', '1h', '') FROM t"); - - assert_sql("SELECT (rate(a) RANGE '6m' + rate(a) RANGE '5m')/b + b * rate(a) RANGE '5m' FROM t ALIGN '1h' FILL NULL;", - "SELECT (range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + range_fn(rate(a), '5m', 'NULL', '0', '1h', '')) / b + b * range_fn(rate(a), '5m', 'NULL', '0', '1h', '') FROM t GROUP BY b"); - - assert_sql("SELECT round(max(a+1) Range '5m' FILL NULL), sin((max(a) + 1) Range '5m' FILL NULL) from t ALIGN '1h' by (b) FILL NULL;", - "SELECT round(range_fn(max(a + 1), '5m', 'NULL', '1', b, '1h', '')), sin((range_fn(max(a), '5m', 'NULL', '1', b, '1h', '') + 1)) FROM t GROUP BY b"); - - assert_sql( - "SELECT floor(ceil((min(a * 2) + max(a *2)) RANGE '20s' + 1.0)) FROM t ALIGN '1h';", - "SELECT FLOOR(CEIL((range_fn(min(a * 2), '20s', '', '0', '1h', '') + range_fn(max(a * 2), '20s', '', '0', '1h', '')) + 1.0)) FROM t", - ); - - assert_sql( - "SELECT gcd(CAST(max(a + 1) Range '5m' FILL NULL AS INT64), CAST(b AS INT64)) + round(max(c+1) Range '6m' FILL NULL + 1) + max(d+3) Range '10m' FILL NULL * CAST(e AS FLOAT64) + 1 FROM test ALIGN '1h' by (f, g);", - "SELECT gcd(CAST(range_fn(max(a + 1), '5m', 'NULL', '2', f, g, '1h', '') AS INT64), CAST(b AS INT64)) + round(range_fn(max(c + 1), '6m', 'NULL', '2', f, g, '1h', '') + 1) + range_fn(max(d + 3), '10m', 'NULL', '2', f, g, '1h', '') * CAST(e AS FLOAT64) + 1 FROM test GROUP BY b, e, f, g", - ); - - // Legal syntax but illegal semantic, nested range semantics are problematic, leave semantic problem to greptimedb - assert_sql( - "SELECT rate(max(a) RANGE '6m') RANGE '6m' + 1 FROM t ALIGN '1h' FILL NULL;", - "SELECT range_fn(rate(range_fn(max(a), '6m', '')), '6m', 'NULL', '0', '1h', '') + 1 FROM t", - ); - - assert_sql_err( - "SELECT rate(a) RANGE '6m' RANGE '6m' + 1 FROM t ALIGN '1h' FILL NULL;", - "Expected: end of statement, found: RANGE", - ); - - assert_sql_err( - "SELECT rate(a) + 1 RANGE '5m' FROM t ALIGN '1h' FILL NULL;", - "Can't use the RANGE keyword in Expr 1 without function", - ); +// #[test] +// fn parse_range_select() { +// // rewrite format `range_fn(func, range, fill, byc, [byv], align, to)` +// // regular without by +// assert_sql("SELECT rate(metrics) RANGE '5m', sum(metrics) RANGE '10m' FILL MAX, sum(metrics) RANGE '10m' FROM t ALIGN '1h' FILL NULL;", +// "SELECT range_fn(rate(metrics), '5m', 'NULL', '0', '1h', ''), range_fn(sum(metrics), '10m', 'MAX', '0', '1h', ''), range_fn(sum(metrics), '10m', 'NULL', '0', '1h', '') FROM t"); + +// // regular with by +// assert_sql("SELECT rate(metrics) RANGE '5m', sum(metrics) RANGE '10m' FILL MAX, sum(metrics) RANGE '10m' FROM t ALIGN '1h' by ((a+1)/2, b) FILL NULL;", +// "SELECT range_fn(rate(metrics), '5m', 'NULL', '2', (a + 1) / 2, b, '1h', ''), range_fn(sum(metrics), '10m', 'MAX', '2', (a + 1) / 2, b, '1h', ''), range_fn(sum(metrics), '10m', 'NULL', '2', (a + 1) / 2, b, '1h', '') FROM t GROUP BY a, b"); + +// // explicit empty by +// assert_sql("SELECT rate(metrics) RANGE '5m', sum(metrics) RANGE '10m' FILL MAX, sum(metrics) RANGE '10m' FROM t ALIGN '1h' by () FILL NULL;", +// "SELECT range_fn(rate(metrics), '5m', 'NULL', '1', 1, '1h', ''), range_fn(sum(metrics), '10m', 'MAX', '1', 1, '1h', ''), range_fn(sum(metrics), '10m', 'NULL', '1', 1, '1h', '') FROM t"); + +// // expression1 +// assert_sql( +// "SELECT avg(a/2 + 1) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", +// "SELECT range_fn(avg(a / 2 + 1), '5m', 'NULL', '0', '1h', '') FROM t", +// ); + +// // expression2 +// assert_sql( +// "SELECT avg(a) RANGE '5m' FILL NULL + 1 FROM t ALIGN '1h' FILL NULL;", +// "SELECT range_fn(avg(a), '5m', 'NULL', '0', '1h', '') + 1 FROM t", +// ); + +// // expression3 +// assert_sql( +// "SELECT ((avg(a) + sum(b))/2) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", +// "SELECT ((range_fn(avg(a), '5m', 'NULL', '0', '1h', '') + range_fn(sum(b), '5m', 'NULL', '0', '1h', '')) / 2) FROM t", +// ); + +// // expression4 +// assert_sql( +// "SELECT covariance(a, b) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", +// "SELECT range_fn(covariance(a, b), '5m', 'NULL', '0', '1h', '') FROM t", +// ); + +// // expression5 +// assert_sql( +// "SELECT covariance(cos(a), sin(b)) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", +// "SELECT range_fn(covariance(cos(a), sin(b)), '5m', 'NULL', '0', '1h', '') FROM t", +// ); + +// // expression6 +// assert_sql( +// "SELECT ((covariance(a+1, b/2) + sum(b))/2) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", +// "SELECT ((range_fn(covariance(a + 1, b / 2), '5m', 'NULL', '0', '1h', '') + range_fn(sum(b), '5m', 'NULL', '0', '1h', '')) / 2) FROM t", +// ); + +// // FILL... ALIGN... +// assert_sql( +// "SELECT sum(metrics) RANGE '10m' FROM t FILL NULL ALIGN '1h';", +// "SELECT range_fn(sum(metrics), '10m', 'NULL', '0', '1h', '') FROM t", +// ); + +// // FILL ... FILL ... +// assert_sql_err( +// "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t FILL NULL FILL NULL;", +// "Duplicate FILL keyword detected in SELECT clause.", +// ); + +// // ALIGN ... ALIGN ... +// assert_sql_err( +// "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t ALIGN '1h' ALIGN '1h';", +// "Duplicate ALIGN keyword detected in SELECT clause.", +// ); + +// // FILL without RANGE +// assert_sql_err( +// "SELECT sum(metrics) FILL MAX FROM t FILL NULL ALIGN '1h';", +// "Detect FILL keyword in SELECT Expr, but no RANGE given or RANGE after FILL", +// ); + +// // RANGE after FILL +// assert_sql_err( +// "SELECT sum(metrics) FILL MAX RANGE '10m' FROM t FILL NULL ALIGN '1h';", +// "Detect FILL keyword in SELECT Expr, but no RANGE given or RANGE after FILL", +// ); + +// // INVALID Duration String +// assert_sql_err( +// "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t FILL NULL ALIGN '1ff';", +// "not a valid duration string: 1ff", +// ); +// assert_sql_err( +// "SELECT sum(metrics) RANGE '1regr' FILL MAX FROM t FILL NULL ALIGN '1h';", +// "not a valid duration string: 1regr", +// ); + +// // omit RANGE +// assert_sql_err( +// "SELECT sum(metrics) FROM t ALIGN '1h' FILL NULL;", +// "Illegal Range select, no RANGE keyword found in any SelectItem", +// ); + +// // omit ALIGN +// assert_sql_err( +// "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t FILL NULL;", +// "ALIGN argument cannot be omitted in the range select query", +// ); + +// assert_sql_err( +// "SELECT sum(metrics) RANGE '10m', * FROM t FILL NULL ALIGN '1h';", +// "Wildcard `*` is not allowed in range select query", +// ); +// } - assert_sql_err( - "SELECT 1 RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", - "Can't use the RANGE keyword in Expr 1 without function", - ); -} +// #[test] +// fn parse_range_in_expr() { +// // use range in expr +// assert_sql( +// "SELECT rate(a) RANGE '6m' + 1 FROM t ALIGN '1h' FILL NULL;", +// "SELECT range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + 1 FROM t", +// ); + +// assert_sql( +// "SELECT sin(rate(a) RANGE '6m' + 1) FROM t ALIGN '1h' FILL NULL;", +// "SELECT sin(range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + 1) FROM t", +// ); + +// assert_sql( +// "SELECT sin(first_value(a ORDER BY b ASC NULLS LAST) RANGE '6m' + 1) FROM t ALIGN '1h' by (tag0, tag1) FILL NULL;", +// "SELECT sin(range_fn(first_value(a ORDER BY b ASC NULLS LAST), '6m', 'NULL', '2', tag0, tag1, '1h', '') + 1) FROM t GROUP BY tag0, tag1", +// ); + +// assert_sql( +// "SELECT sin(count(distinct a) RANGE '6m' + 1) FROM t ALIGN '1h' by (tag0, tag1) FILL NULL;", +// "SELECT sin(range_fn(count(DISTINCT a), '6m', 'NULL', '2', tag0, tag1, '1h', '') + 1) FROM t GROUP BY tag0, tag1", +// ); + +// assert_sql( +// "SELECT sin(rank() OVER (PARTITION BY a ORDER BY b DESC) RANGE '6m' + 1) FROM t ALIGN '1h' by (tag0, tag1) FILL NULL;", +// "SELECT sin(range_fn(rank() OVER (PARTITION BY a ORDER BY b DESC), '6m', 'NULL', '2', tag0, tag1, '1h', '') + 1) FROM t GROUP BY tag0, tag1", +// ); + +// assert_sql( +// "SELECT sin(cos(round(sin(avg(a + b) RANGE '5m' + 1)))) FROM test ALIGN '1h' by (tag_0,tag_1);", +// "SELECT sin(cos(round(sin(range_fn(avg(a + b), '5m', '', '2', tag_0, tag_1, '1h', '') + 1)))) FROM test GROUP BY tag_0, tag_1", +// ); + +// assert_sql("SELECT rate(a) RANGE '6m' + rate(a) RANGE '5m' FROM t ALIGN '1h' FILL NULL;", +// "SELECT range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + range_fn(rate(a), '5m', 'NULL', '0', '1h', '') FROM t"); + +// assert_sql("SELECT (rate(a) RANGE '6m' + rate(a) RANGE '5m')/b + b * rate(a) RANGE '5m' FROM t ALIGN '1h' FILL NULL;", +// "SELECT (range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + range_fn(rate(a), '5m', 'NULL', '0', '1h', '')) / b + b * range_fn(rate(a), '5m', 'NULL', '0', '1h', '') FROM t GROUP BY b"); + +// assert_sql("SELECT round(max(a+1) Range '5m' FILL NULL), sin((max(a) + 1) Range '5m' FILL NULL) from t ALIGN '1h' by (b) FILL NULL;", +// "SELECT round(range_fn(max(a + 1), '5m', 'NULL', '1', b, '1h', '')), sin((range_fn(max(a), '5m', 'NULL', '1', b, '1h', '') + 1)) FROM t GROUP BY b"); + +// assert_sql( +// "SELECT floor(ceil((min(a * 2) + max(a *2)) RANGE '20s' + 1.0)) FROM t ALIGN '1h';", +// "SELECT FLOOR(CEIL((range_fn(min(a * 2), '20s', '', '0', '1h', '') + range_fn(max(a * 2), '20s', '', '0', '1h', '')) + 1.0)) FROM t", +// ); + +// assert_sql( +// "SELECT gcd(CAST(max(a + 1) Range '5m' FILL NULL AS INT64), CAST(b AS INT64)) + round(max(c+1) Range '6m' FILL NULL + 1) + max(d+3) Range '10m' FILL NULL * CAST(e AS FLOAT64) + 1 FROM test ALIGN '1h' by (f, g);", +// "SELECT gcd(CAST(range_fn(max(a + 1), '5m', 'NULL', '2', f, g, '1h', '') AS INT64), CAST(b AS INT64)) + round(range_fn(max(c + 1), '6m', 'NULL', '2', f, g, '1h', '') + 1) + range_fn(max(d + 3), '10m', 'NULL', '2', f, g, '1h', '') * CAST(e AS FLOAT64) + 1 FROM test GROUP BY b, e, f, g", +// ); + +// // Legal syntax but illegal semantic, nested range semantics are problematic, leave semantic problem to greptimedb +// assert_sql( +// "SELECT rate(max(a) RANGE '6m') RANGE '6m' + 1 FROM t ALIGN '1h' FILL NULL;", +// "SELECT range_fn(rate(range_fn(max(a), '6m', '')), '6m', 'NULL', '0', '1h', '') + 1 FROM t", +// ); + +// assert_sql_err( +// "SELECT rate(a) RANGE '6m' RANGE '6m' + 1 FROM t ALIGN '1h' FILL NULL;", +// "Expected: end of statement, found: RANGE", +// ); + +// assert_sql_err( +// "SELECT rate(a) + 1 RANGE '5m' FROM t ALIGN '1h' FILL NULL;", +// "Can't use the RANGE keyword in Expr 1 without function", +// ); + +// assert_sql_err( +// "SELECT 1 RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", +// "Can't use the RANGE keyword in Expr 1 without function", +// ); +// } -#[test] -fn parse_range_interval() { - assert_sql( - "SELECT rate(a) RANGE (INTERVAL '1 year 2 hours 3 minutes') FROM t ALIGN (INTERVAL '1 year 2 hours 3 minutes') FILL NULL;", - "SELECT range_fn(rate(a), INTERVAL '1 year 2 hours 3 minutes', 'NULL', '0', INTERVAL '1 year 2 hours 3 minutes', '') FROM t", - ); - assert_sql( - "SELECT rate(a) RANGE (INTERVAL '1' YEAR) FROM t ALIGN (INTERVAL '1' YEAR) FILL NULL;", - "SELECT range_fn(rate(a), INTERVAL '1' YEAR, 'NULL', '0', INTERVAL '1' YEAR, '') FROM t", - ); - assert_sql( - "SELECT sin(count(distinct a) RANGE (INTERVAL '1 year 2 hours 3 minutes') + 1) FROM t ALIGN (INTERVAL '1 year 2 hours 3 minutes') FILL NULL;", - "SELECT sin(range_fn(count(DISTINCT a), INTERVAL '1 year 2 hours 3 minutes', 'NULL', '0', INTERVAL '1 year 2 hours 3 minutes', '') + 1) FROM t", - ); - assert_sql( - "SELECT rate(a) RANGE (INTERVAL '1' YEAR) FROM t ALIGN (INTERVAL '1' YEAR) TO '1970-01-01T00:00:00+08:00' BY (b, c) FILL NULL;", - "SELECT range_fn(rate(a), INTERVAL '1' YEAR, 'NULL', '2', b, c, INTERVAL '1' YEAR, '1970-01-01T00:00:00+08:00') FROM t GROUP BY b, c", - ); - assert_sql_err( - "SELECT rate(a) RANGE INTERVAL '1 year 2 hours 3 minutes' FROM t ALIGN '1h' FILL NULL;", - "Expected: end of statement, found: RANGE", - ); -} +// #[test] +// fn parse_range_interval() { +// assert_sql( +// "SELECT rate(a) RANGE (INTERVAL '1 year 2 hours 3 minutes') FROM t ALIGN (INTERVAL '1 year 2 hours 3 minutes') FILL NULL;", +// "SELECT range_fn(rate(a), INTERVAL '1 year 2 hours 3 minutes', 'NULL', '0', INTERVAL '1 year 2 hours 3 minutes', '') FROM t", +// ); +// assert_sql( +// "SELECT rate(a) RANGE (INTERVAL '1' YEAR) FROM t ALIGN (INTERVAL '1' YEAR) FILL NULL;", +// "SELECT range_fn(rate(a), INTERVAL '1' YEAR, 'NULL', '0', INTERVAL '1' YEAR, '') FROM t", +// ); +// assert_sql( +// "SELECT sin(count(distinct a) RANGE (INTERVAL '1 year 2 hours 3 minutes') + 1) FROM t ALIGN (INTERVAL '1 year 2 hours 3 minutes') FILL NULL;", +// "SELECT sin(range_fn(count(DISTINCT a), INTERVAL '1 year 2 hours 3 minutes', 'NULL', '0', INTERVAL '1 year 2 hours 3 minutes', '') + 1) FROM t", +// ); +// assert_sql( +// "SELECT rate(a) RANGE (INTERVAL '1' YEAR) FROM t ALIGN (INTERVAL '1' YEAR) TO '1970-01-01T00:00:00+08:00' BY (b, c) FILL NULL;", +// "SELECT range_fn(rate(a), INTERVAL '1' YEAR, 'NULL', '2', b, c, INTERVAL '1' YEAR, '1970-01-01T00:00:00+08:00') FROM t GROUP BY b, c", +// ); +// assert_sql_err( +// "SELECT rate(a) RANGE INTERVAL '1 year 2 hours 3 minutes' FROM t ALIGN '1h' FILL NULL;", +// "Expected: end of statement, found: RANGE", +// ); +// } #[test] fn parse_range_to() { From 32a1d236dd4cafaada27f3d878b014d2f582ff3e Mon Sep 17 00:00:00 2001 From: Keming Date: Sun, 3 Aug 2025 21:52:53 +0800 Subject: [PATCH 3/3] fix the test in another pr Signed-off-by: Keming --- src/parser/mod.rs | 1 - tests/sqlparser_common.rs | 412 +++++++++++++++++++------------------- 2 files changed, 206 insertions(+), 207 deletions(-) diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 624b60b3d..152fc98d8 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -14259,7 +14259,6 @@ impl Word { /// * `Ok(Some(replacement_expr))`: A replacement `Expr` is provided, use replacement `Expr`. /// * `Ok(None)`: A replacement `Expr` is not provided, use old `Expr`. /// * `Err(err)`: Any error returned. -#[cfg_attr(feature = "recursive-protection", recursive::recursive)] fn rewrite_calculation_expr( expr: &Expr, rewrite_func_expr: bool, diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index b896f30e4..3b3b3aeff 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -13104,212 +13104,212 @@ fn assert_sql_err(input: &str, expected: &str) { ); } -// #[test] -// fn parse_range_select() { -// // rewrite format `range_fn(func, range, fill, byc, [byv], align, to)` -// // regular without by -// assert_sql("SELECT rate(metrics) RANGE '5m', sum(metrics) RANGE '10m' FILL MAX, sum(metrics) RANGE '10m' FROM t ALIGN '1h' FILL NULL;", -// "SELECT range_fn(rate(metrics), '5m', 'NULL', '0', '1h', ''), range_fn(sum(metrics), '10m', 'MAX', '0', '1h', ''), range_fn(sum(metrics), '10m', 'NULL', '0', '1h', '') FROM t"); - -// // regular with by -// assert_sql("SELECT rate(metrics) RANGE '5m', sum(metrics) RANGE '10m' FILL MAX, sum(metrics) RANGE '10m' FROM t ALIGN '1h' by ((a+1)/2, b) FILL NULL;", -// "SELECT range_fn(rate(metrics), '5m', 'NULL', '2', (a + 1) / 2, b, '1h', ''), range_fn(sum(metrics), '10m', 'MAX', '2', (a + 1) / 2, b, '1h', ''), range_fn(sum(metrics), '10m', 'NULL', '2', (a + 1) / 2, b, '1h', '') FROM t GROUP BY a, b"); - -// // explicit empty by -// assert_sql("SELECT rate(metrics) RANGE '5m', sum(metrics) RANGE '10m' FILL MAX, sum(metrics) RANGE '10m' FROM t ALIGN '1h' by () FILL NULL;", -// "SELECT range_fn(rate(metrics), '5m', 'NULL', '1', 1, '1h', ''), range_fn(sum(metrics), '10m', 'MAX', '1', 1, '1h', ''), range_fn(sum(metrics), '10m', 'NULL', '1', 1, '1h', '') FROM t"); - -// // expression1 -// assert_sql( -// "SELECT avg(a/2 + 1) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", -// "SELECT range_fn(avg(a / 2 + 1), '5m', 'NULL', '0', '1h', '') FROM t", -// ); - -// // expression2 -// assert_sql( -// "SELECT avg(a) RANGE '5m' FILL NULL + 1 FROM t ALIGN '1h' FILL NULL;", -// "SELECT range_fn(avg(a), '5m', 'NULL', '0', '1h', '') + 1 FROM t", -// ); - -// // expression3 -// assert_sql( -// "SELECT ((avg(a) + sum(b))/2) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", -// "SELECT ((range_fn(avg(a), '5m', 'NULL', '0', '1h', '') + range_fn(sum(b), '5m', 'NULL', '0', '1h', '')) / 2) FROM t", -// ); - -// // expression4 -// assert_sql( -// "SELECT covariance(a, b) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", -// "SELECT range_fn(covariance(a, b), '5m', 'NULL', '0', '1h', '') FROM t", -// ); - -// // expression5 -// assert_sql( -// "SELECT covariance(cos(a), sin(b)) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", -// "SELECT range_fn(covariance(cos(a), sin(b)), '5m', 'NULL', '0', '1h', '') FROM t", -// ); - -// // expression6 -// assert_sql( -// "SELECT ((covariance(a+1, b/2) + sum(b))/2) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", -// "SELECT ((range_fn(covariance(a + 1, b / 2), '5m', 'NULL', '0', '1h', '') + range_fn(sum(b), '5m', 'NULL', '0', '1h', '')) / 2) FROM t", -// ); - -// // FILL... ALIGN... -// assert_sql( -// "SELECT sum(metrics) RANGE '10m' FROM t FILL NULL ALIGN '1h';", -// "SELECT range_fn(sum(metrics), '10m', 'NULL', '0', '1h', '') FROM t", -// ); - -// // FILL ... FILL ... -// assert_sql_err( -// "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t FILL NULL FILL NULL;", -// "Duplicate FILL keyword detected in SELECT clause.", -// ); - -// // ALIGN ... ALIGN ... -// assert_sql_err( -// "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t ALIGN '1h' ALIGN '1h';", -// "Duplicate ALIGN keyword detected in SELECT clause.", -// ); - -// // FILL without RANGE -// assert_sql_err( -// "SELECT sum(metrics) FILL MAX FROM t FILL NULL ALIGN '1h';", -// "Detect FILL keyword in SELECT Expr, but no RANGE given or RANGE after FILL", -// ); - -// // RANGE after FILL -// assert_sql_err( -// "SELECT sum(metrics) FILL MAX RANGE '10m' FROM t FILL NULL ALIGN '1h';", -// "Detect FILL keyword in SELECT Expr, but no RANGE given or RANGE after FILL", -// ); - -// // INVALID Duration String -// assert_sql_err( -// "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t FILL NULL ALIGN '1ff';", -// "not a valid duration string: 1ff", -// ); -// assert_sql_err( -// "SELECT sum(metrics) RANGE '1regr' FILL MAX FROM t FILL NULL ALIGN '1h';", -// "not a valid duration string: 1regr", -// ); - -// // omit RANGE -// assert_sql_err( -// "SELECT sum(metrics) FROM t ALIGN '1h' FILL NULL;", -// "Illegal Range select, no RANGE keyword found in any SelectItem", -// ); - -// // omit ALIGN -// assert_sql_err( -// "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t FILL NULL;", -// "ALIGN argument cannot be omitted in the range select query", -// ); - -// assert_sql_err( -// "SELECT sum(metrics) RANGE '10m', * FROM t FILL NULL ALIGN '1h';", -// "Wildcard `*` is not allowed in range select query", -// ); -// } - -// #[test] -// fn parse_range_in_expr() { -// // use range in expr -// assert_sql( -// "SELECT rate(a) RANGE '6m' + 1 FROM t ALIGN '1h' FILL NULL;", -// "SELECT range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + 1 FROM t", -// ); - -// assert_sql( -// "SELECT sin(rate(a) RANGE '6m' + 1) FROM t ALIGN '1h' FILL NULL;", -// "SELECT sin(range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + 1) FROM t", -// ); - -// assert_sql( -// "SELECT sin(first_value(a ORDER BY b ASC NULLS LAST) RANGE '6m' + 1) FROM t ALIGN '1h' by (tag0, tag1) FILL NULL;", -// "SELECT sin(range_fn(first_value(a ORDER BY b ASC NULLS LAST), '6m', 'NULL', '2', tag0, tag1, '1h', '') + 1) FROM t GROUP BY tag0, tag1", -// ); - -// assert_sql( -// "SELECT sin(count(distinct a) RANGE '6m' + 1) FROM t ALIGN '1h' by (tag0, tag1) FILL NULL;", -// "SELECT sin(range_fn(count(DISTINCT a), '6m', 'NULL', '2', tag0, tag1, '1h', '') + 1) FROM t GROUP BY tag0, tag1", -// ); - -// assert_sql( -// "SELECT sin(rank() OVER (PARTITION BY a ORDER BY b DESC) RANGE '6m' + 1) FROM t ALIGN '1h' by (tag0, tag1) FILL NULL;", -// "SELECT sin(range_fn(rank() OVER (PARTITION BY a ORDER BY b DESC), '6m', 'NULL', '2', tag0, tag1, '1h', '') + 1) FROM t GROUP BY tag0, tag1", -// ); - -// assert_sql( -// "SELECT sin(cos(round(sin(avg(a + b) RANGE '5m' + 1)))) FROM test ALIGN '1h' by (tag_0,tag_1);", -// "SELECT sin(cos(round(sin(range_fn(avg(a + b), '5m', '', '2', tag_0, tag_1, '1h', '') + 1)))) FROM test GROUP BY tag_0, tag_1", -// ); - -// assert_sql("SELECT rate(a) RANGE '6m' + rate(a) RANGE '5m' FROM t ALIGN '1h' FILL NULL;", -// "SELECT range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + range_fn(rate(a), '5m', 'NULL', '0', '1h', '') FROM t"); - -// assert_sql("SELECT (rate(a) RANGE '6m' + rate(a) RANGE '5m')/b + b * rate(a) RANGE '5m' FROM t ALIGN '1h' FILL NULL;", -// "SELECT (range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + range_fn(rate(a), '5m', 'NULL', '0', '1h', '')) / b + b * range_fn(rate(a), '5m', 'NULL', '0', '1h', '') FROM t GROUP BY b"); - -// assert_sql("SELECT round(max(a+1) Range '5m' FILL NULL), sin((max(a) + 1) Range '5m' FILL NULL) from t ALIGN '1h' by (b) FILL NULL;", -// "SELECT round(range_fn(max(a + 1), '5m', 'NULL', '1', b, '1h', '')), sin((range_fn(max(a), '5m', 'NULL', '1', b, '1h', '') + 1)) FROM t GROUP BY b"); - -// assert_sql( -// "SELECT floor(ceil((min(a * 2) + max(a *2)) RANGE '20s' + 1.0)) FROM t ALIGN '1h';", -// "SELECT FLOOR(CEIL((range_fn(min(a * 2), '20s', '', '0', '1h', '') + range_fn(max(a * 2), '20s', '', '0', '1h', '')) + 1.0)) FROM t", -// ); - -// assert_sql( -// "SELECT gcd(CAST(max(a + 1) Range '5m' FILL NULL AS INT64), CAST(b AS INT64)) + round(max(c+1) Range '6m' FILL NULL + 1) + max(d+3) Range '10m' FILL NULL * CAST(e AS FLOAT64) + 1 FROM test ALIGN '1h' by (f, g);", -// "SELECT gcd(CAST(range_fn(max(a + 1), '5m', 'NULL', '2', f, g, '1h', '') AS INT64), CAST(b AS INT64)) + round(range_fn(max(c + 1), '6m', 'NULL', '2', f, g, '1h', '') + 1) + range_fn(max(d + 3), '10m', 'NULL', '2', f, g, '1h', '') * CAST(e AS FLOAT64) + 1 FROM test GROUP BY b, e, f, g", -// ); - -// // Legal syntax but illegal semantic, nested range semantics are problematic, leave semantic problem to greptimedb -// assert_sql( -// "SELECT rate(max(a) RANGE '6m') RANGE '6m' + 1 FROM t ALIGN '1h' FILL NULL;", -// "SELECT range_fn(rate(range_fn(max(a), '6m', '')), '6m', 'NULL', '0', '1h', '') + 1 FROM t", -// ); - -// assert_sql_err( -// "SELECT rate(a) RANGE '6m' RANGE '6m' + 1 FROM t ALIGN '1h' FILL NULL;", -// "Expected: end of statement, found: RANGE", -// ); - -// assert_sql_err( -// "SELECT rate(a) + 1 RANGE '5m' FROM t ALIGN '1h' FILL NULL;", -// "Can't use the RANGE keyword in Expr 1 without function", -// ); - -// assert_sql_err( -// "SELECT 1 RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", -// "Can't use the RANGE keyword in Expr 1 without function", -// ); -// } - -// #[test] -// fn parse_range_interval() { -// assert_sql( -// "SELECT rate(a) RANGE (INTERVAL '1 year 2 hours 3 minutes') FROM t ALIGN (INTERVAL '1 year 2 hours 3 minutes') FILL NULL;", -// "SELECT range_fn(rate(a), INTERVAL '1 year 2 hours 3 minutes', 'NULL', '0', INTERVAL '1 year 2 hours 3 minutes', '') FROM t", -// ); -// assert_sql( -// "SELECT rate(a) RANGE (INTERVAL '1' YEAR) FROM t ALIGN (INTERVAL '1' YEAR) FILL NULL;", -// "SELECT range_fn(rate(a), INTERVAL '1' YEAR, 'NULL', '0', INTERVAL '1' YEAR, '') FROM t", -// ); -// assert_sql( -// "SELECT sin(count(distinct a) RANGE (INTERVAL '1 year 2 hours 3 minutes') + 1) FROM t ALIGN (INTERVAL '1 year 2 hours 3 minutes') FILL NULL;", -// "SELECT sin(range_fn(count(DISTINCT a), INTERVAL '1 year 2 hours 3 minutes', 'NULL', '0', INTERVAL '1 year 2 hours 3 minutes', '') + 1) FROM t", -// ); -// assert_sql( -// "SELECT rate(a) RANGE (INTERVAL '1' YEAR) FROM t ALIGN (INTERVAL '1' YEAR) TO '1970-01-01T00:00:00+08:00' BY (b, c) FILL NULL;", -// "SELECT range_fn(rate(a), INTERVAL '1' YEAR, 'NULL', '2', b, c, INTERVAL '1' YEAR, '1970-01-01T00:00:00+08:00') FROM t GROUP BY b, c", -// ); -// assert_sql_err( -// "SELECT rate(a) RANGE INTERVAL '1 year 2 hours 3 minutes' FROM t ALIGN '1h' FILL NULL;", -// "Expected: end of statement, found: RANGE", -// ); -// } +#[test] +fn parse_range_select() { + // rewrite format `range_fn(func, range, fill, byc, [byv], align, to)` + // regular without by + assert_sql("SELECT rate(metrics) RANGE '5m', sum(metrics) RANGE '10m' FILL MAX, sum(metrics) RANGE '10m' FROM t ALIGN '1h' FILL NULL;", + "SELECT range_fn(rate(metrics), '5m', 'NULL', '0', '1h', ''), range_fn(sum(metrics), '10m', 'MAX', '0', '1h', ''), range_fn(sum(metrics), '10m', 'NULL', '0', '1h', '') FROM t"); + + // regular with by + assert_sql("SELECT rate(metrics) RANGE '5m', sum(metrics) RANGE '10m' FILL MAX, sum(metrics) RANGE '10m' FROM t ALIGN '1h' by ((a+1)/2, b) FILL NULL;", + "SELECT range_fn(rate(metrics), '5m', 'NULL', '2', (a + 1) / 2, b, '1h', ''), range_fn(sum(metrics), '10m', 'MAX', '2', (a + 1) / 2, b, '1h', ''), range_fn(sum(metrics), '10m', 'NULL', '2', (a + 1) / 2, b, '1h', '') FROM t GROUP BY a, b"); + + // explicit empty by + assert_sql("SELECT rate(metrics) RANGE '5m', sum(metrics) RANGE '10m' FILL MAX, sum(metrics) RANGE '10m' FROM t ALIGN '1h' by () FILL NULL;", + "SELECT range_fn(rate(metrics), '5m', 'NULL', '1', 1, '1h', ''), range_fn(sum(metrics), '10m', 'MAX', '1', 1, '1h', ''), range_fn(sum(metrics), '10m', 'NULL', '1', 1, '1h', '') FROM t"); + + // expression1 + assert_sql( + "SELECT avg(a/2 + 1) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", + "SELECT range_fn(avg(a / 2 + 1), '5m', 'NULL', '0', '1h', '') FROM t", + ); + + // expression2 + assert_sql( + "SELECT avg(a) RANGE '5m' FILL NULL + 1 FROM t ALIGN '1h' FILL NULL;", + "SELECT range_fn(avg(a), '5m', 'NULL', '0', '1h', '') + 1 FROM t", + ); + + // expression3 + assert_sql( + "SELECT ((avg(a) + sum(b))/2) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", + "SELECT ((range_fn(avg(a), '5m', 'NULL', '0', '1h', '') + range_fn(sum(b), '5m', 'NULL', '0', '1h', '')) / 2) FROM t", + ); + + // expression4 + assert_sql( + "SELECT covariance(a, b) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", + "SELECT range_fn(covariance(a, b), '5m', 'NULL', '0', '1h', '') FROM t", + ); + + // expression5 + assert_sql( + "SELECT covariance(cos(a), sin(b)) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", + "SELECT range_fn(covariance(cos(a), sin(b)), '5m', 'NULL', '0', '1h', '') FROM t", + ); + + // expression6 + assert_sql( + "SELECT ((covariance(a+1, b/2) + sum(b))/2) RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", + "SELECT ((range_fn(covariance(a + 1, b / 2), '5m', 'NULL', '0', '1h', '') + range_fn(sum(b), '5m', 'NULL', '0', '1h', '')) / 2) FROM t", + ); + + // FILL... ALIGN... + assert_sql( + "SELECT sum(metrics) RANGE '10m' FROM t FILL NULL ALIGN '1h';", + "SELECT range_fn(sum(metrics), '10m', 'NULL', '0', '1h', '') FROM t", + ); + + // FILL ... FILL ... + assert_sql_err( + "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t FILL NULL FILL NULL;", + "Duplicate FILL keyword detected in SELECT clause.", + ); + + // ALIGN ... ALIGN ... + assert_sql_err( + "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t ALIGN '1h' ALIGN '1h';", + "Duplicate ALIGN keyword detected in SELECT clause.", + ); + + // FILL without RANGE + assert_sql_err( + "SELECT sum(metrics) FILL MAX FROM t FILL NULL ALIGN '1h';", + "Detect FILL keyword in SELECT Expr, but no RANGE given or RANGE after FILL", + ); + + // RANGE after FILL + assert_sql_err( + "SELECT sum(metrics) FILL MAX RANGE '10m' FROM t FILL NULL ALIGN '1h';", + "Detect FILL keyword in SELECT Expr, but no RANGE given or RANGE after FILL", + ); + + // INVALID Duration String + assert_sql_err( + "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t FILL NULL ALIGN '1ff';", + "not a valid duration string: 1ff", + ); + assert_sql_err( + "SELECT sum(metrics) RANGE '1regr' FILL MAX FROM t FILL NULL ALIGN '1h';", + "not a valid duration string: 1regr", + ); + + // omit RANGE + assert_sql_err( + "SELECT sum(metrics) FROM t ALIGN '1h' FILL NULL;", + "Illegal Range select, no RANGE keyword found in any SelectItem", + ); + + // omit ALIGN + assert_sql_err( + "SELECT sum(metrics) RANGE '10m' FILL MAX FROM t FILL NULL;", + "ALIGN argument cannot be omitted in the range select query", + ); + + assert_sql_err( + "SELECT sum(metrics) RANGE '10m', * FROM t FILL NULL ALIGN '1h';", + "Wildcard `*` is not allowed in range select query", + ); +} + +#[test] +fn parse_range_in_expr() { + // use range in expr + assert_sql( + "SELECT rate(a) RANGE '6m' + 1 FROM t ALIGN '1h' FILL NULL;", + "SELECT range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + 1 FROM t", + ); + + assert_sql( + "SELECT sin(rate(a) RANGE '6m' + 1) FROM t ALIGN '1h' FILL NULL;", + "SELECT sin(range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + 1) FROM t", + ); + + assert_sql( + "SELECT sin(first_value(a ORDER BY b ASC NULLS LAST) RANGE '6m' + 1) FROM t ALIGN '1h' by (tag0, tag1) FILL NULL;", + "SELECT sin(range_fn(first_value(a ORDER BY b ASC NULLS LAST), '6m', 'NULL', '2', tag0, tag1, '1h', '') + 1) FROM t GROUP BY tag0, tag1", + ); + + assert_sql( + "SELECT sin(count(distinct a) RANGE '6m' + 1) FROM t ALIGN '1h' by (tag0, tag1) FILL NULL;", + "SELECT sin(range_fn(count(DISTINCT a), '6m', 'NULL', '2', tag0, tag1, '1h', '') + 1) FROM t GROUP BY tag0, tag1", + ); + + assert_sql( + "SELECT sin(rank() OVER (PARTITION BY a ORDER BY b DESC) RANGE '6m' + 1) FROM t ALIGN '1h' by (tag0, tag1) FILL NULL;", + "SELECT sin(range_fn(rank() OVER (PARTITION BY a ORDER BY b DESC), '6m', 'NULL', '2', tag0, tag1, '1h', '') + 1) FROM t GROUP BY tag0, tag1", + ); + + assert_sql( + "SELECT sin(cos(round(sin(avg(a + b) RANGE '5m' + 1)))) FROM test ALIGN '1h' by (tag_0,tag_1);", + "SELECT sin(cos(round(sin(range_fn(avg(a + b), '5m', '', '2', tag_0, tag_1, '1h', '') + 1)))) FROM test GROUP BY tag_0, tag_1", + ); + + assert_sql("SELECT rate(a) RANGE '6m' + rate(a) RANGE '5m' FROM t ALIGN '1h' FILL NULL;", + "SELECT range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + range_fn(rate(a), '5m', 'NULL', '0', '1h', '') FROM t"); + + assert_sql("SELECT (rate(a) RANGE '6m' + rate(a) RANGE '5m')/b + b * rate(a) RANGE '5m' FROM t ALIGN '1h' FILL NULL;", + "SELECT (range_fn(rate(a), '6m', 'NULL', '0', '1h', '') + range_fn(rate(a), '5m', 'NULL', '0', '1h', '')) / b + b * range_fn(rate(a), '5m', 'NULL', '0', '1h', '') FROM t GROUP BY b"); + + assert_sql("SELECT round(max(a+1) Range '5m' FILL NULL), sin((max(a) + 1) Range '5m' FILL NULL) from t ALIGN '1h' by (b) FILL NULL;", + "SELECT round(range_fn(max(a + 1), '5m', 'NULL', '1', b, '1h', '')), sin((range_fn(max(a), '5m', 'NULL', '1', b, '1h', '') + 1)) FROM t GROUP BY b"); + + assert_sql( + "SELECT floor(ceil((min(a * 2) + max(a *2)) RANGE '20s' + 1.0)) FROM t ALIGN '1h';", + "SELECT FLOOR(CEIL((range_fn(min(a * 2), '20s', '', '0', '1h', '') + range_fn(max(a * 2), '20s', '', '0', '1h', '')) + 1.0)) FROM t", + ); + + assert_sql( + "SELECT gcd(CAST(max(a + 1) Range '5m' FILL NULL AS INT64), CAST(b AS INT64)) + round(max(c+1) Range '6m' FILL NULL + 1) + max(d+3) Range '10m' FILL NULL * CAST(e AS FLOAT64) + 1 FROM test ALIGN '1h' by (f, g);", + "SELECT gcd(CAST(range_fn(max(a + 1), '5m', 'NULL', '2', f, g, '1h', '') AS INT64), CAST(b AS INT64)) + round(range_fn(max(c + 1), '6m', 'NULL', '2', f, g, '1h', '') + 1) + range_fn(max(d + 3), '10m', 'NULL', '2', f, g, '1h', '') * CAST(e AS FLOAT64) + 1 FROM test GROUP BY b, e, f, g", + ); + + // Legal syntax but illegal semantic, nested range semantics are problematic, leave semantic problem to greptimedb + assert_sql( + "SELECT rate(max(a) RANGE '6m') RANGE '6m' + 1 FROM t ALIGN '1h' FILL NULL;", + "SELECT range_fn(rate(range_fn(max(a), '6m', '')), '6m', 'NULL', '0', '1h', '') + 1 FROM t", + ); + + assert_sql_err( + "SELECT rate(a) RANGE '6m' RANGE '6m' + 1 FROM t ALIGN '1h' FILL NULL;", + "Expected: end of statement, found: RANGE", + ); + + assert_sql_err( + "SELECT rate(a) + 1 RANGE '5m' FROM t ALIGN '1h' FILL NULL;", + "Can't use the RANGE keyword in Expr 1 without function", + ); + + assert_sql_err( + "SELECT 1 RANGE '5m' FILL NULL FROM t ALIGN '1h' FILL NULL;", + "Can't use the RANGE keyword in Expr 1 without function", + ); +} + +#[test] +fn parse_range_interval() { + assert_sql( + "SELECT rate(a) RANGE (INTERVAL '1 year 2 hours 3 minutes') FROM t ALIGN (INTERVAL '1 year 2 hours 3 minutes') FILL NULL;", + "SELECT range_fn(rate(a), INTERVAL '1 year 2 hours 3 minutes', 'NULL', '0', INTERVAL '1 year 2 hours 3 minutes', '') FROM t", + ); + assert_sql( + "SELECT rate(a) RANGE (INTERVAL '1' YEAR) FROM t ALIGN (INTERVAL '1' YEAR) FILL NULL;", + "SELECT range_fn(rate(a), INTERVAL '1' YEAR, 'NULL', '0', INTERVAL '1' YEAR, '') FROM t", + ); + assert_sql( + "SELECT sin(count(distinct a) RANGE (INTERVAL '1 year 2 hours 3 minutes') + 1) FROM t ALIGN (INTERVAL '1 year 2 hours 3 minutes') FILL NULL;", + "SELECT sin(range_fn(count(DISTINCT a), INTERVAL '1 year 2 hours 3 minutes', 'NULL', '0', INTERVAL '1 year 2 hours 3 minutes', '') + 1) FROM t", + ); + assert_sql( + "SELECT rate(a) RANGE (INTERVAL '1' YEAR) FROM t ALIGN (INTERVAL '1' YEAR) TO '1970-01-01T00:00:00+08:00' BY (b, c) FILL NULL;", + "SELECT range_fn(rate(a), INTERVAL '1' YEAR, 'NULL', '2', b, c, INTERVAL '1' YEAR, '1970-01-01T00:00:00+08:00') FROM t GROUP BY b, c", + ); + assert_sql_err( + "SELECT rate(a) RANGE INTERVAL '1 year 2 hours 3 minutes' FROM t ALIGN '1h' FILL NULL;", + "Expected: end of statement, found: RANGE", + ); +} #[test] fn parse_range_to() {