diff --git a/.github/workflows/license.yml b/.github/workflows/license.yml deleted file mode 100644 index c851bff3..00000000 --- a/.github/workflows/license.yml +++ /dev/null @@ -1,39 +0,0 @@ -# Licensed to the Apache Software Foundation (ASF) under one -# or more contributor license agreements. See the NOTICE file -# distributed with this work for additional information -# regarding copyright ownership. The ASF licenses this file -# to you under the Apache License, Version 2.0 (the -# "License"); you may not use this file except in compliance -# with the License. You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, -# software distributed under the License is distributed on an -# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -# KIND, either express or implied. See the License for the -# specific language governing permissions and limitations -# under the License. - -name: license - -# trigger for all PRs and changes to main -on: - push: - branches: - - main - pull_request: - -jobs: - - rat: - name: Release Audit Tool (RAT) - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - name: Setup Python - uses: actions/setup-python@v5 - with: - python-version: 3.8 - - name: Audit licenses - run: ./dev/release/run-rat.sh . diff --git a/dev/release/rat_exclude_files.txt b/dev/release/rat_exclude_files.txt index 280b1bce..562eec2f 100644 --- a/dev/release/rat_exclude_files.txt +++ b/dev/release/rat_exclude_files.txt @@ -1,8 +1,7 @@ +# Files to exclude from the Apache Rat (license) check +.gitignore .tool-versions -target/* -**.gitignore -rat.txt dev/release/rat_exclude_files.txt +fuzz/.gitignore sqlparser_bench/img/flamegraph.svg -**Cargo.lock -filtered_rat.txt + diff --git a/examples/cli.rs b/examples/cli.rs index 08a40a6d..0252fca7 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 6132ee43..24c59c07 100644 --- a/sqlparser_bench/benches/sqlparser_bench.rs +++ b/sqlparser_bench/benches/sqlparser_bench.rs @@ -45,24 +45,25 @@ fn basic_queries(c: &mut Criterion) { let large_statement = { let expressions = (0..1000) - .map(|n| format!("FN_{n}(COL_{n})")) + .map(|n| format!("FN_{}(COL_{})", n, 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_{n} DESC")) + .map(|n| format!("COL_{} DESC", n)) .collect::>() .join(", "); format!( - "SELECT {expressions} FROM {tables} WHERE {where_condition} ORDER BY {order_condition}" + "SELECT {} FROM {} WHERE {} ORDER BY {}", + expressions, tables, where_condition, order_condition ) }; diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index 0897f2db..3a4958c9 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -446,14 +446,6 @@ pub enum DataType { /// /// [PostgreSQL]: https://www.postgresql.org/docs/9.5/functions-geometry.html GeometricType(GeometricTypeKind), - /// PostgreSQL text search vectors, see [PostgreSQL]. - /// - /// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-textsearch.html - TsVector, - /// PostgreSQL text search query, see [PostgreSQL]. - /// - /// [PostgreSQL]: https://www.postgresql.org/docs/17/datatype-textsearch.html - TsQuery, } impl fmt::Display for DataType { @@ -666,7 +658,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 +706,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,9 +737,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::TsVector => write!(f, "TSVECTOR"), - DataType::TsQuery => write!(f, "TSQUERY"), + DataType::GeometricType(kind) => write!(f, "{}", kind), } } } @@ -942,7 +932,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 +987,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 07989407..735ab0cc 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 51e05784..059c6196 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -30,11 +30,11 @@ use sqlparser_derive::{Visit, VisitMut}; use crate::ast::value::escape_single_quote_string; use crate::ast::{ - display_comma_separated, display_separated, ArgMode, CommentDef, CreateFunctionBody, + display_comma_separated, display_separated, CommentDef, CreateFunctionBody, CreateFunctionUsing, DataType, Expr, FunctionBehavior, FunctionCalledOnNull, - FunctionDeterminismSpecifier, FunctionParallel, Ident, IndexColumn, MySQLColumnPosition, - ObjectName, OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, - Value, ValueWithSpan, + FunctionDeterminismSpecifier, FunctionParallel, Ident, MySQLColumnPosition, ObjectName, + OperateFunctionArg, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Tag, Value, + ValueWithSpan, }; use crate::keywords::Keyword; use crate::tokenizer::Token; @@ -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), } } } @@ -67,11 +67,8 @@ impl fmt::Display for ReplicaIdentity { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum AlterTableOperation { - /// `ADD [NOT VALID]` - AddConstraint { - constraint: TableConstraint, - not_valid: bool, - }, + /// `ADD ` + AddConstraint(TableConstraint), /// `ADD [COLUMN] [IF NOT EXISTS] ` AddColumn { /// `[COLUMN]`. @@ -140,10 +137,10 @@ pub enum AlterTableOperation { name: Ident, drop_behavior: Option, }, - /// `DROP [ COLUMN ] [ IF EXISTS ] [ , , ... ] [ CASCADE ]` + /// `DROP [ COLUMN ] [ IF EXISTS ] [ CASCADE ]` DropColumn { has_column_keyword: bool, - column_names: Vec, + column_name: Ident, if_exists: bool, drop_behavior: Option, }, @@ -347,10 +344,6 @@ pub enum AlterTableOperation { equals: bool, value: ValueWithSpan, }, - /// `VALIDATE CONSTRAINT ` - ValidateConstraint { - name: Ident, - }, } /// An `ALTER Policy` (`Statement::AlterPolicy`) operation @@ -457,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"), @@ -501,16 +494,7 @@ impl fmt::Display for AlterTableOperation { display_separated(new_partitions, " "), ine = if *if_not_exists { " IF NOT EXISTS" } else { "" } ), - AlterTableOperation::AddConstraint { - not_valid, - constraint, - } => { - write!(f, "ADD {constraint}")?; - if *not_valid { - write!(f, " NOT VALID")?; - } - Ok(()) - } + AlterTableOperation::AddConstraint(c) => write!(f, "ADD {c}"), AlterTableOperation::AddColumn { column_keyword, if_not_exists, @@ -541,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!( @@ -556,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, @@ -567,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(()) } @@ -582,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(()) } @@ -631,7 +615,7 @@ impl fmt::Display for AlterTableOperation { AlterTableOperation::DropIndex { name } => write!(f, "DROP INDEX {name}"), AlterTableOperation::DropColumn { has_column_keyword, - column_names: column_name, + column_name, if_exists, drop_behavior, } => write!( @@ -639,7 +623,7 @@ impl fmt::Display for AlterTableOperation { "DROP {}{}{}{}", if *has_column_keyword { "COLUMN " } else { "" }, if *if_exists { "IF EXISTS " } else { "" }, - display_comma_separated(column_name), + column_name, match drop_behavior { None => "", Some(DropBehavior::Restrict) => " RESTRICT", @@ -788,9 +772,6 @@ impl fmt::Display for AlterTableOperation { AlterTableOperation::ReplicaIdentity { identity } => { write!(f, "REPLICA IDENTITY {identity}") } - AlterTableOperation::ValidateConstraint { name } => { - write!(f, "VALIDATE CONSTRAINT {name}") - } } } } @@ -912,10 +893,7 @@ pub enum AlterColumnOperation { data_type: DataType, /// PostgreSQL specific using: Option, - /// Set to true if the statement includes the `SET DATA TYPE` keywords - had_set: bool, }, - /// `ADD GENERATED { ALWAYS | BY DEFAULT } AS IDENTITY [ ( sequence_options ) ]` /// /// Note: this is a PostgreSQL-specific operation. @@ -936,19 +914,12 @@ impl fmt::Display for AlterColumnOperation { AlterColumnOperation::DropDefault => { write!(f, "DROP DEFAULT") } - AlterColumnOperation::SetDataType { - data_type, - using, - had_set, - } => { - if *had_set { - write!(f, "SET DATA ")?; - } - write!(f, "TYPE {data_type}")?; + AlterColumnOperation::SetDataType { data_type, using } => { if let Some(expr) = using { - write!(f, " USING {expr}")?; + write!(f, "SET DATA TYPE {data_type} USING {expr}") + } else { + write!(f, "SET DATA TYPE {data_type}") } - Ok(()) } AlterColumnOperation::AddGenerated { generated_as, @@ -1008,7 +979,7 @@ pub enum TableConstraint { /// [1]: IndexType index_type: Option, /// Identifiers of the columns that are unique. - columns: Vec, + columns: Vec, index_options: Vec, characteristics: Option, /// Optional Postgres nulls handling: `[ NULLS [ NOT ] DISTINCT ]` @@ -1044,7 +1015,7 @@ pub enum TableConstraint { /// [1]: IndexType index_type: Option, /// Identifiers of the columns that form the primary key. - columns: Vec, + columns: Vec, index_options: Vec, characteristics: Option, }, @@ -1089,7 +1060,7 @@ pub enum TableConstraint { /// [1]: IndexType index_type: Option, /// Referred column identifier list. - columns: Vec, + columns: Vec, }, /// MySQLs [fulltext][1] definition. Since the [`SPATIAL`][2] definition is exactly the same, /// and MySQL displays both the same way, it is part of this definition as well. @@ -1112,7 +1083,7 @@ pub enum TableConstraint { /// Optional index name. opt_index_name: Option, /// Referred column identifier list. - columns: Vec, + columns: Vec, }, } @@ -1197,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(()) } @@ -1337,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), } } } @@ -1396,16 +1367,11 @@ impl fmt::Display for NullsDistinctOption { pub struct ProcedureParam { pub name: Ident, pub data_type: DataType, - pub mode: Option, } impl fmt::Display for ProcedureParam { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - if let Some(mode) = &self.mode { - write!(f, "{mode} {} {}", self.name, self.data_type) - } else { - write!(f, "{} {}", self.name, self.data_type) - } + write!(f, "{} {}", self.name, self.data_type) } } @@ -1455,41 +1421,17 @@ impl fmt::Display for ColumnDef { pub struct ViewColumnDef { pub name: Ident, pub data_type: 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(), - } - } + pub options: Option>, } 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 { - 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(), " "))? - } - } + write!(f, " {}", display_comma_separated(options.as_slice()))?; } Ok(()) } @@ -1709,7 +1651,7 @@ pub struct ColumnPolicyProperty { /// ``` /// [Snowflake]: https://docs.snowflake.com/en/sql-reference/sql/create-table pub with: bool, - pub policy_name: ObjectName, + pub policy_name: Ident, pub using_columns: Option>, } @@ -1874,7 +1816,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(()) } @@ -1896,7 +1838,7 @@ impl fmt::Display for ColumnOption { write!(f, " ON UPDATE {action}")?; } if let Some(characteristics) = characteristics { - write!(f, " {characteristics}")?; + write!(f, " {}", characteristics)?; } Ok(()) } @@ -1956,7 +1898,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 e179f5d7..292650c8 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 796bfd5e..06f028dd 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 75e88f8a..a1d8ff6f 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -28,7 +28,6 @@ use helpers::{ stmt_data_loading::{FileStagingCommand, StageLoadSelectItemKind}, }; -use core::cmp::Ordering; use core::ops::Deref; use core::{ fmt::{self, Display}, @@ -61,14 +60,13 @@ pub use self::ddl::{ AlterColumnOperation, AlterConnectorOwner, AlterIndexOperation, AlterPolicyOperation, AlterTableAlgorithm, AlterTableLock, AlterTableOperation, AlterType, AlterTypeAddValue, AlterTypeAddValuePosition, AlterTypeOperation, AlterTypeRename, AlterTypeRenameValue, - 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, + 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, }; pub use self::dml::{CreateIndex, CreateTable, Delete, IndexColumn, Insert}; pub use self::operator::{BinaryOperator, UnaryOperator}; @@ -174,7 +172,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)] +#[derive(Debug, Clone, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct Ident { @@ -216,35 +214,6 @@ 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 @@ -344,14 +313,12 @@ impl fmt::Display for ObjectName { #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub enum ObjectNamePart { Identifier(Ident), - Function(ObjectNamePartFunction), } impl ObjectNamePart { pub fn as_ident(&self) -> Option<&Ident> { match self { ObjectNamePart::Identifier(ident) => Some(ident), - ObjectNamePart::Function(_) => None, } } } @@ -359,31 +326,11 @@ 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::Function(func) => write!(f, "{func}"), + ObjectNamePart::Identifier(ident) => write!(f, "{}", ident), } } } -/// An object name part that consists of a function that dynamically -/// constructs identifiers. -/// -/// - [Snowflake](https://docs.snowflake.com/en/sql-reference/identifier-literal) -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct ObjectNamePartFunction { - pub name: Ident, - pub args: Vec, -} - -impl fmt::Display for ObjectNamePartFunction { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}(", self.name)?; - write!(f, "{})", display_comma_separated(&self.args)) - } -} - /// Represents an Array Expression, either /// `ARRAY[..]`, or `[..]` #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] @@ -481,22 +428,14 @@ impl fmt::Display for Interval { pub struct StructField { pub field_name: Option, pub field_type: DataType, - /// Struct field options. - /// See [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/data-definition-language#column_name_and_column_schema) - pub options: Option>, } impl fmt::Display for StructField { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(name) = &self.field_name { - write!(f, "{name} {}", self.field_type)?; + write!(f, "{name} {}", self.field_type) } else { - write!(f, "{}", self.field_type)?; - } - if let Some(options) = &self.options { - write!(f, " OPTIONS({})", display_separated(options, ", ")) - } else { - Ok(()) + write!(f, "{}", self.field_type) } } } @@ -801,7 +740,7 @@ pub enum Expr { /// `[ NOT ] IN (SELECT ...)` InSubquery { expr: Box, - subquery: Box, + subquery: Box, negated: bool, }, /// `[ NOT ] IN UNNEST(array_expression)` @@ -831,7 +770,7 @@ pub enum Expr { any: bool, expr: Box, pattern: Box, - escape_char: Option, + escape_char: Option, }, /// `ILIKE` (case-insensitive `LIKE`) ILike { @@ -841,14 +780,14 @@ pub enum Expr { any: bool, expr: Box, pattern: Box, - escape_char: Option, + escape_char: Option, }, /// SIMILAR TO regex SimilarTo { negated: bool, expr: Box, pattern: Box, - escape_char: Option, + escape_char: Option, }, /// MySQL: RLIKE regex or REGEXP regex RLike { @@ -1018,7 +957,7 @@ pub enum Expr { data_type: DataType, /// The value of the constant. /// Hint: you can unwrap the string value using `value.into_string()`. - value: ValueWithSpan, + value: Value, }, /// Scalar function call e.g. `LEFT(foo, 5)` Function(Function), @@ -1146,8 +1085,6 @@ pub enum Expr { /// [Databricks](https://docs.databricks.com/en/sql/language-manual/sql-ref-lambda-functions.html) /// [DuckDb](https://duckdb.org/docs/sql/functions/lambda.html) Lambda(LambdaFunction), - /// Checks membership of a value in a JSON array - MemberOf(MemberOf), } impl Expr { @@ -1234,8 +1171,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), } } } @@ -1437,12 +1374,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(()) } @@ -1510,7 +1447,7 @@ impl fmt::Display for Expr { } => match escape_char { Some(ch) => write!( f, - "{} {}LIKE {}{} ESCAPE {}", + "{} {}LIKE {}{} ESCAPE '{}'", expr, if *negated { "NOT " } else { "" }, if *any { "ANY " } else { "" }, @@ -1535,7 +1472,7 @@ impl fmt::Display for Expr { } => match escape_char { Some(ch) => write!( f, - "{} {}ILIKE {}{} ESCAPE {}", + "{} {}ILIKE {}{} ESCAPE '{}'", expr, if *negated { "NOT " } else { "" }, if *any { "ANY" } else { "" }, @@ -1571,7 +1508,7 @@ impl fmt::Display for Expr { } => { let not_ = if *negated { "NOT " } else { "" }; if form.is_none() { - write!(f, "{expr} IS {not_}NORMALIZED") + write!(f, "{} IS {}NORMALIZED", expr, not_) } else { write!( f, @@ -1590,7 +1527,7 @@ impl fmt::Display for Expr { } => match escape_char { Some(ch) => write!( f, - "{} {}SIMILAR TO {} ESCAPE {}", + "{} {}SIMILAR TO {} ESCAPE '{}'", expr, if *negated { "NOT " } else { "" }, pattern, @@ -1893,7 +1830,7 @@ impl fmt::Display for Expr { } } Expr::Named { expr, name } => { - write!(f, "{expr} AS {name}") + write!(f, "{} AS {}", expr, name) } Expr::Dictionary(fields) => { write!(f, "{{{}}}", display_comma_separated(fields)) @@ -1936,7 +1873,6 @@ impl fmt::Display for Expr { } Expr::Prior(expr) => write!(f, "PRIOR {expr}"), Expr::Lambda(lambda) => write!(f, "{lambda}"), - Expr::MemberOf(member_of) => write!(f, "{member_of}"), } } } @@ -2450,7 +2386,7 @@ impl fmt::Display for ConditionalStatements { } Ok(()) } - ConditionalStatements::BeginEnd(bes) => write!(f, "{bes}"), + ConditionalStatements::BeginEnd(bes) => write!(f, "{}", bes), } } } @@ -2970,7 +2906,9 @@ 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}"), @@ -3003,7 +2941,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 ")?; @@ -3026,7 +2964,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) @@ -3044,36 +2982,6 @@ impl From for Statement { } } -/// A representation of a `WHEN` arm with all the identifiers catched and the statements to execute -/// for the arm. -/// -/// Snowflake: -/// BigQuery: -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct ExceptionWhen { - pub idents: Vec, - pub statements: Vec, -} - -impl Display for ExceptionWhen { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!( - f, - "WHEN {idents} THEN", - idents = display_separated(&self.idents, " OR ") - )?; - - if !self.statements.is_empty() { - write!(f, " ")?; - format_statement_list(f, &self.statements)?; - } - - Ok(()) - } -} - /// A top-level statement (SELECT, INSERT, CREATE, etc.) #[allow(clippy::large_enum_variant)] #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] @@ -3340,8 +3248,6 @@ pub enum Statement { secret_type: Ident, options: Vec, }, - /// A `CREATE SERVER` statement. - CreateServer(CreateServerStatement), /// ```sql /// CREATE POLICY /// ``` @@ -3764,20 +3670,17 @@ pub enum Statement { /// END; /// ``` statements: Vec, - /// Exception handling with exception clauses. + /// Statements of an exception clause. /// Example: /// ```sql - /// EXCEPTION - /// WHEN EXCEPTION_1 THEN - /// SELECT 2; - /// WHEN EXCEPTION_2 OR EXCEPTION_3 THEN - /// SELECT 3; - /// WHEN OTHER THEN - /// SELECT 4; - /// ``` + /// BEGIN + /// SELECT 1; + /// EXCEPTION WHEN ERROR THEN + /// SELECT 2; + /// SELECT 3; + /// END; /// - /// - exception: Option>, + exception_statements: Option>, /// TRUE if the statement has an `END` keyword. has_end_keyword: bool, }, @@ -3970,7 +3873,6 @@ pub enum Statement { or_alter: bool, name: ObjectName, params: Option>, - language: Option, body: ConditionalStatements, }, /// ```sql @@ -4018,7 +3920,6 @@ pub enum Statement { with_grant_option: bool, as_grantor: Option, granted_by: Option, - current_grants: Option, }, /// ```sql /// DENY privileges ON object TO grantees @@ -4272,7 +4173,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 { @@ -4335,28 +4236,6 @@ pub enum Statement { Return(ReturnStatement), } -/// ```sql -/// {COPY | REVOKE} CURRENT GRANTS -/// ``` -/// -/// - [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/grant-ownership#optional-parameters) -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub enum CurrentGrantsKind { - CopyCurrentGrants, - RevokeCurrentGrants, -} - -impl fmt::Display for CurrentGrantsKind { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - CurrentGrantsKind::CopyCurrentGrants => write!(f, "COPY CURRENT GRANTS"), - CurrentGrantsKind::RevokeCurrentGrants => write!(f, "REVOKE CURRENT GRANTS"), - } - } -} - #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] @@ -4453,7 +4332,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 ")?; @@ -4897,7 +4776,6 @@ impl fmt::Display for Statement { name, or_alter, params, - language, body, } => { write!( @@ -4913,10 +4791,6 @@ impl fmt::Display for Statement { } } - if let Some(language) = language { - write!(f, " LANGUAGE {language}")?; - } - write!(f, " AS {body}") } Statement::CreateMacro { @@ -5225,9 +5099,6 @@ impl fmt::Display for Statement { write!(f, " )")?; Ok(()) } - Statement::CreateServer(stmt) => { - write!(f, "{stmt}") - } Statement::CreatePolicy { name, table_name, @@ -5292,7 +5163,7 @@ impl fmt::Display for Statement { if *only { write!(f, "ONLY ")?; } - write!(f, "{name} ")?; + write!(f, "{name} ", name = name)?; if let Some(cluster) = on_cluster { write!(f, "ON CLUSTER {cluster} ")?; } @@ -5370,7 +5241,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 @@ -5404,7 +5275,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(()) } @@ -5654,12 +5525,12 @@ impl fmt::Display for Statement { transaction, modifier, statements, - exception, + exception_statements, has_end_keyword, } => { if *syntax_begin { if let Some(modifier) = *modifier { - write!(f, "BEGIN {modifier}")?; + write!(f, "BEGIN {}", modifier)?; } else { write!(f, "BEGIN")?; } @@ -5676,10 +5547,11 @@ impl fmt::Display for Statement { write!(f, " ")?; format_statement_list(f, statements)?; } - if let Some(exception_when) = exception { - write!(f, " EXCEPTION")?; - for when in exception_when { - write!(f, " {when}")?; + if let Some(exception_statements) = exception_statements { + write!(f, " EXCEPTION WHEN ERROR THEN")?; + if !exception_statements.is_empty() { + write!(f, " ")?; + format_statement_list(f, exception_statements)?; } } if *has_end_keyword { @@ -5695,7 +5567,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")?; @@ -5760,7 +5632,6 @@ impl fmt::Display for Statement { with_grant_option, as_grantor, granted_by, - current_grants, } => { write!(f, "GRANT {privileges} ")?; if let Some(objects) = objects { @@ -5770,9 +5641,6 @@ impl fmt::Display for Statement { if *with_grant_option { write!(f, " WITH GRANT OPTION")?; } - if let Some(current_grants) = current_grants { - write!(f, " {current_grants}")?; - } if let Some(grantor) = as_grantor { write!(f, " AS {grantor}")?; } @@ -5798,7 +5666,7 @@ impl fmt::Display for Statement { write!(f, " GRANTED BY {grantor}")?; } if let Some(cascade) = cascade { - write!(f, " {cascade}")?; + write!(f, " {}", cascade)?; } Ok(()) } @@ -5977,13 +5845,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())?; @@ -6006,7 +5874,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))?; } @@ -6022,12 +5890,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}")?; } @@ -6040,24 +5908,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(()) } @@ -6103,10 +5971,10 @@ impl fmt::Display for Statement { } => { write!(f, "OPTIMIZE TABLE {name}")?; if let Some(on_cluster) = on_cluster { - write!(f, " ON CLUSTER {on_cluster}")?; + write!(f, " ON CLUSTER {on_cluster}", on_cluster = on_cluster)?; } if let Some(partition) = partition { - write!(f, " {partition}")?; + write!(f, " {partition}", partition = partition)?; } if *include_final { write!(f, " FINAL")?; @@ -6233,7 +6101,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 ) @@ -6962,7 +6830,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) } } } @@ -6977,24 +6845,6 @@ pub enum GrantObjects { AllSequencesInSchema { schemas: Vec }, /// Grant privileges on `ALL TABLES IN SCHEMA [, ...]` AllTablesInSchema { schemas: Vec }, - /// Grant privileges on `ALL VIEWS IN SCHEMA [, ...]` - AllViewsInSchema { schemas: Vec }, - /// Grant privileges on `ALL MATERIALIZED VIEWS IN SCHEMA [, ...]` - AllMaterializedViewsInSchema { schemas: Vec }, - /// Grant privileges on `ALL EXTERNAL TABLES IN SCHEMA [, ...]` - AllExternalTablesInSchema { 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 `FUTURE EXTERNAL TABLES IN SCHEMA [, ...]` - FutureExternalTablesInSchema { schemas: Vec }, - /// Grant privileges on `FUTURE MATERIALIZED VIEWS IN SCHEMA [, ...]` - FutureMaterializedViewsInSchema { schemas: Vec }, - /// Grant privileges on `FUTURE SEQUENCES IN SCHEMA [, ...]` - FutureSequencesInSchema { schemas: Vec }, /// Grant privileges on specific databases Databases(Vec), /// Grant privileges on specific schemas @@ -7023,25 +6873,6 @@ pub enum GrantObjects { ReplicationGroup(Vec), /// Grant privileges on external volumes ExternalVolumes(Vec), - /// Grant privileges on a procedure. In dialects that - /// support overloading, the argument types must be specified. - /// - /// For example: - /// `GRANT USAGE ON PROCEDURE foo(varchar) TO ROLE role1` - Procedure { - name: ObjectName, - arg_types: Vec, - }, - - /// Grant privileges on a function. In dialects that - /// support overloading, the argument types must be specified. - /// - /// For example: - /// `GRANT USAGE ON FUNCTION foo(varchar) TO ROLE role1` - Function { - name: ObjectName, - arg_types: Vec, - }, } impl fmt::Display for GrantObjects { @@ -7082,69 +6913,6 @@ impl fmt::Display for GrantObjects { display_comma_separated(schemas) ) } - GrantObjects::AllExternalTablesInSchema { schemas } => { - write!( - f, - "ALL EXTERNAL TABLES IN SCHEMA {}", - display_comma_separated(schemas) - ) - } - GrantObjects::AllViewsInSchema { schemas } => { - write!( - f, - "ALL VIEWS IN SCHEMA {}", - display_comma_separated(schemas) - ) - } - GrantObjects::AllMaterializedViewsInSchema { schemas } => { - write!( - f, - "ALL MATERIALIZED VIEWS IN SCHEMA {}", - 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::FutureExternalTablesInSchema { schemas } => { - write!( - f, - "FUTURE EXTERNAL TABLES IN SCHEMA {}", - display_comma_separated(schemas) - ) - } - GrantObjects::FutureViewsInSchema { schemas } => { - write!( - f, - "FUTURE VIEWS IN SCHEMA {}", - display_comma_separated(schemas) - ) - } - GrantObjects::FutureMaterializedViewsInSchema { schemas } => { - write!( - f, - "FUTURE MATERIALIZED VIEWS IN SCHEMA {}", - display_comma_separated(schemas) - ) - } - GrantObjects::FutureSequencesInSchema { schemas } => { - write!( - f, - "FUTURE SEQUENCES IN SCHEMA {}", - display_comma_separated(schemas) - ) - } GrantObjects::ResourceMonitors(objects) => { write!(f, "RESOURCE MONITOR {}", display_comma_separated(objects)) } @@ -7166,20 +6934,6 @@ impl fmt::Display for GrantObjects { GrantObjects::ExternalVolumes(objects) => { write!(f, "EXTERNAL VOLUME {}", display_comma_separated(objects)) } - GrantObjects::Procedure { name, arg_types } => { - write!(f, "PROCEDURE {name}")?; - if !arg_types.is_empty() { - write!(f, "({})", display_comma_separated(arg_types))?; - } - Ok(()) - } - GrantObjects::Function { name, arg_types } => { - write!(f, "FUNCTION {name}")?; - if !arg_types.is_empty() { - write!(f, "({})", display_comma_separated(arg_types))?; - } - Ok(()) - } } } } @@ -7246,7 +7000,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)), } } @@ -7491,8 +7245,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), } } } @@ -7513,7 +7267,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() { @@ -7573,7 +7327,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)) @@ -8029,12 +7783,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, @@ -8074,7 +7828,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))? @@ -8117,70 +7871,6 @@ impl fmt::Display for SecretOption { } } -/// A `CREATE SERVER` statement. -/// -/// [PostgreSQL Documentation](https://www.postgresql.org/docs/current/sql-createserver.html) -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct CreateServerStatement { - pub name: ObjectName, - pub if_not_exists: bool, - pub server_type: Option, - pub version: Option, - pub foreign_data_wrapper: ObjectName, - pub options: Option>, -} - -impl fmt::Display for CreateServerStatement { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let CreateServerStatement { - name, - if_not_exists, - server_type, - version, - foreign_data_wrapper, - options, - } = self; - - write!( - f, - "CREATE SERVER {if_not_exists}{name} ", - if_not_exists = if *if_not_exists { "IF NOT EXISTS " } else { "" }, - )?; - - if let Some(st) = server_type { - write!(f, "TYPE {st} ")?; - } - - if let Some(v) = version { - write!(f, "VERSION {v} ")?; - } - - write!(f, "FOREIGN DATA WRAPPER {foreign_data_wrapper}")?; - - if let Some(o) = options { - write!(f, " OPTIONS ({o})", o = display_comma_separated(o))?; - } - - Ok(()) - } -} - -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct CreateServerOption { - pub key: Ident, - pub value: Ident, -} - -impl fmt::Display for CreateServerOption { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} {}", self.key, self.value) - } -} - #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] @@ -8195,7 +7885,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), } } } @@ -9508,12 +9198,12 @@ impl Display for RowAccessPolicy { #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct Tag { - pub key: ObjectName, + pub key: Ident, pub value: String, } impl Tag { - pub fn new(key: ObjectName, value: String) -> Self { + pub fn new(key: Ident, value: String) -> Self { Self { key, value } } } @@ -9718,10 +9408,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(()) } @@ -9802,7 +9492,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), } } } @@ -9990,7 +9680,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"), } } @@ -10039,31 +9729,8 @@ impl fmt::Display for NullInclusion { } } -/// Checks membership of a value in a JSON array -/// -/// Syntax: -/// ```sql -/// MEMBER OF() -/// ``` -/// [MySQL](https://dev.mysql.com/doc/refman/8.4/en/json-search-functions.html#operator_member-of) -#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct MemberOf { - pub value: Box, - pub array: Box, -} - -impl fmt::Display for MemberOf { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{} MEMBER OF({})", self.value, self.array) - } -} - #[cfg(test)] mod tests { - use crate::tokenizer::Location; - use super::*; #[test] @@ -10359,16 +10026,4 @@ 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); - } } diff --git a/src/ast/query.rs b/src/ast/query.rs index 7ffb64d9..1fb93b6c 100644 --- a/src/ast/query.rs +++ b/src/ast/query.rs @@ -321,11 +321,6 @@ pub struct Select { pub top_before_distinct: bool, /// projection expressions pub projection: Vec, - /// Excluded columns from the projection expression which are not specified - /// directly after a wildcard. - /// - /// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_EXCLUDE_list.html) - pub exclude: Option, /// INTO pub into: Option, /// FROM @@ -406,10 +401,6 @@ impl fmt::Display for Select { indented_list(f, &self.projection)?; } - if let Some(exclude) = &self.exclude { - write!(f, " {exclude}")?; - } - if let Some(ref into) = self.into { f.write_str(" ")?; into.fmt(f)?; @@ -1056,7 +1047,7 @@ impl fmt::Display for ConnectBy { #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct Setting { pub key: Ident, - pub value: Expr, + pub value: Value, } impl fmt::Display for Setting { @@ -1192,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)) } @@ -1468,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, ")")?; @@ -1561,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(()) } @@ -1570,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(()) } @@ -1660,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(()) } @@ -1786,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, " ")), @@ -2157,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(()) } @@ -2407,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)?; } } @@ -2438,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(()) } @@ -2461,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(()) } @@ -2496,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(()) } @@ -2574,7 +2565,7 @@ impl fmt::Display for LimitClause { Ok(()) } LimitClause::OffsetCommaLimit { offset, limit } => { - write!(f, " LIMIT {offset}, {limit}") + write!(f, " LIMIT {}, {}", offset, limit) } } } @@ -2693,79 +2684,6 @@ pub enum PipeOperator { /// Syntax: `|> TABLESAMPLE SYSTEM (10 PERCENT) /// See more at TableSample { sample: Box }, - /// Renames columns in the input table. - /// - /// Syntax: `|> RENAME old_name AS new_name, ...` - /// - /// See more at - Rename { mappings: Vec }, - /// Combines the input table with one or more tables using UNION. - /// - /// Syntax: `|> UNION [ALL|DISTINCT] (), (), ...` - /// - /// See more at - Union { - set_quantifier: SetQuantifier, - queries: Vec, - }, - /// Returns only the rows that are present in both the input table and the specified tables. - /// - /// Syntax: `|> INTERSECT [DISTINCT] (), (), ...` - /// - /// See more at - Intersect { - set_quantifier: SetQuantifier, - queries: Vec, - }, - /// Returns only the rows that are present in the input table but not in the specified tables. - /// - /// Syntax: `|> EXCEPT DISTINCT (), (), ...` - /// - /// See more at - Except { - set_quantifier: SetQuantifier, - queries: Vec, - }, - /// Calls a table function or procedure that returns a table. - /// - /// Syntax: `|> CALL function_name(args) [AS alias]` - /// - /// See more at - Call { - function: Function, - alias: Option, - }, - /// Pivots data from rows to columns. - /// - /// Syntax: `|> PIVOT(aggregate_function(column) FOR pivot_column IN (value1, value2, ...)) [AS alias]` - /// - /// See more at - Pivot { - aggregate_functions: Vec, - value_column: Vec, - value_source: PivotValueSource, - alias: Option, - }, - /// The `UNPIVOT` pipe operator transforms columns into rows. - /// - /// Syntax: - /// ```sql - /// |> UNPIVOT(value_column FOR name_column IN (column1, column2, ...)) [alias] - /// ``` - /// - /// See more at - Unpivot { - value_column: Ident, - name_column: Ident, - unpivot_columns: Vec, - alias: Option, - }, - /// Joins the input table with another table. - /// - /// Syntax: `|> [JOIN_TYPE] JOIN [alias] ON ` or `|> [JOIN_TYPE] JOIN
[alias] USING ()` - /// - /// See more at - Join(Join), } impl fmt::Display for PipeOperator { @@ -2784,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(()) } @@ -2812,96 +2730,16 @@ 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}") - } - PipeOperator::Rename { mappings } => { - write!(f, "RENAME {}", display_comma_separated(mappings)) - } - PipeOperator::Union { - set_quantifier, - queries, - } => Self::fmt_set_operation(f, "UNION", set_quantifier, queries), - PipeOperator::Intersect { - set_quantifier, - queries, - } => Self::fmt_set_operation(f, "INTERSECT", set_quantifier, queries), - PipeOperator::Except { - set_quantifier, - queries, - } => Self::fmt_set_operation(f, "EXCEPT", set_quantifier, queries), - PipeOperator::Call { function, alias } => { - write!(f, "CALL {function}")?; - Self::fmt_optional_alias(f, alias) - } - PipeOperator::Pivot { - aggregate_functions, - value_column, - value_source, - alias, - } => { - write!( - f, - "PIVOT({} FOR {} IN ({}))", - display_comma_separated(aggregate_functions), - Expr::CompoundIdentifier(value_column.to_vec()), - value_source - )?; - Self::fmt_optional_alias(f, alias) - } - PipeOperator::Unpivot { - value_column, - name_column, - unpivot_columns, - alias, - } => { - write!( - f, - "UNPIVOT({} FOR {} IN ({}))", - value_column, - name_column, - display_comma_separated(unpivot_columns) - )?; - Self::fmt_optional_alias(f, alias) - } - PipeOperator::Join(join) => write!(f, "{join}"), - } - } -} - -impl PipeOperator { - /// Helper function to format optional alias for pipe operators - fn fmt_optional_alias(f: &mut fmt::Formatter<'_>, alias: &Option) -> fmt::Result { - if let Some(alias) = alias { - write!(f, " AS {alias}")?; - } - Ok(()) - } - - /// Helper function to format set operations (UNION, INTERSECT, EXCEPT) with queries - fn fmt_set_operation( - f: &mut fmt::Formatter<'_>, - operation: &str, - set_quantifier: &SetQuantifier, - queries: &[Query], - ) -> fmt::Result { - write!(f, "{operation}")?; - match set_quantifier { - SetQuantifier::None => {} - _ => { - write!(f, " {set_quantifier}")?; + write!(f, "{}", sample) } } - write!(f, " ")?; - let parenthesized_queries: Vec = - queries.iter().map(|query| format!("({query})")).collect(); - write!(f, "{}", display_comma_separated(&parenthesized_queries)) } } @@ -3178,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"), } } @@ -3240,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")?; @@ -3260,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")?; } @@ -3268,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")?; @@ -3295,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(()) } @@ -3304,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(()) } @@ -3367,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, "{ident} FOR ORDINALITY"), + JsonTableColumn::ForOrdinality(ident) => write!(f, "{} FOR ORDINALITY", ident), JsonTableColumn::Nested(json_table_nested_column) => { write!(f, "{json_table_nested_column}") } @@ -3433,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(()) } @@ -3458,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"), } @@ -3591,12 +3429,12 @@ impl fmt::Display for XmlTableColumn { default, nullable, } => { - write!(f, " {type}")?; + write!(f, " {}", r#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")?; @@ -3627,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/spans.rs b/src/ast/spans.rs index 3e82905e..14664b4c 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, ColumnOptions}; +use crate::ast::query::SelectItemQualifiedWildcardKind; use core::iter; use crate::tokenizer::Span; @@ -28,17 +28,16 @@ use super::{ ConstraintCharacteristics, CopySource, CreateIndex, CreateTable, CreateTableOptions, Cte, Delete, DoUpdate, ExceptSelectItem, ExcludeSelectItem, Expr, ExprWithAlias, Fetch, FromTable, Function, FunctionArg, FunctionArgExpr, FunctionArgumentClause, FunctionArgumentList, - FunctionArguments, GroupByExpr, HavingBound, IfStatement, IlikeSelectItem, IndexColumn, Insert, - Interpolate, InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, - LateralView, LimitClause, MatchRecognizePattern, Measure, NamedParenthesizedList, - NamedWindowDefinition, ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction, - OnInsert, OpenStatement, OrderBy, OrderByExpr, OrderByKind, Partition, PivotValueSource, - ProjectionSelect, Query, RaiseStatement, RaiseStatementValue, ReferentialAction, - RenameSelectItem, ReplaceSelectElement, ReplaceSelectItem, Select, SelectInto, SelectItem, - SetExpr, SqlOption, Statement, Subscript, SymbolDefinition, TableAlias, TableAliasColumnDef, - TableConstraint, TableFactor, TableObject, TableOptionsClustered, TableWithJoins, - UpdateTableFromKind, Use, Value, Values, ViewColumnDef, WhileStatement, - WildcardAdditionalOptions, With, WithFill, + FunctionArguments, GroupByExpr, HavingBound, IfStatement, IlikeSelectItem, Insert, Interpolate, + InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonPath, JsonPathElem, LateralView, + LimitClause, MatchRecognizePattern, Measure, NamedParenthesizedList, NamedWindowDefinition, + ObjectName, ObjectNamePart, Offset, OnConflict, OnConflictAction, OnInsert, OpenStatement, + OrderBy, OrderByExpr, OrderByKind, Partition, PivotValueSource, ProjectionSelect, Query, + RaiseStatement, RaiseStatementValue, ReferentialAction, RenameSelectItem, ReplaceSelectElement, + ReplaceSelectItem, Select, SelectInto, SelectItem, SetExpr, SqlOption, Statement, Subscript, + SymbolDefinition, TableAlias, TableAliasColumnDef, TableConstraint, TableFactor, TableObject, + TableOptionsClustered, TableWithJoins, UpdateTableFromKind, Use, Value, Values, ViewColumnDef, + WhileStatement, WildcardAdditionalOptions, With, WithFill, }; /// Given an iterator of spans, return the [Span::union] of all spans. @@ -423,7 +422,6 @@ impl Spanned for Statement { Statement::CreateIndex(create_index) => create_index.span(), Statement::CreateRole { .. } => Span::empty(), Statement::CreateSecret { .. } => Span::empty(), - Statement::CreateServer { .. } => Span::empty(), Statement::CreateConnector { .. } => Span::empty(), Statement::AlterTable { name, @@ -652,7 +650,7 @@ impl Spanned for TableConstraint { name.iter() .map(|i| i.span) .chain(index_name.iter().map(|i| i.span)) - .chain(columns.iter().map(|i| i.span())) + .chain(columns.iter().map(|i| i.span)) .chain(characteristics.iter().map(|i| i.span())), ), TableConstraint::PrimaryKey { @@ -666,7 +664,7 @@ impl Spanned for TableConstraint { name.iter() .map(|i| i.span) .chain(index_name.iter().map(|i| i.span)) - .chain(columns.iter().map(|i| i.span())) + .chain(columns.iter().map(|i| i.span)) .chain(characteristics.iter().map(|i| i.span())), ), TableConstraint::ForeignKey { @@ -702,7 +700,7 @@ impl Spanned for TableConstraint { } => union_spans( name.iter() .map(|i| i.span) - .chain(columns.iter().map(|i| i.span())), + .chain(columns.iter().map(|i| i.span)), ), TableConstraint::FulltextOrSpatial { fulltext: _, @@ -713,7 +711,7 @@ impl Spanned for TableConstraint { opt_index_name .iter() .map(|i| i.span) - .chain(columns.iter().map(|i| i.span())), + .chain(columns.iter().map(|i| i.span)), ), } } @@ -747,12 +745,6 @@ impl Spanned for CreateIndex { } } -impl Spanned for IndexColumn { - fn span(&self) -> Span { - self.column.span() - } -} - impl Spanned for CaseStatement { fn span(&self) -> Span { let CaseStatement { @@ -925,7 +917,6 @@ impl Spanned for AlterColumnOperation { AlterColumnOperation::SetDataType { data_type: _, using, - had_set: _, } => using.as_ref().map_or(Span::empty(), |u| u.span()), AlterColumnOperation::AddGenerated { .. } => Span::empty(), } @@ -993,13 +984,10 @@ impl Spanned for ViewColumnDef { options, } = self; - 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())) + union_spans( + core::iter::once(name.span) + .chain(options.iter().flat_map(|i| i.iter().map(|k| k.span()))), + ) } } @@ -1060,9 +1048,7 @@ 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.as_slice().iter().map(|i| i.span())) - } + CreateTableOptions::Options(vec) => union_spans(vec.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())), } @@ -1076,10 +1062,7 @@ impl Spanned for CreateTableOptions { impl Spanned for AlterTableOperation { fn span(&self) -> Span { match self { - AlterTableOperation::AddConstraint { - constraint, - not_valid: _, - } => constraint.span(), + AlterTableOperation::AddConstraint(table_constraint) => table_constraint.span(), AlterTableOperation::AddColumn { column_keyword: _, if_not_exists: _, @@ -1112,10 +1095,10 @@ impl Spanned for AlterTableOperation { } => name.span, AlterTableOperation::DropColumn { has_column_keyword: _, - column_names, + column_name, if_exists: _, drop_behavior: _, - } => union_spans(column_names.iter().map(|i| i.span)), + } => column_name.span, AlterTableOperation::AttachPartition { partition } => partition.span(), AlterTableOperation::DetachPartition { partition } => partition.span(), AlterTableOperation::FreezePartition { @@ -1200,7 +1183,6 @@ impl Spanned for AlterTableOperation { AlterTableOperation::AutoIncrement { value, .. } => value.span(), AlterTableOperation::Lock { .. } => Span::empty(), AlterTableOperation::ReplicaIdentity { .. } => Span::empty(), - AlterTableOperation::ValidateConstraint { name } => name.span, } } } @@ -1416,6 +1398,7 @@ impl Spanned for AssignmentTarget { /// f.e. `IS NULL ` reports as `::span`. /// /// Missing spans: +/// - [Expr::TypedString] # missing span for data_type /// - [Expr::MatchAgainst] # MySQL specific /// - [Expr::RLike] # MySQL specific /// - [Expr::Struct] # BigQuery specific @@ -1624,7 +1607,6 @@ impl Spanned for Expr { Expr::OuterJoin(expr) => expr.span(), Expr::Prior(expr) => expr.span(), Expr::Lambda(_) => Span::empty(), - Expr::MemberOf(member_of) => member_of.value.span().union(&member_of.array.span()), } } } @@ -1671,10 +1653,6 @@ impl Spanned for ObjectNamePart { fn span(&self) -> Span { match self { ObjectNamePart::Identifier(ident) => ident.span, - ObjectNamePart::Function(func) => func - .name - .span - .union(&union_spans(func.args.iter().map(|i| i.span()))), } } } @@ -2220,7 +2198,6 @@ impl Spanned for Select { distinct: _, // todo top: _, // todo, mysql specific projection, - exclude: _, into, from, lateral_views, diff --git a/src/ast/value.rs b/src/ast/value.rs index fdfa6a67..98616407 100644 --- a/src/ast/value.rs +++ b/src/ast/value.rs @@ -116,6 +116,7 @@ impl From for Value { derive(Visit, VisitMut), visit(with = "visit_value") )] + pub enum Value { /// Numeric literal #[cfg(not(feature = "bigdecimal"))] @@ -550,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, "\\{codepoint:04X}")?; + write!(f, "\\{:04X}", codepoint)?; } else { - write!(f, "\\+{codepoint:06X}")?; + write!(f, "\\+{:06X}", codepoint)?; } } } diff --git a/src/ast/visitor.rs b/src/ast/visitor.rs index 8e0a3139..ab4f73aa 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 {cond}"); + let sql = format!("SELECT x where {0}", cond); let dialect = GenericDialect {}; let tokens = Tokenizer::new(&dialect, sql.as_str()).tokenize().unwrap(); diff --git a/src/dialect/bigquery.rs b/src/dialect/bigquery.rs index c2cd507c..68ca1390 100644 --- a/src/dialect/bigquery.rs +++ b/src/dialect/bigquery.rs @@ -46,11 +46,7 @@ pub struct BigQueryDialect; impl Dialect for BigQueryDialect { fn parse_statement(&self, parser: &mut Parser) -> Option> { - if parser.parse_keyword(Keyword::BEGIN) { - return Some(parser.parse_begin_exception_end()); - } - - None + self.maybe_parse_statement(parser) } /// See @@ -145,3 +141,48 @@ impl Dialect for BigQueryDialect { true } } + +impl BigQueryDialect { + fn maybe_parse_statement(&self, parser: &mut Parser) -> Option> { + if parser.peek_keyword(Keyword::BEGIN) { + return Some(self.parse_begin(parser)); + } + None + } + + /// Parse a `BEGIN` statement. + /// + fn parse_begin(&self, parser: &mut Parser) -> Result { + parser.expect_keyword(Keyword::BEGIN)?; + + let statements = parser.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?; + + let has_exception_when_clause = parser.parse_keywords(&[ + Keyword::EXCEPTION, + Keyword::WHEN, + Keyword::ERROR, + Keyword::THEN, + ]); + let exception_statements = if has_exception_when_clause { + if !parser.peek_keyword(Keyword::END) { + Some(parser.parse_statement_list(&[Keyword::END])?) + } else { + Some(Default::default()) + } + } else { + None + }; + + parser.expect_keyword(Keyword::END)?; + + Ok(Statement::StartTransaction { + begin: true, + statements, + exception_statements, + has_end_keyword: true, + transaction: None, + modifier: None, + modes: Default::default(), + }) + } +} diff --git a/src/dialect/duckdb.rs b/src/dialect/duckdb.rs index fa18463a..3366c670 100644 --- a/src/dialect/duckdb.rs +++ b/src/dialect/duckdb.rs @@ -94,8 +94,4 @@ impl Dialect for DuckDbDialect { fn supports_order_by_all(&self) -> bool { true } - - fn supports_select_wildcard_exclude(&self) -> bool { - true - } } diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index be2cc007..8f57e487 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -52,10 +52,6 @@ impl Dialect for GenericDialect { true } - fn supports_left_associative_joins_without_parens(&self) -> bool { - true - } - fn supports_connect_by(&self) -> bool { true } @@ -112,14 +108,6 @@ impl Dialect for GenericDialect { true } - fn supports_from_first_select(&self) -> bool { - true - } - - fn supports_projection_trailing_commas(&self) -> bool { - true - } - fn supports_asc_desc_in_column_definition(&self) -> bool { true } @@ -179,8 +167,4 @@ impl Dialect for GenericDialect { fn supports_filter_during_aggregation(&self) -> bool { true } - - fn supports_select_wildcard_exclude(&self) -> bool { - true - } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index deb5719d..a4c899e6 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -49,7 +49,7 @@ pub use self::postgresql::PostgreSqlDialect; pub use self::redshift::RedshiftSqlDialect; pub use self::snowflake::SnowflakeDialect; pub use self::sqlite::SQLiteDialect; -use crate::ast::{ColumnOption, Expr, GranteesType, Ident, ObjectNamePart, Statement}; +use crate::ast::{ColumnOption, Expr, GranteesType, Statement}; pub use crate::keywords; use crate::keywords::Keyword; use crate::parser::{Parser, ParserError}; @@ -278,34 +278,6 @@ pub trait Dialect: Debug + Any { false } - /// Indicates whether the dialect supports left-associative join parsing - /// by default when parentheses are omitted in nested joins. - /// - /// Most dialects (like MySQL or Postgres) assume **left-associative** precedence, - /// so a query like: - /// - /// ```sql - /// SELECT * FROM t1 NATURAL JOIN t5 INNER JOIN t0 ON ... - /// ``` - /// is interpreted as: - /// ```sql - /// ((t1 NATURAL JOIN t5) INNER JOIN t0 ON ...) - /// ``` - /// and internally represented as a **flat list** of joins. - /// - /// In contrast, some dialects (e.g. **Snowflake**) assume **right-associative** - /// precedence and interpret the same query as: - /// ```sql - /// (t1 NATURAL JOIN (t5 INNER JOIN t0 ON ...)) - /// ``` - /// which results in a **nested join** structure in the AST. - /// - /// If this method returns `false`, the parser must build nested join trees - /// even in the absence of parentheses to reflect the correct associativity - fn supports_left_associative_joins_without_parens(&self) -> bool { - true - } - /// Returns true if the dialect supports the `(+)` syntax for OUTER JOIN. fn supports_outer_join_operator(&self) -> bool { false @@ -570,26 +542,6 @@ pub trait Dialect: Debug + Any { false } - /// Returns true if the dialect supports an exclude option - /// following a wildcard in the projection section. For example: - /// `SELECT * EXCLUDE col1 FROM tbl`. - /// - /// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_EXCLUDE_list.html) - /// [Snowflake](https://docs.snowflake.com/en/sql-reference/sql/select) - fn supports_select_wildcard_exclude(&self) -> bool { - false - } - - /// Returns true if the dialect supports an exclude option - /// as the last item in the projection section, not necessarily - /// after a wildcard. For example: - /// `SELECT *, c1, c2 EXCLUDE c3 FROM tbl` - /// - /// [Redshift](https://docs.aws.amazon.com/redshift/latest/dg/r_EXCLUDE_list.html) - fn supports_select_exclude(&self) -> bool { - false - } - /// Dialect-specific infix parser override /// /// This method is called to parse the next infix expression. @@ -635,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)), @@ -669,7 +621,6 @@ pub trait Dialect: Debug + Any { Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)), Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)), Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)), - Token::Word(w) if w.keyword == Keyword::MEMBER => Ok(p!(Like)), _ => Ok(self.prec_unknown()), }, Token::Word(w) if w.keyword == Keyword::IS => Ok(p!(Is)), @@ -682,7 +633,6 @@ pub trait Dialect: Debug + Any { Token::Word(w) if w.keyword == Keyword::REGEXP => Ok(p!(Like)), Token::Word(w) if w.keyword == Keyword::MATCH => Ok(p!(Like)), Token::Word(w) if w.keyword == Keyword::SIMILAR => Ok(p!(Like)), - Token::Word(w) if w.keyword == Keyword::MEMBER => Ok(p!(Like)), Token::Word(w) if w.keyword == Keyword::OPERATOR => Ok(p!(Between)), Token::Word(w) if w.keyword == Keyword::DIV => Ok(p!(MulDivModOp)), Token::Period => Ok(p!(Period)), @@ -1012,17 +962,11 @@ pub trait Dialect: Debug + Any { explicit || self.is_column_alias(kw, parser) } - /// Returns true if the specified keyword should be parsed as a table identifier. - /// See [keywords::RESERVED_FOR_TABLE_ALIAS] - fn is_table_alias(&self, kw: &Keyword, _parser: &mut Parser) -> bool { - !keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw) - } - /// Returns true if the specified keyword should be parsed as a table factor alias. /// When explicit is true, the keyword is preceded by an `AS` word. Parser is provided /// to enable looking ahead if needed. - fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool { - explicit || self.is_table_alias(kw, parser) + fn is_table_factor_alias(&self, explicit: bool, kw: &Keyword, _parser: &mut Parser) -> bool { + explicit || !keywords::RESERVED_FOR_TABLE_ALIAS.contains(kw) } /// Returns true if this dialect supports querying historical table data @@ -1084,37 +1028,6 @@ pub trait Dialect: Debug + Any { fn supports_set_names(&self) -> bool { false } - - fn supports_space_separated_column_options(&self) -> bool { - false - } - - /// Returns true if the dialect supports the `USING` clause in an `ALTER COLUMN` statement. - /// Example: - /// ```sql - /// ALTER TABLE tbl ALTER COLUMN col SET DATA TYPE USING ` - /// ``` - fn supports_alter_column_type_using(&self) -> bool { - false - } - - /// Returns true if the dialect supports `ALTER TABLE tbl DROP COLUMN c1, ..., cn` - fn supports_comma_separated_drop_column_list(&self) -> bool { - false - } - - /// Returns true if the dialect considers the specified ident as a function - /// that returns an identifier. Typically used to generate identifiers - /// programmatically. - /// - /// - [Snowflake](https://docs.snowflake.com/en/sql-reference/identifier-literal) - fn is_identifier_generating_function_name( - &self, - _ident: &Ident, - _name_parts: &[ObjectNamePart], - ) -> bool { - false - } } /// This represents the operators for which precedence must be defined diff --git a/src/dialect/postgresql.rs b/src/dialect/postgresql.rs index b2d4014c..9b08b8f3 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 @@ -258,8 +258,4 @@ impl Dialect for PostgreSqlDialect { fn supports_set_names(&self) -> bool { true } - - fn supports_alter_column_type_using(&self) -> bool { - true - } } diff --git a/src/dialect/redshift.rs b/src/dialect/redshift.rs index 8ffed98a..feccca5d 100644 --- a/src/dialect/redshift.rs +++ b/src/dialect/redshift.rs @@ -80,15 +80,13 @@ impl Dialect for RedshiftSqlDialect { } fn is_identifier_start(&self, ch: char) -> bool { - // Extends Postgres dialect with sharp and UTF-8 multibyte chars - // https://docs.aws.amazon.com/redshift/latest/dg/r_names.html - PostgreSqlDialect {}.is_identifier_start(ch) || ch == '#' || !ch.is_ascii() + // Extends Postgres dialect with sharp + PostgreSqlDialect {}.is_identifier_start(ch) || ch == '#' } fn is_identifier_part(&self, ch: char) -> bool { - // Extends Postgres dialect with sharp and UTF-8 multibyte chars - // https://docs.aws.amazon.com/redshift/latest/dg/r_names.html - PostgreSqlDialect {}.is_identifier_part(ch) || ch == '#' || !ch.is_ascii() + // Extends Postgres dialect with sharp + PostgreSqlDialect {}.is_identifier_part(ch) || ch == '#' } /// redshift has `CONVERT(type, value)` instead of `CONVERT(value, type)` @@ -131,12 +129,4 @@ impl Dialect for RedshiftSqlDialect { fn supports_string_literal_backslash_escape(&self) -> bool { true } - - fn supports_select_wildcard_exclude(&self) -> bool { - true - } - - fn supports_select_exclude(&self) -> bool { - true - } } diff --git a/src/dialect/snowflake.rs b/src/dialect/snowflake.rs index 3b1eff39..ea0b94a6 100644 --- a/src/dialect/snowflake.rs +++ b/src/dialect/snowflake.rs @@ -25,8 +25,8 @@ use crate::ast::helpers::stmt_data_loading::{ use crate::ast::{ ColumnOption, ColumnPolicy, ColumnPolicyProperty, CopyIntoSnowflakeKind, Ident, IdentityParameters, IdentityProperty, IdentityPropertyFormatKind, IdentityPropertyKind, - IdentityPropertyOrder, ObjectName, ObjectNamePart, RowAccessPolicy, ShowObjects, SqlOption, - Statement, TagsColumnOption, WrappedCollection, + IdentityPropertyOrder, ObjectName, RowAccessPolicy, ShowObjects, SqlOption, Statement, + TagsColumnOption, WrappedCollection, }; use crate::dialect::{Dialect, Precedence}; use crate::keywords::Keyword; @@ -131,10 +131,6 @@ impl Dialect for SnowflakeDialect { } fn parse_statement(&self, parser: &mut Parser) -> Option> { - if parser.parse_keyword(Keyword::BEGIN) { - return Some(parser.parse_begin_exception_end()); - } - if parser.parse_keywords(&[Keyword::ALTER, Keyword::SESSION]) { // ALTER SESSION let set = match parser.parse_one_of_keywords(&[Keyword::SET, Keyword::UNSET]) { @@ -283,10 +279,6 @@ impl Dialect for SnowflakeDialect { true } - fn supports_left_associative_joins_without_parens(&self) -> bool { - false - } - fn is_reserved_for_identifier(&self, kw: Keyword) -> bool { // Unreserve some keywords that Snowflake accepts as identifiers // See: https://docs.snowflake.com/en/sql-reference/reserved-keywords @@ -301,8 +293,9 @@ impl Dialect for SnowflakeDialect { true } - fn is_column_alias(&self, kw: &Keyword, parser: &mut Parser) -> bool { - match kw { + fn is_select_item_alias(&self, explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool { + explicit + || match kw { // The following keywords can be considered an alias as long as // they are not followed by other tokens that may change their meaning // e.g. `SELECT * EXCEPT (col1) FROM tbl` @@ -318,11 +311,9 @@ impl Dialect for SnowflakeDialect { } // `FETCH` can be considered an alias as long as it's not followed by `FIRST`` or `NEXT` - // which would give it a different meanings, for example: - // `SELECT 1 FETCH FIRST 10 ROWS` - not an alias - // `SELECT 1 FETCH 10` - not an alias - Keyword::FETCH if parser.peek_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT]).is_some() - || matches!(parser.peek_token().token, Token::Number(_, _)) => + // which would give it a different meanings, for example: `SELECT 1 FETCH FIRST 10 ROWS` - not an alias + Keyword::FETCH + if parser.peek_keyword(Keyword::FIRST) || parser.peek_keyword(Keyword::NEXT) => { false } @@ -347,86 +338,6 @@ impl Dialect for SnowflakeDialect { } } - fn is_table_alias(&self, kw: &Keyword, parser: &mut Parser) -> bool { - match kw { - // The following keywords can be considered an alias as long as - // they are not followed by other tokens that may change their meaning - Keyword::LIMIT - | Keyword::RETURNING - | Keyword::INNER - | Keyword::USING - | Keyword::PIVOT - | Keyword::UNPIVOT - | Keyword::EXCEPT - | Keyword::MATCH_RECOGNIZE - | Keyword::OFFSET - if !matches!(parser.peek_token_ref().token, Token::SemiColon | Token::EOF) => - { - false - } - - // `FETCH` can be considered an alias as long as it's not followed by `FIRST`` or `NEXT` - // which would give it a different meanings, for example: - // `SELECT * FROM tbl FETCH FIRST 10 ROWS` - not an alias - // `SELECT * FROM tbl FETCH 10` - not an alias - Keyword::FETCH - if parser - .peek_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT]) - .is_some() - || matches!(parser.peek_token().token, Token::Number(_, _)) => - { - false - } - - // All sorts of join-related keywords can be considered aliases unless additional - // keywords change their meaning. - Keyword::RIGHT | Keyword::LEFT | Keyword::SEMI | Keyword::ANTI - if parser - .peek_one_of_keywords(&[Keyword::JOIN, Keyword::OUTER]) - .is_some() => - { - false - } - Keyword::GLOBAL if parser.peek_keyword(Keyword::FULL) => false, - - // Reserved keywords by the Snowflake dialect, which seem to be less strictive - // than what is listed in `keywords::RESERVED_FOR_TABLE_ALIAS`. The following - // keywords were tested with the this statement: `SELECT .* FROM tbl `. - Keyword::WITH - | Keyword::ORDER - | Keyword::SELECT - | Keyword::WHERE - | Keyword::GROUP - | Keyword::HAVING - | Keyword::LATERAL - | Keyword::UNION - | Keyword::INTERSECT - | Keyword::MINUS - | Keyword::ON - | Keyword::JOIN - | Keyword::INNER - | Keyword::CROSS - | Keyword::FULL - | Keyword::LEFT - | Keyword::RIGHT - | Keyword::NATURAL - | Keyword::USING - | Keyword::ASOF - | Keyword::MATCH_CONDITION - | Keyword::SET - | Keyword::QUALIFY - | Keyword::FOR - | Keyword::START - | Keyword::CONNECT - | Keyword::SAMPLE - | Keyword::TABLESAMPLE - | Keyword::FROM => false, - - // Any other word is considered an alias - _ => true, - } - } - /// See: fn supports_timestamp_versioning(&self) -> bool { true @@ -441,35 +352,6 @@ 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 supports_comma_separated_drop_column_list(&self) -> bool { - true - } - - fn is_identifier_generating_function_name( - &self, - ident: &Ident, - name_parts: &[ObjectNamePart], - ) -> bool { - ident.quote_style.is_none() - && ident.value.to_lowercase() == "identifier" - && !name_parts - .iter() - .any(|p| matches!(p, ObjectNamePart::Function(_))) - } - - // For example: `SELECT IDENTIFIER('alias1').* FROM tbl AS alias1` - fn supports_select_expr_star(&self) -> bool { - true - } - - fn supports_select_wildcard_exclude(&self) -> bool { - true - } } fn parse_file_staging_command(kw: Keyword, parser: &mut Parser) -> Result { @@ -821,7 +703,6 @@ pub fn parse_stage_name_identifier(parser: &mut Parser) -> Result ident.push('~'), Token::Mod => ident.push('%'), Token::Div => ident.push('/'), - Token::Plus => ident.push('+'), Token::Word(w) => ident.push_str(&w.to_string()), _ => return parser.expected("stage name identifier", parser.peek_token()), } @@ -1296,7 +1177,7 @@ fn parse_column_policy_property( parser: &mut Parser, with: bool, ) -> Result { - let policy_name = parser.parse_object_name(false)?; + let policy_name = parser.parse_identifier()?; let using_columns = if parser.parse_keyword(Keyword::USING) { parser.expect_token(&Token::LParen)?; let columns = parser.parse_comma_separated(|p| p.parse_identifier())?; diff --git a/src/keywords.rs b/src/keywords.rs index 9e689a6d..f5c5e567 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -395,7 +395,6 @@ define_keywords!( FUNCTION, FUNCTIONS, FUSION, - FUTURE, GENERAL, GENERATE, GENERATED, @@ -647,7 +646,6 @@ define_keywords!( ORDER, ORDINALITY, ORGANIZATION, - OTHER, OUT, OUTER, OUTPUT, @@ -816,7 +814,6 @@ define_keywords!( SERDE, SERDEPROPERTIES, SERIALIZABLE, - SERVER, SERVICE, SESSION, SESSION_USER, @@ -937,8 +934,6 @@ define_keywords!( TRY, TRY_CAST, TRY_CONVERT, - TSQUERY, - TSVECTOR, TUPLE, TYPE, UBIGINT, @@ -982,7 +977,6 @@ define_keywords!( UUID, VACUUM, VALID, - VALIDATE, VALIDATION_MODE, VALUE, VALUES, @@ -1018,7 +1012,6 @@ define_keywords!( WITHOUT, WITHOUT_ARRAY_WRAPPER, WORK, - WRAPPER, WRITE, XML, XMLNAMESPACES, @@ -1119,7 +1112,6 @@ pub const RESERVED_FOR_COLUMN_ALIAS: &[Keyword] = &[ Keyword::FETCH, Keyword::UNION, Keyword::EXCEPT, - Keyword::EXCLUDE, Keyword::INTERSECT, Keyword::MINUS, Keyword::CLUSTER, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 47b63da8..73cc3e0e 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -222,9 +222,6 @@ pub struct ParserOptions { /// Controls how literal values are unescaped. See /// [`Tokenizer::with_unescape`] for more details. pub unescape: bool, - /// Controls if the parser expects a semi-colon token - /// between statements. Default is `true`. - pub require_semicolon_stmt_delimiter: bool, } impl Default for ParserOptions { @@ -232,7 +229,6 @@ impl Default for ParserOptions { Self { trailing_commas: false, unescape: true, - require_semicolon_stmt_delimiter: true, } } } @@ -440,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()?; @@ -471,10 +467,6 @@ impl<'a> Parser<'a> { expecting_statement_delimiter = false; } - if !self.options.require_semicolon_stmt_delimiter { - expecting_statement_delimiter = false; - } - match self.peek_token().token { Token::EOF => break, @@ -1234,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; @@ -1529,7 +1521,7 @@ impl<'a> Parser<'a> { DataType::Custom(..) => parser_err!("dummy", loc), data_type => Ok(Expr::TypedString { data_type, - value: parser.parse_value()?, + value: parser.parse_value()?.value, }), } })?; @@ -1639,7 +1631,8 @@ 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 ))) } }; @@ -1716,9 +1709,10 @@ impl<'a> Parser<'a> { } fn parse_geometric_type(&mut self, kind: GeometricTypeKind) -> Result { + let value: Value = self.parse_value()?.value; Ok(Expr::TypedString { data_type: DataType::GeometricType(kind), - value: self.parse_value()?, + value, }) } @@ -2583,7 +2577,7 @@ impl<'a> Parser<'a> { trim_characters: None, }) } else if self.consume_token(&Token::Comma) - && dialect_of!(self is DuckDbDialect | SnowflakeDialect | BigQueryDialect | GenericDialect) + && dialect_of!(self is SnowflakeDialect | BigQueryDialect | GenericDialect) { let characters = self.parse_comma_separated(Parser::parse_expr)?; self.expect_token(&Token::RParen)?; @@ -2777,7 +2771,7 @@ impl<'a> Parser<'a> { if self.dialect.supports_dictionary_syntax() { self.prev_token(); // Put back the '{' - return self.parse_dictionary(); + return self.parse_duckdb_struct_literal(); } self.expected("an expression", token) @@ -3040,6 +3034,7 @@ impl<'a> Parser<'a> { where F: FnMut(&mut Parser<'a>) -> Result<(StructField, MatchedTrailingBracket), ParserError>, { + let start_token = self.peek_token(); self.expect_keyword_is(Keyword::STRUCT)?; // Nothing to do if we have no type information. @@ -3052,10 +3047,16 @@ impl<'a> Parser<'a> { let trailing_bracket = loop { let (def, trailing_bracket) = elem_parser(self)?; field_defs.push(def); - // The struct field definition is finished if it occurs `>>` or comma. - if trailing_bracket.0 || !self.consume_token(&Token::Comma) { + if !self.consume_token(&Token::Comma) { break trailing_bracket; } + + // Angle brackets are balanced so we only expect the trailing `>>` after + // we've matched all field types for the current struct. + // e.g. this is invalid syntax `STRUCT>>, INT>(NULL)` + if trailing_bracket.0 { + return parser_err!("unmatched > in STRUCT definition", start_token.span.start); + } }; Ok(( @@ -3075,7 +3076,6 @@ impl<'a> Parser<'a> { Ok(StructField { field_name: Some(field_name), field_type, - options: None, }) }); self.expect_token(&Token::RParen)?; @@ -3109,12 +3109,10 @@ impl<'a> Parser<'a> { let (field_type, trailing_bracket) = self.parse_data_type_helper()?; - let options = self.maybe_parse_options(Keyword::OPTIONS)?; Ok(( StructField { field_name, field_type, - options, }, trailing_bracket, )) @@ -3146,7 +3144,7 @@ impl<'a> Parser<'a> { Ok(fields) } - /// DuckDB and ClickHouse specific: Parse a duckdb [dictionary] or a clickhouse [map] setting + /// DuckDB specific: Parse a duckdb [dictionary] /// /// Syntax: /// @@ -3155,18 +3153,18 @@ impl<'a> Parser<'a> { /// ``` /// /// [dictionary]: https://duckdb.org/docs/sql/data_types/struct#creating-structs - /// [map]: https://clickhouse.com/docs/operations/settings/settings#additional_table_filters - fn parse_dictionary(&mut self) -> Result { + fn parse_duckdb_struct_literal(&mut self) -> Result { self.expect_token(&Token::LBrace)?; - let fields = self.parse_comma_separated0(Self::parse_dictionary_field, Token::RBrace)?; + let fields = + self.parse_comma_separated0(Self::parse_duckdb_dictionary_field, Token::RBrace)?; self.expect_token(&Token::RBrace)?; Ok(Expr::Dictionary(fields)) } - /// Parse a field for a duckdb [dictionary] or a clickhouse [map] setting + /// Parse a field for a duckdb [dictionary] /// /// Syntax /// @@ -3175,8 +3173,7 @@ impl<'a> Parser<'a> { /// ``` /// /// [dictionary]: https://duckdb.org/docs/sql/data_types/struct#creating-structs - /// [map]: https://clickhouse.com/docs/operations/settings/settings#additional_table_filters - fn parse_dictionary_field(&mut self) -> Result { + fn parse_duckdb_dictionary_field(&mut self) -> Result { let key = self.parse_identifier()?; self.expect_token(&Token::Colon)?; @@ -3616,19 +3613,6 @@ impl<'a> Parser<'a> { self.expected("IN or BETWEEN after NOT", self.peek_token()) } } - Keyword::MEMBER => { - if self.parse_keyword(Keyword::OF) { - self.expect_token(&Token::LParen)?; - let array = self.parse_expr()?; - self.expect_token(&Token::RParen)?; - Ok(Expr::MemberOf(MemberOf { - value: Box::new(expr), - array: Box::new(array), - })) - } else { - self.expected("OF after MEMBER", self.peek_token()) - } - } // Can only happen if `get_next_precedence` got out of sync with this function _ => parser_err!( format!("No infix parser for token {:?}", tok.token), @@ -3662,9 +3646,9 @@ impl<'a> Parser<'a> { } /// Parse the `ESCAPE CHAR` portion of `LIKE`, `ILIKE`, and `SIMILAR TO` - pub fn parse_escape_char(&mut self) -> Result, ParserError> { + pub fn parse_escape_char(&mut self) -> Result, ParserError> { if self.parse_keyword(Keyword::ESCAPE) { - Ok(Some(self.parse_value()?.into())) + Ok(Some(self.parse_literal_string()?)) } else { Ok(None) } @@ -3837,7 +3821,7 @@ impl<'a> Parser<'a> { }); } self.expect_token(&Token::LParen)?; - let in_op = match self.maybe_parse(|p| p.parse_query())? { + let in_op = match self.maybe_parse(|p| p.parse_query_body(p.dialect.prec_unknown()))? { Some(subquery) => Expr::InSubquery { expr: Box::new(expr), subquery, @@ -4682,8 +4666,6 @@ impl<'a> Parser<'a> { self.parse_create_procedure(or_alter) } else if self.parse_keyword(Keyword::CONNECTOR) { self.parse_create_connector() - } else if self.parse_keyword(Keyword::SERVER) { - self.parse_pg_create_server() } else { self.expected("an object type after CREATE", self.peek_token()) } @@ -6890,7 +6872,9 @@ impl<'a> Parser<'a> { None }; - let columns = self.parse_parenthesized_index_column_list()?; + self.expect_token(&Token::LParen)?; + let columns = self.parse_comma_separated(Parser::parse_create_index_expr)?; + self.expect_token(&Token::RParen)?; let include = if self.parse_keyword(Keyword::INCLUDE) { self.expect_token(&Token::LParen)?; @@ -7646,22 +7630,9 @@ impl<'a> Parser<'a> { } pub fn parse_procedure_param(&mut self) -> Result { - let mode = if self.parse_keyword(Keyword::IN) { - Some(ArgMode::In) - } else if self.parse_keyword(Keyword::OUT) { - Some(ArgMode::Out) - } else if self.parse_keyword(Keyword::INOUT) { - Some(ArgMode::InOut) - } else { - None - }; let name = self.parse_identifier()?; let data_type = self.parse_data_type()?; - Ok(ProcedureParam { - name, - data_type, - mode, - }) + Ok(ProcedureParam { name, data_type }) } pub fn parse_column_def(&mut self) -> Result { @@ -7892,7 +7863,7 @@ impl<'a> Parser<'a> { } pub(crate) fn parse_tag(&mut self) -> Result { - let name = self.parse_object_name(false)?; + let name = self.parse_identifier()?; self.expect_token(&Token::Eq)?; let value = self.parse_literal_string()?; @@ -8103,7 +8074,7 @@ impl<'a> Parser<'a> { let index_name = self.parse_optional_ident()?; let index_type = self.parse_optional_using_then_index_type()?; - let columns = self.parse_parenthesized_index_column_list()?; + let columns = self.parse_parenthesized_column_list(Mandatory, false)?; let index_options = self.parse_index_options()?; let characteristics = self.parse_constraint_characteristics()?; Ok(Some(TableConstraint::Unique { @@ -8125,7 +8096,7 @@ impl<'a> Parser<'a> { let index_name = self.parse_optional_ident()?; let index_type = self.parse_optional_using_then_index_type()?; - let columns = self.parse_parenthesized_index_column_list()?; + let columns = self.parse_parenthesized_column_list(Mandatory, false)?; let index_options = self.parse_index_options()?; let characteristics = self.parse_constraint_characteristics()?; Ok(Some(TableConstraint::PrimaryKey { @@ -8203,7 +8174,7 @@ impl<'a> Parser<'a> { }; let index_type = self.parse_optional_using_then_index_type()?; - let columns = self.parse_parenthesized_index_column_list()?; + let columns = self.parse_parenthesized_column_list(Mandatory, false)?; Ok(Some(TableConstraint::Index { display_as_key, @@ -8232,7 +8203,7 @@ impl<'a> Parser<'a> { let opt_index_name = self.parse_optional_ident()?; - let columns = self.parse_parenthesized_index_column_list()?; + let columns = self.parse_parenthesized_column_list(Mandatory, false)?; Ok(Some(TableConstraint::FulltextOrSpatial { fulltext, @@ -8499,11 +8470,7 @@ impl<'a> Parser<'a> { pub fn parse_alter_table_operation(&mut self) -> Result { let operation = if self.parse_keyword(Keyword::ADD) { if let Some(constraint) = self.parse_optional_table_constraint()? { - let not_valid = self.parse_keywords(&[Keyword::NOT, Keyword::VALID]); - AlterTableOperation::AddConstraint { - constraint, - not_valid, - } + AlterTableOperation::AddConstraint(constraint) } else if dialect_of!(self is ClickHouseDialect|GenericDialect) && self.parse_keyword(Keyword::PROJECTION) { @@ -8683,15 +8650,11 @@ impl<'a> Parser<'a> { } else { let has_column_keyword = self.parse_keyword(Keyword::COLUMN); // [ COLUMN ] let if_exists = self.parse_keywords(&[Keyword::IF, Keyword::EXISTS]); - let column_names = if self.dialect.supports_comma_separated_drop_column_list() { - self.parse_comma_separated(Parser::parse_identifier)? - } else { - vec![self.parse_identifier()?] - }; + let column_name = self.parse_identifier()?; let drop_behavior = self.parse_optional_drop_behavior(); AlterTableOperation::DropColumn { has_column_keyword, - column_names, + column_name, if_exists, drop_behavior, } @@ -8764,10 +8727,16 @@ impl<'a> Parser<'a> { } } else if self.parse_keywords(&[Keyword::DROP, Keyword::DEFAULT]) { AlterColumnOperation::DropDefault {} - } else if self.parse_keywords(&[Keyword::SET, Keyword::DATA, Keyword::TYPE]) { - self.parse_set_data_type(true)? - } else if self.parse_keyword(Keyword::TYPE) { - self.parse_set_data_type(false)? + } else if self.parse_keywords(&[Keyword::SET, Keyword::DATA, Keyword::TYPE]) + || (is_postgresql && self.parse_keyword(Keyword::TYPE)) + { + let data_type = self.parse_data_type()?; + let using = if is_postgresql && self.parse_keyword(Keyword::USING) { + Some(self.parse_expr()?) + } else { + None + }; + AlterColumnOperation::SetDataType { data_type, using } } else if self.parse_keywords(&[Keyword::ADD, Keyword::GENERATED]) { let generated_as = if self.parse_keyword(Keyword::ALWAYS) { Some(GeneratedAs::Always) @@ -8916,9 +8885,6 @@ impl<'a> Parser<'a> { }; AlterTableOperation::ReplicaIdentity { identity } - } else if self.parse_keywords(&[Keyword::VALIDATE, Keyword::CONSTRAINT]) { - let name = self.parse_identifier()?; - AlterTableOperation::ValidateConstraint { name } } else { let options: Vec = self.parse_options_with_keywords(&[Keyword::SET, Keyword::TBLPROPERTIES])?; @@ -8936,22 +8902,6 @@ impl<'a> Parser<'a> { Ok(operation) } - fn parse_set_data_type(&mut self, had_set: bool) -> Result { - let data_type = self.parse_data_type()?; - let using = if self.dialect.supports_alter_column_type_using() - && self.parse_keyword(Keyword::USING) - { - Some(self.parse_expr()?) - } else { - None - }; - Ok(AlterColumnOperation::SetDataType { - data_type, - using, - had_set, - }) - } - fn parse_part_or_partition(&mut self) -> Result { let keyword = self.expect_one_of_keywords(&[Keyword::PART, Keyword::PARTITION])?; match keyword { @@ -9963,12 +9913,6 @@ impl<'a> Parser<'a> { Ok(DataType::Unsigned) } } - Keyword::TSVECTOR if dialect_is!(dialect is PostgreSqlDialect | GenericDialect) => { - Ok(DataType::TsVector) - } - Keyword::TSQUERY if dialect_is!(dialect is PostgreSqlDialect | GenericDialect) => { - Ok(DataType::TsQuery) - } _ => { self.prev_token(); let type_name = self.parse_object_name(false)?; @@ -10031,48 +9975,6 @@ impl<'a> Parser<'a> { Ok(IdentWithAlias { ident, alias }) } - /// Parse `identifier [AS] identifier` where the AS keyword is optional - fn parse_identifier_with_optional_alias(&mut self) -> Result { - let ident = self.parse_identifier()?; - let _after_as = self.parse_keyword(Keyword::AS); - let alias = self.parse_identifier()?; - Ok(IdentWithAlias { ident, alias }) - } - - /// Parse comma-separated list of parenthesized queries for pipe operators - fn parse_pipe_operator_queries(&mut self) -> Result, ParserError> { - self.parse_comma_separated(|parser| { - parser.expect_token(&Token::LParen)?; - let query = parser.parse_query()?; - parser.expect_token(&Token::RParen)?; - Ok(*query) - }) - } - - /// Parse set quantifier for pipe operators that require DISTINCT. E.g. INTERSECT and EXCEPT - fn parse_distinct_required_set_quantifier( - &mut self, - operator_name: &str, - ) -> Result { - let quantifier = self.parse_set_quantifier(&Some(SetOperator::Intersect)); - match quantifier { - SetQuantifier::Distinct | SetQuantifier::DistinctByName => Ok(quantifier), - _ => Err(ParserError::ParserError(format!( - "{operator_name} pipe operator requires DISTINCT modifier", - ))), - } - } - - /// Parse optional identifier alias (with or without AS keyword) - fn parse_identifier_optional_alias(&mut self) -> Result, ParserError> { - if self.parse_keyword(Keyword::AS) { - Ok(Some(self.parse_identifier()?)) - } else { - // Check if the next token is an identifier (implicit alias) - self.maybe_parse(|parser| parser.parse_identifier()) - } - } - /// Optionally parses an alias for a select list item fn maybe_parse_select_item_alias(&mut self) -> Result, ParserError> { fn validator(explicit: bool, kw: &Keyword, parser: &mut Parser) -> bool { @@ -10361,14 +10263,49 @@ impl<'a> Parser<'a> { } } - /// Parse a possibly qualified, possibly quoted identifier, e.g. - /// `foo` or `myschema."table" - /// - /// The `in_table_clause` parameter indicates whether the object name is a table in a FROM, JOIN, - /// or similar table clause. Currently, this is used only to support unquoted hyphenated identifiers - /// in this context on BigQuery. - pub fn parse_object_name(&mut self, in_table_clause: bool) -> Result { - self.parse_object_name_inner(in_table_clause, false) + /// Parse a possibly qualified, possibly quoted identifier, optionally allowing for wildcards, + /// e.g. *, *.*, `foo`.*, or "foo"."bar" + fn parse_object_name_with_wildcards( + &mut self, + in_table_clause: bool, + allow_wildcards: bool, + ) -> Result { + let mut idents = vec![]; + + if dialect_of!(self is BigQueryDialect) && in_table_clause { + loop { + let (ident, end_with_period) = self.parse_unquoted_hyphenated_identifier()?; + idents.push(ident); + if !self.consume_token(&Token::Period) && !end_with_period { + break; + } + } + } else { + loop { + let ident = if allow_wildcards && self.peek_token().token == Token::Mul { + let span = self.next_token().span; + Ident { + value: Token::Mul.to_string(), + quote_style: None, + span, + } + } else { + if self.dialect.supports_object_name_double_dot_notation() + && idents.len() == 1 + && self.consume_token(&Token::Period) + { + // Empty string here means default schema + idents.push(Ident::new("")); + } + self.parse_identifier()? + }; + idents.push(ident); + if !self.consume_token(&Token::Period) { + break; + } + } + } + Ok(ObjectName::from(idents)) } /// Parse a possibly qualified, possibly quoted identifier, e.g. @@ -10377,76 +10314,19 @@ impl<'a> Parser<'a> { /// The `in_table_clause` parameter indicates whether the object name is a table in a FROM, JOIN, /// or similar table clause. Currently, this is used only to support unquoted hyphenated identifiers /// in this context on BigQuery. - /// - /// The `allow_wildcards` parameter indicates whether to allow for wildcards in the object name - /// e.g. *, *.*, `foo`.*, or "foo"."bar" - fn parse_object_name_inner( - &mut self, - in_table_clause: bool, - allow_wildcards: bool, - ) -> Result { - let mut parts = vec![]; - if dialect_of!(self is BigQueryDialect) && in_table_clause { - loop { - let (ident, end_with_period) = self.parse_unquoted_hyphenated_identifier()?; - parts.push(ObjectNamePart::Identifier(ident)); - if !self.consume_token(&Token::Period) && !end_with_period { - break; - } - } - } else { - loop { - if allow_wildcards && self.peek_token().token == Token::Mul { - let span = self.next_token().span; - parts.push(ObjectNamePart::Identifier(Ident { - value: Token::Mul.to_string(), - quote_style: None, - span, - })); - } else if dialect_of!(self is BigQueryDialect) && in_table_clause { - let (ident, end_with_period) = self.parse_unquoted_hyphenated_identifier()?; - parts.push(ObjectNamePart::Identifier(ident)); - if !self.consume_token(&Token::Period) && !end_with_period { - break; - } - } else if self.dialect.supports_object_name_double_dot_notation() - && parts.len() == 1 - && matches!(self.peek_token().token, Token::Period) - { - // Empty string here means default schema - parts.push(ObjectNamePart::Identifier(Ident::new(""))); - } else { - let ident = self.parse_identifier()?; - let part = if self - .dialect - .is_identifier_generating_function_name(&ident, &parts) - { - self.expect_token(&Token::LParen)?; - let args: Vec = - self.parse_comma_separated0(Self::parse_function_args, Token::RParen)?; - self.expect_token(&Token::RParen)?; - ObjectNamePart::Function(ObjectNamePartFunction { name: ident, args }) - } else { - ObjectNamePart::Identifier(ident) - }; - parts.push(part); - } - - if !self.consume_token(&Token::Period) { - break; - } - } - } + pub fn parse_object_name(&mut self, in_table_clause: bool) -> Result { + let ObjectName(mut idents) = + self.parse_object_name_with_wildcards(in_table_clause, false)?; // BigQuery accepts any number of quoted identifiers of a table name. // https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_identifiers if dialect_of!(self is BigQueryDialect) - && parts.iter().any(|part| { + && idents.iter().any(|part| { part.as_ident() .is_some_and(|ident| ident.value.contains('.')) }) { - parts = parts + idents = idents .into_iter() .flat_map(|part| match part.as_ident() { Some(ident) => ident @@ -10465,7 +10345,7 @@ impl<'a> Parser<'a> { .collect() } - Ok(ObjectName(parts)) + Ok(ObjectName(idents)) } /// Parse identifiers @@ -10686,7 +10566,17 @@ 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 = self.parse_view_column_options()?; + 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 data_type = if dialect_of!(self is ClickHouseDialect) { Some(self.parse_data_type()?) } else { @@ -10699,25 +10589,6 @@ 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( @@ -10728,14 +10599,6 @@ impl<'a> Parser<'a> { self.parse_parenthesized_column_list_inner(optional, allow_empty, |p| p.parse_identifier()) } - /// Parses a parenthesized comma-separated list of index columns, which can be arbitrary - /// expressions with ordering information (and an opclass in some dialects). - fn parse_parenthesized_index_column_list(&mut self) -> Result, ParserError> { - self.parse_parenthesized_column_list_inner(Mandatory, false, |p| { - p.parse_create_index_expr() - }) - } - /// Parses a parenthesized comma-separated list of qualified, possibly quoted identifiers. /// For example: `(db1.sc1.tbl1.col1, db1.sc1.tbl1."col 2", ...)` pub fn parse_parenthesized_qualified_column_list( @@ -11241,19 +11104,6 @@ impl<'a> Parser<'a> { Keyword::AGGREGATE, Keyword::ORDER, Keyword::TABLESAMPLE, - Keyword::RENAME, - Keyword::UNION, - Keyword::INTERSECT, - Keyword::EXCEPT, - Keyword::CALL, - Keyword::PIVOT, - Keyword::UNPIVOT, - Keyword::JOIN, - Keyword::INNER, - Keyword::LEFT, - Keyword::RIGHT, - Keyword::FULL, - Keyword::CROSS, ])?; match kw { Keyword::SELECT => { @@ -11320,121 +11170,6 @@ impl<'a> Parser<'a> { let sample = self.parse_table_sample(TableSampleModifier::TableSample)?; pipe_operators.push(PipeOperator::TableSample { sample }); } - Keyword::RENAME => { - let mappings = - self.parse_comma_separated(Parser::parse_identifier_with_optional_alias)?; - pipe_operators.push(PipeOperator::Rename { mappings }); - } - Keyword::UNION => { - let set_quantifier = self.parse_set_quantifier(&Some(SetOperator::Union)); - let queries = self.parse_pipe_operator_queries()?; - pipe_operators.push(PipeOperator::Union { - set_quantifier, - queries, - }); - } - Keyword::INTERSECT => { - let set_quantifier = - self.parse_distinct_required_set_quantifier("INTERSECT")?; - let queries = self.parse_pipe_operator_queries()?; - pipe_operators.push(PipeOperator::Intersect { - set_quantifier, - queries, - }); - } - Keyword::EXCEPT => { - let set_quantifier = self.parse_distinct_required_set_quantifier("EXCEPT")?; - let queries = self.parse_pipe_operator_queries()?; - pipe_operators.push(PipeOperator::Except { - set_quantifier, - queries, - }); - } - Keyword::CALL => { - let function_name = self.parse_object_name(false)?; - let function_expr = self.parse_function(function_name)?; - if let Expr::Function(function) = function_expr { - let alias = self.parse_identifier_optional_alias()?; - pipe_operators.push(PipeOperator::Call { function, alias }); - } else { - return Err(ParserError::ParserError( - "Expected function call after CALL".to_string(), - )); - } - } - Keyword::PIVOT => { - self.expect_token(&Token::LParen)?; - let aggregate_functions = - self.parse_comma_separated(Self::parse_aliased_function_call)?; - self.expect_keyword_is(Keyword::FOR)?; - let value_column = self.parse_period_separated(|p| p.parse_identifier())?; - self.expect_keyword_is(Keyword::IN)?; - - self.expect_token(&Token::LParen)?; - let value_source = if self.parse_keyword(Keyword::ANY) { - let order_by = if self.parse_keywords(&[Keyword::ORDER, Keyword::BY]) { - self.parse_comma_separated(Parser::parse_order_by_expr)? - } else { - vec![] - }; - PivotValueSource::Any(order_by) - } else if self.peek_sub_query() { - PivotValueSource::Subquery(self.parse_query()?) - } else { - PivotValueSource::List( - self.parse_comma_separated(Self::parse_expr_with_alias)?, - ) - }; - self.expect_token(&Token::RParen)?; - self.expect_token(&Token::RParen)?; - - let alias = self.parse_identifier_optional_alias()?; - - pipe_operators.push(PipeOperator::Pivot { - aggregate_functions, - value_column, - value_source, - alias, - }); - } - Keyword::UNPIVOT => { - self.expect_token(&Token::LParen)?; - let value_column = self.parse_identifier()?; - self.expect_keyword(Keyword::FOR)?; - let name_column = self.parse_identifier()?; - self.expect_keyword(Keyword::IN)?; - - self.expect_token(&Token::LParen)?; - let unpivot_columns = self.parse_comma_separated(Parser::parse_identifier)?; - self.expect_token(&Token::RParen)?; - - self.expect_token(&Token::RParen)?; - - let alias = self.parse_identifier_optional_alias()?; - - pipe_operators.push(PipeOperator::Unpivot { - value_column, - name_column, - unpivot_columns, - alias, - }); - } - Keyword::JOIN - | Keyword::INNER - | Keyword::LEFT - | Keyword::RIGHT - | Keyword::FULL - | Keyword::CROSS => { - self.prev_token(); - let mut joins = self.parse_joins()?; - if joins.len() != 1 { - return Err(ParserError::ParserError( - "Join pipe operator must have a single join".to_string(), - )); - } - let join = joins.swap_remove(0); - pipe_operators.push(PipeOperator::Join(join)) - } unhandled => { return Err(ParserError::ParserError(format!( "`expect_one_of_keywords` further up allowed unhandled keyword: {unhandled:?}" @@ -11452,7 +11187,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_expr()?; + let value = p.parse_value()?.value; Ok(Setting { key, value }) })?; Some(key_values) @@ -11748,7 +11483,6 @@ impl<'a> Parser<'a> { top: None, top_before_distinct: false, projection: vec![], - exclude: None, into: None, from, lateral_views: vec![], @@ -11791,12 +11525,6 @@ impl<'a> Parser<'a> { self.parse_projection()? }; - let exclude = if self.dialect.supports_select_exclude() { - self.parse_optional_select_item_exclude()? - } else { - None - }; - let into = if self.parse_keyword(Keyword::INTO) { Some(self.parse_select_into()?) } else { @@ -11930,7 +11658,6 @@ impl<'a> Parser<'a> { top, top_before_distinct, projection, - exclude, into, from, lateral_views, @@ -12738,11 +12465,7 @@ impl<'a> Parser<'a> { }; let mut relation = self.parse_table_factor()?; - if !self - .dialect - .supports_left_associative_joins_without_parens() - && self.peek_parens_less_nested_join() - { + if self.peek_parens_less_nested_join() { let joins = self.parse_joins()?; relation = TableFactor::NestedJoin { table_with_joins: Box::new(TableWithJoins { relation, joins }), @@ -13832,15 +13555,6 @@ impl<'a> Parser<'a> { let with_grant_option = self.parse_keywords(&[Keyword::WITH, Keyword::GRANT, Keyword::OPTION]); - let current_grants = - if self.parse_keywords(&[Keyword::COPY, Keyword::CURRENT, Keyword::GRANTS]) { - Some(CurrentGrantsKind::CopyCurrentGrants) - } else if self.parse_keywords(&[Keyword::REVOKE, Keyword::CURRENT, Keyword::GRANTS]) { - Some(CurrentGrantsKind::RevokeCurrentGrants) - } else { - None - }; - let as_grantor = if self.parse_keywords(&[Keyword::AS]) { Some(self.parse_identifier()?) } else { @@ -13860,7 +13574,6 @@ impl<'a> Parser<'a> { with_grant_option, as_grantor, granted_by, - current_grants, }) } @@ -13912,7 +13625,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), )])); }; } @@ -13949,82 +13662,6 @@ impl<'a> Parser<'a> { Some(GrantObjects::AllTablesInSchema { schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, }) - } else if self.parse_keywords(&[ - Keyword::ALL, - Keyword::EXTERNAL, - Keyword::TABLES, - Keyword::IN, - Keyword::SCHEMA, - ]) { - Some(GrantObjects::AllExternalTablesInSchema { - schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, - }) - } else if self.parse_keywords(&[ - Keyword::ALL, - Keyword::VIEWS, - Keyword::IN, - Keyword::SCHEMA, - ]) { - Some(GrantObjects::AllViewsInSchema { - schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, - }) - } else if self.parse_keywords(&[ - Keyword::ALL, - Keyword::MATERIALIZED, - Keyword::VIEWS, - Keyword::IN, - Keyword::SCHEMA, - ]) { - Some(GrantObjects::AllMaterializedViewsInSchema { - 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::EXTERNAL, - Keyword::TABLES, - Keyword::IN, - Keyword::SCHEMA, - ]) { - Some(GrantObjects::FutureExternalTablesInSchema { - 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::FUTURE, - Keyword::MATERIALIZED, - Keyword::VIEWS, - Keyword::IN, - Keyword::SCHEMA, - ]) { - Some(GrantObjects::FutureMaterializedViewsInSchema { - schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, - }) } else if self.parse_keywords(&[ Keyword::ALL, Keyword::SEQUENCES, @@ -14034,35 +13671,26 @@ impl<'a> Parser<'a> { Some(GrantObjects::AllSequencesInSchema { schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, }) - } else if self.parse_keywords(&[ - Keyword::FUTURE, - Keyword::SEQUENCES, - Keyword::IN, - Keyword::SCHEMA, - ]) { - Some(GrantObjects::FutureSequencesInSchema { - schemas: self.parse_comma_separated(|p| p.parse_object_name(false))?, - }) } else if self.parse_keywords(&[Keyword::RESOURCE, Keyword::MONITOR]) { - Some(GrantObjects::ResourceMonitors( - self.parse_comma_separated(|p| p.parse_object_name(false))?, - )) + Some(GrantObjects::ResourceMonitors(self.parse_comma_separated( + |p| p.parse_object_name_with_wildcards(false, true), + )?)) } else if self.parse_keywords(&[Keyword::COMPUTE, Keyword::POOL]) { - Some(GrantObjects::ComputePools( - self.parse_comma_separated(|p| p.parse_object_name(false))?, - )) + Some(GrantObjects::ComputePools(self.parse_comma_separated( + |p| p.parse_object_name_with_wildcards(false, true), + )?)) } else if self.parse_keywords(&[Keyword::FAILOVER, Keyword::GROUP]) { - Some(GrantObjects::FailoverGroup( - self.parse_comma_separated(|p| p.parse_object_name(false))?, - )) + Some(GrantObjects::FailoverGroup(self.parse_comma_separated( + |p| p.parse_object_name_with_wildcards(false, true), + )?)) } else if self.parse_keywords(&[Keyword::REPLICATION, Keyword::GROUP]) { - Some(GrantObjects::ReplicationGroup( - self.parse_comma_separated(|p| p.parse_object_name(false))?, - )) + Some(GrantObjects::ReplicationGroup(self.parse_comma_separated( + |p| p.parse_object_name_with_wildcards(false, true), + )?)) } else if self.parse_keywords(&[Keyword::EXTERNAL, Keyword::VOLUME]) { - Some(GrantObjects::ExternalVolumes( - self.parse_comma_separated(|p| p.parse_object_name(false))?, - )) + Some(GrantObjects::ExternalVolumes(self.parse_comma_separated( + |p| p.parse_object_name_with_wildcards(false, true), + )?)) } else { let object_type = self.parse_one_of_keywords(&[ Keyword::SEQUENCE, @@ -14077,11 +13705,9 @@ impl<'a> Parser<'a> { Keyword::INTEGRATION, Keyword::USER, Keyword::CONNECTION, - Keyword::PROCEDURE, - Keyword::FUNCTION, ]); let objects = - self.parse_comma_separated(|p| p.parse_object_name_inner(false, true)); + self.parse_comma_separated(|p| p.parse_object_name_with_wildcards(false, true)); match object_type { Some(Keyword::DATABASE) => Some(GrantObjects::Databases(objects?)), Some(Keyword::SCHEMA) => Some(GrantObjects::Schemas(objects?)), @@ -14091,13 +13717,6 @@ impl<'a> Parser<'a> { Some(Keyword::VIEW) => Some(GrantObjects::Views(objects?)), Some(Keyword::USER) => Some(GrantObjects::Users(objects?)), Some(Keyword::CONNECTION) => Some(GrantObjects::Connections(objects?)), - kw @ (Some(Keyword::PROCEDURE) | Some(Keyword::FUNCTION)) => { - if let Some(name) = objects?.first() { - self.parse_grant_procedure_or_function(name, &kw)? - } else { - self.expected("procedure or function name", self.peek_token())? - } - } Some(Keyword::TABLE) | None => Some(GrantObjects::Tables(objects?)), _ => unreachable!(), } @@ -14109,31 +13728,6 @@ impl<'a> Parser<'a> { Ok((privileges, objects)) } - fn parse_grant_procedure_or_function( - &mut self, - name: &ObjectName, - kw: &Option, - ) -> Result, ParserError> { - let arg_types = if self.consume_token(&Token::LParen) { - let list = self.parse_comma_separated0(Self::parse_data_type, Token::RParen)?; - self.expect_token(&Token::RParen)?; - list - } else { - vec![] - }; - match kw { - Some(Keyword::PROCEDURE) => Ok(Some(GrantObjects::Procedure { - name: name.clone(), - arg_types, - })), - Some(Keyword::FUNCTION) => Ok(Some(GrantObjects::Function { - name: name.clone(), - arg_types, - })), - _ => self.expected("procedure or function keywords", self.peek_token())?, - } - } - pub fn parse_grant_permission(&mut self) -> Result { fn parse_columns(parser: &mut Parser) -> Result>, ParserError> { let columns = parser.parse_parenthesized_column_list(Optional, false)?; @@ -15001,7 +14595,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( @@ -15068,7 +14662,8 @@ impl<'a> Parser<'a> { } else { None }; - let opt_exclude = if opt_ilike.is_none() && self.dialect.supports_select_wildcard_exclude() + let opt_exclude = if opt_ilike.is_none() + && dialect_of!(self is GenericDialect | DuckDbDialect | SnowflakeDialect) { self.parse_optional_select_item_exclude()? } else { @@ -15420,8 +15015,7 @@ impl<'a> Parser<'a> { /// Parse a FETCH clause pub fn parse_fetch(&mut self) -> Result { - let _ = self.parse_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT]); - + self.expect_one_of_keywords(&[Keyword::FIRST, Keyword::NEXT])?; let (quantity, percent) = if self .parse_one_of_keywords(&[Keyword::ROW, Keyword::ROWS]) .is_some() @@ -15430,16 +15024,16 @@ impl<'a> Parser<'a> { } else { let quantity = Expr::Value(self.parse_value()?); let percent = self.parse_keyword(Keyword::PERCENT); - let _ = self.parse_one_of_keywords(&[Keyword::ROW, Keyword::ROWS]); + self.expect_one_of_keywords(&[Keyword::ROW, Keyword::ROWS])?; (Some(quantity), percent) }; - let with_ties = if self.parse_keyword(Keyword::ONLY) { false + } else if self.parse_keywords(&[Keyword::WITH, Keyword::TIES]) { + true } else { - self.parse_keywords(&[Keyword::WITH, Keyword::TIES]) + return self.expected("one of ONLY or WITH TIES", self.peek_token()); }; - Ok(Fetch { with_ties, percent, @@ -15502,7 +15096,7 @@ impl<'a> Parser<'a> { transaction: Some(BeginTransactionKind::Transaction), modifier: None, statements: vec![], - exception: None, + exception_statements: None, has_end_keyword: false, }) } @@ -15534,56 +15128,11 @@ impl<'a> Parser<'a> { transaction, modifier, statements: vec![], - exception: None, + exception_statements: None, has_end_keyword: false, }) } - pub fn parse_begin_exception_end(&mut self) -> Result { - let statements = self.parse_statement_list(&[Keyword::EXCEPTION, Keyword::END])?; - - let exception = if self.parse_keyword(Keyword::EXCEPTION) { - let mut when = Vec::new(); - - // We can have multiple `WHEN` arms so we consume all cases until `END` - while !self.peek_keyword(Keyword::END) { - self.expect_keyword(Keyword::WHEN)?; - - // Each `WHEN` case can have one or more conditions, e.g. - // WHEN EXCEPTION_1 [OR EXCEPTION_2] THEN - // So we parse identifiers until the `THEN` keyword. - let mut idents = Vec::new(); - - while !self.parse_keyword(Keyword::THEN) { - let ident = self.parse_identifier()?; - idents.push(ident); - - self.maybe_parse(|p| p.expect_keyword(Keyword::OR))?; - } - - let statements = self.parse_statement_list(&[Keyword::WHEN, Keyword::END])?; - - when.push(ExceptionWhen { idents, statements }); - } - - Some(when) - } else { - None - }; - - self.expect_keyword(Keyword::END)?; - - Ok(Statement::StartTransaction { - begin: true, - statements, - exception, - has_end_keyword: true, - transaction: None, - modifier: None, - modes: Default::default(), - }) - } - pub fn parse_end(&mut self) -> Result { let modifier = if !self.dialect.supports_end_transaction_modifier() { None @@ -16154,49 +15703,6 @@ impl<'a> Parser<'a> { Ok(sequence_options) } - /// Parse a `CREATE SERVER` statement. - /// - /// See [Statement::CreateServer] - pub fn parse_pg_create_server(&mut self) -> Result { - let ine = self.parse_keywords(&[Keyword::IF, Keyword::NOT, Keyword::EXISTS]); - let name = self.parse_object_name(false)?; - - let server_type = if self.parse_keyword(Keyword::TYPE) { - Some(self.parse_identifier()?) - } else { - None - }; - - let version = if self.parse_keyword(Keyword::VERSION) { - Some(self.parse_identifier()?) - } else { - None - }; - - self.expect_keywords(&[Keyword::FOREIGN, Keyword::DATA, Keyword::WRAPPER])?; - let foreign_data_wrapper = self.parse_object_name(false)?; - - let mut options = None; - if self.parse_keyword(Keyword::OPTIONS) { - self.expect_token(&Token::LParen)?; - options = Some(self.parse_comma_separated(|p| { - let key = p.parse_identifier()?; - let value = p.parse_identifier()?; - Ok(CreateServerOption { key, value }) - })?); - self.expect_token(&Token::RParen)?; - } - - Ok(Statement::CreateServer(CreateServerStatement { - name, - if_not_exists: ine, - server_type, - version, - foreign_data_wrapper, - options, - })) - } - /// The index of the first unprocessed token. pub fn index(&self) -> usize { self.index @@ -16220,13 +15726,6 @@ 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])?; @@ -16235,7 +15734,6 @@ impl<'a> Parser<'a> { name, or_alter, params, - language, body, }) } @@ -16334,9 +15832,9 @@ impl<'a> Parser<'a> { fn parse_parenthesized_identifiers(&mut self) -> Result, ParserError> { self.expect_token(&Token::LParen)?; - let idents = self.parse_comma_separated0(|p| p.parse_identifier(), Token::RParen)?; + let partitions = self.parse_comma_separated(|p| p.parse_identifier())?; self.expect_token(&Token::RParen)?; - Ok(idents) + Ok(partitions) } fn parse_column_position(&mut self) -> Result, ParserError> { @@ -16982,20 +16480,6 @@ mod tests { }}; } - fn mk_expected_col(name: &str) -> IndexColumn { - IndexColumn { - column: OrderByExpr { - expr: Expr::Identifier(name.into()), - options: OrderByOptions { - asc: None, - nulls_first: None, - }, - with_fill: None, - }, - operator_class: None, - } - } - let dialect = TestedDialects::new(vec![Box::new(GenericDialect {}), Box::new(MySqlDialect {})]); @@ -17006,7 +16490,7 @@ mod tests { display_as_key: false, name: None, index_type: None, - columns: vec![mk_expected_col("c1")], + columns: vec![Ident::new("c1")], } ); @@ -17017,7 +16501,7 @@ mod tests { display_as_key: true, name: None, index_type: None, - columns: vec![mk_expected_col("c1")], + columns: vec![Ident::new("c1")], } ); @@ -17028,7 +16512,7 @@ mod tests { display_as_key: false, name: Some(Ident::with_quote('\'', "index")), index_type: None, - columns: vec![mk_expected_col("c1"), mk_expected_col("c2")], + columns: vec![Ident::new("c1"), Ident::new("c2")], } ); @@ -17039,7 +16523,7 @@ mod tests { display_as_key: false, name: None, index_type: Some(IndexType::BTree), - columns: vec![mk_expected_col("c1")], + columns: vec![Ident::new("c1")], } ); @@ -17050,7 +16534,7 @@ mod tests { display_as_key: false, name: None, index_type: Some(IndexType::Hash), - columns: vec![mk_expected_col("c1")], + columns: vec![Ident::new("c1")], } ); @@ -17061,7 +16545,7 @@ mod tests { display_as_key: false, name: Some(Ident::new("idx_name")), index_type: Some(IndexType::BTree), - columns: vec![mk_expected_col("c1")], + columns: vec![Ident::new("c1")], } ); @@ -17072,7 +16556,7 @@ mod tests { display_as_key: false, name: Some(Ident::new("idx_name")), index_type: Some(IndexType::Hash), - columns: vec![mk_expected_col("c1")], + columns: vec![Ident::new("c1")], } ); } diff --git a/src/test_utils.rs b/src/test_utils.rs index 654f2723..24c0ca57 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); }); } } @@ -294,11 +294,6 @@ pub fn all_dialects() -> TestedDialects { ]) } -// Returns all available dialects with the specified parser options -pub fn all_dialects_with_options(options: ParserOptions) -> TestedDialects { - TestedDialects::new_with_options(all_dialects().dialects, options) -} - /// Returns all dialects matching the given predicate. pub fn all_dialects_where(predicate: F) -> TestedDialects where @@ -371,11 +366,6 @@ 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), @@ -458,52 +448,3 @@ pub fn call(function: &str, args: impl IntoIterator) -> Expr { within_group: vec![], }) } - -/// Gets the first index column (mysql calls it a key part) of the first index found in a -/// [`Statement::CreateIndex`], [`Statement::CreateTable`], or [`Statement::AlterTable`]. -pub fn index_column(stmt: Statement) -> Expr { - match stmt { - Statement::CreateIndex(CreateIndex { columns, .. }) => { - columns.first().unwrap().column.expr.clone() - } - Statement::CreateTable(CreateTable { constraints, .. }) => { - match constraints.first().unwrap() { - TableConstraint::Index { columns, .. } => { - columns.first().unwrap().column.expr.clone() - } - TableConstraint::Unique { columns, .. } => { - columns.first().unwrap().column.expr.clone() - } - TableConstraint::PrimaryKey { columns, .. } => { - columns.first().unwrap().column.expr.clone() - } - TableConstraint::FulltextOrSpatial { columns, .. } => { - columns.first().unwrap().column.expr.clone() - } - _ => panic!("Expected an index, unique, primary, full text, or spatial constraint (foreign key does not support general key part expressions)"), - } - } - Statement::AlterTable { operations, .. } => match operations.first().unwrap() { - AlterTableOperation::AddConstraint { constraint, .. } => { - match constraint { - TableConstraint::Index { columns, .. } => { - columns.first().unwrap().column.expr.clone() - } - TableConstraint::Unique { columns, .. } => { - columns.first().unwrap().column.expr.clone() - } - TableConstraint::PrimaryKey { columns, .. } => { - columns.first().unwrap().column.expr.clone() - } - TableConstraint::FulltextOrSpatial { - columns, - .. - } => columns.first().unwrap().column.expr.clone(), - _ => panic!("Expected an index, unique, primary, full text, or spatial constraint (foreign key does not support general key part expressions)"), - } - } - _ => panic!("Expected a constraint"), - }, - _ => panic!("Expected CREATE INDEX, ALTER TABLE, or CREATE TABLE, got: {stmt:?}"), - } -} diff --git a/src/tokenizer.rs b/src/tokenizer.rs index 8382a534..afe1e35c 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: {result:x}"), + message: format!("Invalid unicode character: {:x}", result), 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_bigquery.rs b/tests/sqlparser_bigquery.rs index 2ba54d3e..6a303577 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -261,10 +261,10 @@ fn parse_at_at_identifier() { #[test] fn parse_begin() { - let sql = r#"BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2; RAISE USING MESSAGE = FORMAT('ERR: %s', 'Bad'); END"#; + let sql = r#"BEGIN SELECT 1; EXCEPTION WHEN ERROR THEN SELECT 2; END"#; let Statement::StartTransaction { statements, - exception, + exception_statements, has_end_keyword, .. } = bigquery().verified_stmt(sql) @@ -272,10 +272,7 @@ fn parse_begin() { unreachable!(); }; assert_eq!(1, statements.len()); - assert!(exception.is_some()); - - let exception = exception.unwrap(); - assert_eq!(1, exception.len()); + assert_eq!(1, exception_statements.unwrap().len()); assert!(has_end_keyword); bigquery().verified_stmt( @@ -355,16 +352,14 @@ fn parse_create_view_with_options() { ViewColumnDef { name: Ident::new("age"), data_type: None, - 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)) - ) - ), - }] - )])), + 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)) + ) + ), + }])]), }, ], columns @@ -606,13 +601,11 @@ fn parse_nested_data_types() { field_name: Some("a".into()), field_type: DataType::Array(ArrayElemTypeDef::AngleBracket( Box::new(DataType::Int64,) - )), - options: None, + )) }, StructField { field_name: Some("b".into()), - field_type: DataType::Bytes(Some(42)), - options: None, + field_type: DataType::Bytes(Some(42)) }, ], StructBracketKind::AngleBrackets @@ -626,7 +619,6 @@ fn parse_nested_data_types() { vec![StructField { field_name: None, field_type: DataType::Int64, - options: None, }], StructBracketKind::AngleBrackets ), @@ -640,6 +632,35 @@ fn parse_nested_data_types() { } } +#[test] +fn parse_invalid_brackets() { + let sql = "SELECT STRUCT>(NULL)"; + assert_eq!( + bigquery_and_generic() + .parse_sql_statements(sql) + .unwrap_err(), + ParserError::ParserError("unmatched > in STRUCT literal".to_string()) + ); + + let sql = "SELECT STRUCT>>(NULL)"; + assert_eq!( + bigquery_and_generic() + .parse_sql_statements(sql) + .unwrap_err(), + ParserError::ParserError("Expected: (, found: >".to_string()) + ); + + let sql = "CREATE TABLE table (x STRUCT>>)"; + assert_eq!( + bigquery_and_generic() + .parse_sql_statements(sql) + .unwrap_err(), + ParserError::ParserError( + "Expected: ',' or ')' after column definition, found: >".to_string() + ) + ); +} + #[test] fn parse_tuple_struct_literal() { // tuple syntax: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#tuple_syntax @@ -750,7 +771,6 @@ fn parse_typed_struct_syntax_bigquery() { fields: vec![StructField { field_name: None, field_type: DataType::Int64, - options: None, }] }, expr_from_projection(&select.projection[0]) @@ -779,8 +799,7 @@ fn parse_typed_struct_syntax_bigquery() { quote_style: None, span: Span::empty(), }), - field_type: DataType::Int64, - options: None, + field_type: DataType::Int64 }, StructField { field_name: Some(Ident { @@ -788,8 +807,7 @@ fn parse_typed_struct_syntax_bigquery() { quote_style: None, span: Span::empty(), }), - field_type: DataType::String(None), - options: None, + field_type: DataType::String(None) }, ] }, @@ -807,20 +825,17 @@ fn parse_typed_struct_syntax_bigquery() { field_name: Some("arr".into()), field_type: DataType::Array(ArrayElemTypeDef::AngleBracket(Box::new( DataType::Float64 - ))), - options: None, + ))) }, StructField { field_name: Some("str".into()), field_type: DataType::Struct( vec![StructField { field_name: None, - field_type: DataType::Bool, - options: None, + field_type: DataType::Bool }], StructBracketKind::AngleBrackets - ), - options: None, + ) }, ] }, @@ -843,15 +858,13 @@ fn parse_typed_struct_syntax_bigquery() { field_type: DataType::Struct( Default::default(), StructBracketKind::AngleBrackets - ), - options: None, + ) }, StructField { field_name: Some("y".into()), field_type: DataType::Array(ArrayElemTypeDef::AngleBracket(Box::new( DataType::Struct(Default::default(), StructBracketKind::AngleBrackets) - ))), - options: None, + ))) }, ] }, @@ -866,8 +879,7 @@ fn parse_typed_struct_syntax_bigquery() { values: vec![Expr::Value(Value::Boolean(true).with_empty_span())], fields: vec![StructField { field_name: None, - field_type: DataType::Bool, - options: None, + field_type: DataType::Bool }] }, expr_from_projection(&select.projection[0]) @@ -879,8 +891,7 @@ fn parse_typed_struct_syntax_bigquery() { )], fields: vec![StructField { field_name: None, - field_type: DataType::Bytes(Some(42)), - options: None, + field_type: DataType::Bytes(Some(42)) }] }, expr_from_projection(&select.projection[1]) @@ -896,8 +907,7 @@ fn parse_typed_struct_syntax_bigquery() { )], fields: vec![StructField { field_name: None, - field_type: DataType::Date, - options: None, + field_type: DataType::Date }] }, expr_from_projection(&select.projection[0]) @@ -906,15 +916,11 @@ fn parse_typed_struct_syntax_bigquery() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Datetime(None), - value: ValueWithSpan { - value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()), - span: Span::empty(), - }, + value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()) }], fields: vec![StructField { field_name: None, - field_type: DataType::Datetime(None), - options: None, + field_type: DataType::Datetime(None) }] }, expr_from_projection(&select.projection[1]) @@ -924,8 +930,7 @@ fn parse_typed_struct_syntax_bigquery() { values: vec![Expr::value(number("5.0"))], fields: vec![StructField { field_name: None, - field_type: DataType::Float64, - options: None, + field_type: DataType::Float64 }] }, expr_from_projection(&select.projection[2]) @@ -935,8 +940,7 @@ fn parse_typed_struct_syntax_bigquery() { values: vec![Expr::value(number("1"))], fields: vec![StructField { field_name: None, - field_type: DataType::Int64, - options: None, + field_type: DataType::Int64 }] }, expr_from_projection(&select.projection[3]) @@ -958,8 +962,7 @@ fn parse_typed_struct_syntax_bigquery() { })], fields: vec![StructField { field_name: None, - field_type: DataType::Interval, - options: None, + field_type: DataType::Interval }] }, expr_from_projection(&select.projection[0]) @@ -968,17 +971,13 @@ fn parse_typed_struct_syntax_bigquery() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::JSON, - value: ValueWithSpan { - value: Value::SingleQuotedString( - r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.into() - ), - span: Span::empty(), - } + value: Value::SingleQuotedString( + r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.into() + ) }], fields: vec![StructField { field_name: None, - field_type: DataType::JSON, - options: None, + field_type: DataType::JSON }] }, expr_from_projection(&select.projection[1]) @@ -994,8 +993,7 @@ fn parse_typed_struct_syntax_bigquery() { )], fields: vec![StructField { field_name: None, - field_type: DataType::String(Some(42)), - options: None, + field_type: DataType::String(Some(42)) }] }, expr_from_projection(&select.projection[0]) @@ -1004,17 +1002,11 @@ fn parse_typed_struct_syntax_bigquery() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString( - "2008-12-25 15:30:00 America/Los_Angeles".into() - ), - span: Span::empty(), - }, + value: Value::SingleQuotedString("2008-12-25 15:30:00 America/Los_Angeles".into()) }], fields: vec![StructField { field_name: None, - field_type: DataType::Timestamp(None, TimezoneInfo::None), - options: None, + field_type: DataType::Timestamp(None, TimezoneInfo::None) }] }, expr_from_projection(&select.projection[1]) @@ -1024,15 +1016,11 @@ fn parse_typed_struct_syntax_bigquery() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Time(None, TimezoneInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString("15:30:00".into()), - span: Span::empty(), - } + value: Value::SingleQuotedString("15:30:00".into()) }], fields: vec![StructField { field_name: None, - field_type: DataType::Time(None, TimezoneInfo::None), - options: None, + field_type: DataType::Time(None, TimezoneInfo::None) }] }, expr_from_projection(&select.projection[2]) @@ -1045,15 +1033,11 @@ fn parse_typed_struct_syntax_bigquery() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Numeric(ExactNumberInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString("1".into()), - span: Span::empty(), - } + value: Value::SingleQuotedString("1".into()) }], fields: vec![StructField { field_name: None, - field_type: DataType::Numeric(ExactNumberInfo::None), - options: None, + field_type: DataType::Numeric(ExactNumberInfo::None) }] }, expr_from_projection(&select.projection[0]) @@ -1062,15 +1046,11 @@ fn parse_typed_struct_syntax_bigquery() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString("1".into()), - span: Span::empty(), - } + value: Value::SingleQuotedString("1".into()) }], fields: vec![StructField { field_name: None, - field_type: DataType::BigNumeric(ExactNumberInfo::None), - options: None, + field_type: DataType::BigNumeric(ExactNumberInfo::None) }] }, expr_from_projection(&select.projection[1]) @@ -1087,12 +1067,10 @@ fn parse_typed_struct_syntax_bigquery() { StructField { field_name: Some("key".into()), field_type: DataType::Int64, - options: None, }, StructField { field_name: Some("value".into()), field_type: DataType::Int64, - options: None, }, ] }, @@ -1114,7 +1092,6 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { fields: vec![StructField { field_name: None, field_type: DataType::Int64, - options: None, }] }, expr_from_projection(&select.projection[0]) @@ -1143,8 +1120,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { quote_style: None, span: Span::empty(), }), - field_type: DataType::Int64, - options: None, + field_type: DataType::Int64 }, StructField { field_name: Some(Ident { @@ -1152,8 +1128,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { quote_style: None, span: Span::empty(), }), - field_type: DataType::String(None), - options: None, + field_type: DataType::String(None) }, ] }, @@ -1176,15 +1151,13 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { field_type: DataType::Struct( Default::default(), StructBracketKind::AngleBrackets - ), - options: None, + ) }, StructField { field_name: Some("y".into()), field_type: DataType::Array(ArrayElemTypeDef::AngleBracket(Box::new( DataType::Struct(Default::default(), StructBracketKind::AngleBrackets) - ))), - options: None, + ))) }, ] }, @@ -1199,8 +1172,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { values: vec![Expr::Value(Value::Boolean(true).with_empty_span())], fields: vec![StructField { field_name: None, - field_type: DataType::Bool, - options: None, + field_type: DataType::Bool }] }, expr_from_projection(&select.projection[0]) @@ -1212,8 +1184,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { )], fields: vec![StructField { field_name: None, - field_type: DataType::Bytes(Some(42)), - options: None, + field_type: DataType::Bytes(Some(42)) }] }, expr_from_projection(&select.projection[1]) @@ -1229,8 +1200,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { )], fields: vec![StructField { field_name: None, - field_type: DataType::Date, - options: None, + field_type: DataType::Date }] }, expr_from_projection(&select.projection[0]) @@ -1239,15 +1209,11 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Datetime(None), - value: ValueWithSpan { - value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()), - span: Span::empty(), - } + value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()) }], fields: vec![StructField { field_name: None, - field_type: DataType::Datetime(None), - options: None, + field_type: DataType::Datetime(None) }] }, expr_from_projection(&select.projection[1]) @@ -1257,8 +1223,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { values: vec![Expr::value(number("5.0"))], fields: vec![StructField { field_name: None, - field_type: DataType::Float64, - options: None, + field_type: DataType::Float64 }] }, expr_from_projection(&select.projection[2]) @@ -1268,8 +1233,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { values: vec![Expr::value(number("1"))], fields: vec![StructField { field_name: None, - field_type: DataType::Int64, - options: None, + field_type: DataType::Int64 }] }, expr_from_projection(&select.projection[3]) @@ -1291,8 +1255,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { })], fields: vec![StructField { field_name: None, - field_type: DataType::Interval, - options: None, + field_type: DataType::Interval }] }, expr_from_projection(&select.projection[0]) @@ -1301,17 +1264,13 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::JSON, - value: ValueWithSpan { - value: Value::SingleQuotedString( - r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.into() - ), - span: Span::empty(), - } + value: Value::SingleQuotedString( + r#"{"class" : {"students" : [{"name" : "Jane"}]}}"#.into() + ) }], fields: vec![StructField { field_name: None, - field_type: DataType::JSON, - options: None, + field_type: DataType::JSON }] }, expr_from_projection(&select.projection[1]) @@ -1327,8 +1286,7 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { )], fields: vec![StructField { field_name: None, - field_type: DataType::String(Some(42)), - options: None, + field_type: DataType::String(Some(42)) }] }, expr_from_projection(&select.projection[0]) @@ -1337,17 +1295,11 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString( - "2008-12-25 15:30:00 America/Los_Angeles".into() - ), - span: Span::empty(), - } + value: Value::SingleQuotedString("2008-12-25 15:30:00 America/Los_Angeles".into()) }], fields: vec![StructField { field_name: None, - field_type: DataType::Timestamp(None, TimezoneInfo::None), - options: None, + field_type: DataType::Timestamp(None, TimezoneInfo::None) }] }, expr_from_projection(&select.projection[1]) @@ -1357,15 +1309,11 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Time(None, TimezoneInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString("15:30:00".into()), - span: Span::empty(), - } + value: Value::SingleQuotedString("15:30:00".into()) }], fields: vec![StructField { field_name: None, - field_type: DataType::Time(None, TimezoneInfo::None), - options: None, + field_type: DataType::Time(None, TimezoneInfo::None) }] }, expr_from_projection(&select.projection[2]) @@ -1378,15 +1326,11 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::Numeric(ExactNumberInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString("1".into()), - span: Span::empty(), - } + value: Value::SingleQuotedString("1".into()) }], fields: vec![StructField { field_name: None, - field_type: DataType::Numeric(ExactNumberInfo::None), - options: None, + field_type: DataType::Numeric(ExactNumberInfo::None) }] }, expr_from_projection(&select.projection[0]) @@ -1395,15 +1339,11 @@ fn parse_typed_struct_syntax_bigquery_and_generic() { &Expr::Struct { values: vec![Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString("1".into()), - span: Span::empty(), - } + value: Value::SingleQuotedString("1".into()) }], fields: vec![StructField { field_name: None, - field_type: DataType::BigNumeric(ExactNumberInfo::None), - options: None, + field_type: DataType::BigNumeric(ExactNumberInfo::None) }] }, expr_from_projection(&select.projection[1]) @@ -1420,8 +1360,7 @@ fn parse_typed_struct_with_field_name_bigquery() { values: vec![Expr::value(number("5"))], fields: vec![StructField { field_name: Some(Ident::from("x")), - field_type: DataType::Int64, - options: None, + field_type: DataType::Int64 }] }, expr_from_projection(&select.projection[0]) @@ -1433,8 +1372,7 @@ fn parse_typed_struct_with_field_name_bigquery() { )], fields: vec![StructField { field_name: Some(Ident::from("y")), - field_type: DataType::String(None), - options: None, + field_type: DataType::String(None) }] }, expr_from_projection(&select.projection[1]) @@ -1449,13 +1387,11 @@ fn parse_typed_struct_with_field_name_bigquery() { fields: vec![ StructField { field_name: Some(Ident::from("x")), - field_type: DataType::Int64, - options: None, + field_type: DataType::Int64 }, StructField { field_name: Some(Ident::from("y")), - field_type: DataType::Int64, - options: None, + field_type: DataType::Int64 } ] }, @@ -1473,8 +1409,7 @@ fn parse_typed_struct_with_field_name_bigquery_and_generic() { values: vec![Expr::value(number("5"))], fields: vec![StructField { field_name: Some(Ident::from("x")), - field_type: DataType::Int64, - options: None, + field_type: DataType::Int64 }] }, expr_from_projection(&select.projection[0]) @@ -1486,8 +1421,7 @@ fn parse_typed_struct_with_field_name_bigquery_and_generic() { )], fields: vec![StructField { field_name: Some(Ident::from("y")), - field_type: DataType::String(None), - options: None, + field_type: DataType::String(None) }] }, expr_from_projection(&select.projection[1]) @@ -1502,13 +1436,11 @@ fn parse_typed_struct_with_field_name_bigquery_and_generic() { fields: vec![ StructField { field_name: Some(Ident::from("x")), - field_type: DataType::Int64, - options: None, + field_type: DataType::Int64 }, StructField { field_name: Some(Ident::from("y")), - field_type: DataType::Int64, - options: None, + field_type: DataType::Int64 } ] }, @@ -2433,10 +2365,7 @@ fn test_triple_quote_typed_strings() { assert_eq!( Expr::TypedString { data_type: DataType::JSON, - value: ValueWithSpan { - value: Value::TripleDoubleQuotedString(r#"{"foo":"bar's"}"#.into()), - span: Span::empty(), - } + value: Value::TripleDoubleQuotedString(r#"{"foo":"bar's"}"#.into()) }, expr ); @@ -2478,91 +2407,3 @@ fn test_any_type() { fn test_any_type_dont_break_custom_type() { bigquery_and_generic().verified_stmt("CREATE TABLE foo (x ANY)"); } - -#[test] -fn test_struct_field_options() { - bigquery().verified_stmt(concat!( - "CREATE TABLE my_table (", - "f0 STRUCT, ", - "f1 STRUCT<", - "a STRING OPTIONS(description = 'This is a string', type = 'string'), ", - "b INT64", - "> OPTIONS(description = 'This is a struct field')", - ")", - )); -} - -#[test] -fn test_struct_trailing_and_nested_bracket() { - bigquery().verified_stmt(concat!( - "CREATE TABLE my_table (", - "f0 STRING, ", - "f1 STRUCT>, ", - "f2 STRING", - ")", - )); - - // More complex nested structs - bigquery().verified_stmt(concat!( - "CREATE TABLE my_table (", - "f0 STRING, ", - "f1 STRUCT>>, ", - "f2 STRUCT>>>, ", - "f3 STRUCT>", - ")", - )); - - // Bad case with missing closing bracket - assert_eq!( - ParserError::ParserError("Expected: >, found: )".to_owned()), - bigquery() - .parse_sql_statements("CREATE TABLE my_table(f1 STRUCT after parsing data type STRUCT)".to_owned() - ), - bigquery() - .parse_sql_statements("CREATE TABLE my_table(f1 STRUCT>)") - .unwrap_err() - ); - - // Base case with redundant closing bracket in nested struct - assert_eq!( - ParserError::ParserError( - "Expected: ',' or ')' after column definition, found: >".to_owned() - ), - bigquery() - .parse_sql_statements("CREATE TABLE my_table(f1 STRUCT>>, c INT64)") - .unwrap_err() - ); - - let sql = "SELECT STRUCT>(NULL)"; - assert_eq!( - bigquery_and_generic() - .parse_sql_statements(sql) - .unwrap_err(), - ParserError::ParserError("unmatched > in STRUCT literal".to_string()) - ); - - let sql = "SELECT STRUCT>>(NULL)"; - assert_eq!( - bigquery_and_generic() - .parse_sql_statements(sql) - .unwrap_err(), - ParserError::ParserError("Expected: (, found: >".to_string()) - ); - - let sql = "CREATE TABLE table (x STRUCT>>)"; - assert_eq!( - bigquery_and_generic() - .parse_sql_statements(sql) - .unwrap_err(), - ParserError::ParserError( - "Expected: ',' or ')' after column definition, found: >".to_string() - ) - ); -} diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index 9e5b6ce8..d0218b6c 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::Boolean; +use sqlparser::ast::Value::Number; use sqlparser::ast::*; use sqlparser::dialect::ClickHouseDialect; use sqlparser::dialect::GenericDialect; @@ -60,7 +60,6 @@ fn parse_map_access_expr() { ), })], })], - exclude: None, into: None, from: vec![TableWithJoins { relation: table_from_name(ObjectName::from(vec![Ident::new("foos")])), @@ -225,10 +224,6 @@ fn parse_create_table() { clickhouse().verified_stmt( r#"CREATE TABLE "x" ("a" "int") ENGINE = MergeTree ORDER BY "x" AS SELECT * FROM "t" WHERE true"#, ); - clickhouse().one_statement_parses_to( - "CREATE TABLE x (a int) ENGINE = MergeTree() ORDER BY a", - "CREATE TABLE x (a INT) ENGINE = MergeTree ORDER BY a", - ); } #[test] @@ -674,13 +669,11 @@ fn parse_create_table_with_nested_data_types() { DataType::Tuple(vec![ StructField { field_name: None, - field_type: DataType::FixedString(128), - options: None, + field_type: DataType::FixedString(128) }, StructField { field_name: None, - field_type: DataType::Int128, - options: None, + field_type: DataType::Int128 } ]) ))), @@ -692,14 +685,12 @@ fn parse_create_table_with_nested_data_types() { StructField { field_name: Some("a".into()), field_type: DataType::Datetime64(9, None), - options: None, }, StructField { field_name: Some("b".into()), field_type: DataType::Array(ArrayElemTypeDef::Parenthesis( Box::new(DataType::Uuid) - )), - options: None, + )) }, ]), options: vec![], @@ -919,7 +910,7 @@ fn parse_create_view_with_fields_data_types() { }]), vec![] )), - options: None, + options: None }, ViewColumnDef { name: "f".into(), @@ -931,7 +922,7 @@ fn parse_create_view_with_fields_data_types() { }]), vec![] )), - options: None, + options: None }, ] ); @@ -970,103 +961,38 @@ fn parse_limit_by() { #[test] fn parse_settings_in_query() { - fn check_settings(sql: &str, expected: Vec) { - match clickhouse_and_generic().verified_stmt(sql) { - Statement::Query(q) => { - assert_eq!(q.settings, Some(expected)); - } - _ => unreachable!(), + 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) + }, + ]) + ); } - } - - 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); + _ => unreachable!(), } let invalid_cases = vec![ - ("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: (", - ), + "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", ]; - for (sql, error_msg) in invalid_cases { - assert_eq!( - clickhouse_and_generic() - .parse_sql_statements(sql) - .unwrap_err(), - ParserError(error_msg.to_string()) - ); + for sql in invalid_cases { + clickhouse_and_generic() + .parse_sql_statements(sql) + .expect_err("Expected: SETTINGS key = value, found: "); } } #[test] @@ -1415,7 +1341,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() )]))) @@ -1423,7 +1349,7 @@ fn parse_use() { for "e in "e_styles { // Test single identifier with different type of quotes assert_eq!( - clickhouse().verified_stmt(&format!("USE {quote}{object_name}{quote}")), + clickhouse().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)), Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote( quote, object_name.to_string(), @@ -1437,7 +1363,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" { @@ -1620,11 +1546,11 @@ fn parse_select_table_function_settings() { settings: Some(vec![ Setting { key: "s0".into(), - value: Expr::value(number("3")), + value: Value::Number("3".parse().unwrap(), false), }, Setting { key: "s1".into(), - value: Expr::value(single_quoted_string("s")), + value: Value::SingleQuotedString("s".into()), }, ]), }, @@ -1645,11 +1571,11 @@ fn parse_select_table_function_settings() { settings: Some(vec![ Setting { key: "s0".into(), - value: Expr::value(number("3")), + value: Value::Number("3".parse().unwrap(), false), }, Setting { key: "s1".into(), - value: Expr::value(single_quoted_string("s")), + value: Value::SingleQuotedString("s".into()), }, ]), }, @@ -1659,6 +1585,7 @@ 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() diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 15144479..abcadb45 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -40,9 +40,8 @@ use sqlparser::parser::{Parser, ParserError, ParserOptions}; use sqlparser::tokenizer::Tokenizer; use sqlparser::tokenizer::{Location, Span}; use test_utils::{ - all_dialects, all_dialects_where, all_dialects_with_options, alter_table_op, assert_eq_vec, - call, expr_from_projection, join, number, only, table, table_alias, table_from_name, - TestedDialects, + all_dialects, all_dialects_where, alter_table_op, assert_eq_vec, call, expr_from_projection, + join, number, only, table, table_alias, table_from_name, TestedDialects, }; #[macro_use] @@ -460,7 +459,6 @@ fn parse_update_set_from() { SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("name"))), SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("id"))), ], - exclude: None, into: None, from: vec![TableWithJoins { relation: table_from_name(ObjectName::from(vec![Ident::new("t1")])), @@ -1234,6 +1232,7 @@ fn parse_select_expr_star() { "SELECT 2. * 3 FROM T", ); dialects.verified_only_select("SELECT myfunc().* FROM T"); + dialects.verified_only_select("SELECT myfunc().* EXCEPT (foo) FROM T"); // Invalid let res = dialects.parse_sql_statements("SELECT foo.*.* FROM T"); @@ -1241,11 +1240,6 @@ fn parse_select_expr_star() { ParserError::ParserError("Expected: end of statement, found: .".to_string()), res.unwrap_err() ); - - let dialects = all_dialects_where(|d| { - d.supports_select_expr_star() && d.supports_select_wildcard_except() - }); - dialects.verified_only_select("SELECT myfunc().* EXCEPT (foo) FROM T"); } #[test] @@ -2046,7 +2040,7 @@ fn parse_ilike() { pattern: Box::new(Expr::Value( (Value::SingleQuotedString("%a".to_string())).with_empty_span() )), - escape_char: Some(Value::SingleQuotedString('^'.to_string())), + escape_char: Some('^'.to_string()), any: false, }, select.selection.unwrap() @@ -2110,7 +2104,7 @@ fn parse_like() { pattern: Box::new(Expr::Value( (Value::SingleQuotedString("%a".to_string())).with_empty_span() )), - escape_char: Some(Value::SingleQuotedString('^'.to_string())), + escape_char: Some('^'.to_string()), any: false, }, select.selection.unwrap() @@ -2173,24 +2167,7 @@ fn parse_similar_to() { pattern: Box::new(Expr::Value( (Value::SingleQuotedString("%a".to_string())).with_empty_span() )), - escape_char: Some(Value::SingleQuotedString('^'.to_string())), - }, - select.selection.unwrap() - ); - - let sql = &format!( - "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE NULL", - if negated { "NOT " } else { "" } - ); - let select = verified_only_select(sql); - assert_eq!( - Expr::SimilarTo { - expr: Box::new(Expr::Identifier(Ident::new("name"))), - negated, - pattern: Box::new(Expr::Value( - (Value::SingleQuotedString("%a".to_string())).with_empty_span() - )), - escape_char: Some(Value::Null), + escape_char: Some('^'.to_string()), }, select.selection.unwrap() ); @@ -2208,7 +2185,7 @@ fn parse_similar_to() { pattern: Box::new(Expr::Value( (Value::SingleQuotedString("%a".to_string())).with_empty_span() )), - escape_char: Some(Value::SingleQuotedString('^'.to_string())), + escape_char: Some('^'.to_string()), })), select.selection.unwrap() ); @@ -2248,7 +2225,7 @@ fn parse_in_subquery() { assert_eq!( Expr::InSubquery { expr: Box::new(Expr::Identifier(Ident::new("segment"))), - subquery: Box::new(verified_query("SELECT segm FROM bar")), + subquery: verified_query("SELECT segm FROM bar").body, negated: false, }, select.selection.unwrap() @@ -2262,9 +2239,7 @@ fn parse_in_union() { assert_eq!( Expr::InSubquery { expr: Box::new(Expr::Identifier(Ident::new("segment"))), - subquery: Box::new(verified_query( - "(SELECT segm FROM bar) UNION (SELECT segm FROM bar2)" - )), + subquery: verified_query("(SELECT segm FROM bar) UNION (SELECT segm FROM bar2)").body, negated: false, }, select.selection.unwrap() @@ -3586,7 +3561,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]); @@ -4046,13 +4021,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() { @@ -4979,7 +4954,7 @@ fn parse_alter_table_constraints() { match alter_table_op(verified_stmt(&format!( "ALTER TABLE tab ADD {constraint_text}" ))) { - AlterTableOperation::AddConstraint { constraint, .. } => { + AlterTableOperation::AddConstraint(constraint) => { assert_eq!(constraint_text, constraint.to_string()); } _ => unreachable!(), @@ -5002,18 +4977,15 @@ fn parse_alter_table_drop_column() { "ALTER TABLE tab DROP is_active CASCADE", ); - let dialects = all_dialects_where(|d| d.supports_comma_separated_drop_column_list()); - dialects.verified_stmt("ALTER TABLE tbl DROP COLUMN c1, c2, c3"); - fn check_one(constraint_text: &str) { match alter_table_op(verified_stmt(&format!("ALTER TABLE tab {constraint_text}"))) { AlterTableOperation::DropColumn { has_column_keyword: true, - column_names, + column_name, if_exists, drop_behavior, } => { - assert_eq!("is_active", column_names.first().unwrap().to_string()); + assert_eq!("is_active", column_name.to_string()); assert!(if_exists); match drop_behavior { None => assert!(constraint_text.ends_with(" is_active")), @@ -5083,21 +5055,22 @@ fn parse_alter_table_alter_column_type() { AlterColumnOperation::SetDataType { data_type: DataType::Text, using: None, - had_set: true, } ); } _ => unreachable!(), } - verified_stmt(&format!("{alter_stmt} ALTER COLUMN is_active TYPE TEXT")); - let dialects = all_dialects_where(|d| d.supports_alter_column_type_using()); - dialects.verified_stmt(&format!( - "{alter_stmt} ALTER COLUMN is_active SET DATA TYPE TEXT USING 'text'" - )); + let dialect = TestedDialects::new(vec![Box::new(GenericDialect {})]); - let dialects = all_dialects_except(|d| d.supports_alter_column_type_using()); - let res = dialects.parse_sql_statements(&format!( + let res = + dialect.parse_sql_statements(&format!("{alter_stmt} ALTER COLUMN is_active TYPE TEXT")); + assert_eq!( + ParserError::ParserError("Expected: SET/DROP NOT NULL, SET DEFAULT, or SET DATA TYPE after ALTER COLUMN, found: TYPE".to_string()), + res.unwrap_err() + ); + + let res = dialect.parse_sql_statements(&format!( "{alter_stmt} ALTER COLUMN is_active SET DATA TYPE TEXT USING 'text'" )); assert_eq!( @@ -5550,8 +5523,7 @@ fn parse_named_window_functions() { WINDOW w AS (PARTITION BY x), win AS (ORDER BY y)"; supported_dialects.verified_stmt(sql); - let select = all_dialects_except(|d| d.is_table_alias(&Keyword::WINDOW, &mut Parser::new(d))) - .verified_only_select(sql); + let select = verified_only_select(sql); const EXPECTED_PROJ_QTY: usize = 2; assert_eq!(EXPECTED_PROJ_QTY, select.projection.len()); @@ -5581,7 +5553,6 @@ fn parse_named_window_functions() { #[test] fn parse_window_clause() { - let dialects = all_dialects_except(|d| d.is_table_alias(&Keyword::WINDOW, &mut Parser::new(d))); let sql = "SELECT * \ FROM mytable \ WINDOW \ @@ -5594,14 +5565,10 @@ fn parse_window_clause() { window7 AS (window1 ROWS UNBOUNDED PRECEDING), \ window8 AS (window1 PARTITION BY a ORDER BY b ROWS UNBOUNDED PRECEDING) \ ORDER BY C3"; - dialects.verified_only_select(sql); + verified_only_select(sql); let sql = "SELECT * from mytable WINDOW window1 AS window2"; - let dialects = all_dialects_except(|d| { - d.is::() - || d.is::() - || d.is_table_alias(&Keyword::WINDOW, &mut Parser::new(d)) - }); + let dialects = all_dialects_except(|d| d.is::() || d.is::()); let res = dialects.parse_sql_statements(sql); assert_eq!( ParserError::ParserError("Expected: (, found: window2".to_string()), @@ -5611,7 +5578,6 @@ fn parse_window_clause() { #[test] fn test_parse_named_window() { - let dialects = all_dialects_except(|d| d.is_table_alias(&Keyword::WINDOW, &mut Parser::new(d))); let sql = "SELECT \ MIN(c12) OVER window1 AS min1, \ MAX(c12) OVER window2 AS max1 \ @@ -5619,7 +5585,7 @@ fn test_parse_named_window() { WINDOW window1 AS (ORDER BY C12), \ window2 AS (PARTITION BY C11) \ ORDER BY C3"; - let actual_select_only = dialects.verified_only_select(sql); + let actual_select_only = verified_only_select(sql); let expected = Select { select_token: AttachedToken::empty(), distinct: None, @@ -5697,7 +5663,6 @@ fn test_parse_named_window() { }, }, ], - exclude: None, into: None, from: vec![TableWithJoins { relation: table_from_name(ObjectName::from(vec![Ident { @@ -5769,10 +5734,6 @@ fn test_parse_named_window() { #[test] fn parse_window_and_qualify_clause() { - let dialects = all_dialects_except(|d| { - d.is_table_alias(&Keyword::WINDOW, &mut Parser::new(d)) - || d.is_table_alias(&Keyword::QUALIFY, &mut Parser::new(d)) - }); let sql = "SELECT \ MIN(c12) OVER window1 AS min1 \ FROM aggregate_test_100 \ @@ -5780,7 +5741,7 @@ fn parse_window_and_qualify_clause() { WINDOW window1 AS (ORDER BY C12), \ window2 AS (PARTITION BY C11) \ ORDER BY C3"; - dialects.verified_only_select(sql); + verified_only_select(sql); let sql = "SELECT \ MIN(c12) OVER window1 AS min1 \ @@ -5789,7 +5750,7 @@ fn parse_window_and_qualify_clause() { window2 AS (PARTITION BY C11) \ QUALIFY ROW_NUMBER() OVER my_window \ ORDER BY C3"; - dialects.verified_only_select(sql); + verified_only_select(sql); } #[test] @@ -5889,10 +5850,7 @@ fn parse_literal_date() { assert_eq!( &Expr::TypedString { data_type: DataType::Date, - value: ValueWithSpan { - value: Value::SingleQuotedString("1999-01-01".into()), - span: Span::empty(), - } + value: Value::SingleQuotedString("1999-01-01".into()), }, expr_from_projection(only(&select.projection)), ); @@ -5905,10 +5863,7 @@ fn parse_literal_time() { assert_eq!( &Expr::TypedString { data_type: DataType::Time(None, TimezoneInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString("01:23:34".into()), - span: Span::empty(), - }, + value: Value::SingleQuotedString("01:23:34".into()), }, expr_from_projection(only(&select.projection)), ); @@ -5921,10 +5876,7 @@ fn parse_literal_datetime() { assert_eq!( &Expr::TypedString { data_type: DataType::Datetime(None), - value: ValueWithSpan { - value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()), - span: Span::empty(), - }, + value: Value::SingleQuotedString("1999-01-01 01:23:34.45".into()), }, expr_from_projection(only(&select.projection)), ); @@ -5937,10 +5889,7 @@ fn parse_literal_timestamp_without_time_zone() { assert_eq!( &Expr::TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString("1999-01-01 01:23:34".into()), - span: Span::empty(), - }, + value: Value::SingleQuotedString("1999-01-01 01:23:34".into()), }, expr_from_projection(only(&select.projection)), ); @@ -5955,10 +5904,7 @@ fn parse_literal_timestamp_with_time_zone() { assert_eq!( &Expr::TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::Tz), - value: ValueWithSpan { - value: Value::SingleQuotedString("1999-01-01 01:23:34Z".into()), - span: Span::empty(), - }, + value: Value::SingleQuotedString("1999-01-01 01:23:34Z".into()), }, expr_from_projection(only(&select.projection)), ); @@ -6354,7 +6300,6 @@ fn parse_interval_and_or_xor() { quote_style: None, span: Span::empty(), }))], - exclude: None, into: None, from: vec![TableWithJoins { relation: table_from_name(ObjectName::from(vec![Ident { @@ -6531,9 +6476,8 @@ fn parse_json_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::JSON, - value: ValueWithSpan { - value: Value::SingleQuotedString( - r#"{ + value: Value::SingleQuotedString( + r#"{ "id": 10, "type": "fruit", "name": "apple", @@ -6553,10 +6497,8 @@ fn parse_json_keyword() { ] } }"# - .to_string() - ), - span: Span::empty(), - } + .to_string() + ) }, expr_from_projection(only(&select.projection)), ); @@ -6568,10 +6510,7 @@ fn parse_typed_strings() { assert_eq!( Expr::TypedString { data_type: DataType::JSON, - value: ValueWithSpan { - value: Value::SingleQuotedString(r#"{"foo":"bar"}"#.into()), - span: Span::empty(), - } + value: Value::SingleQuotedString(r#"{"foo":"bar"}"#.into()) }, expr ); @@ -6589,10 +6528,7 @@ fn parse_bignumeric_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString(r#"0"#.into()), - span: Span::empty(), - } + value: Value::SingleQuotedString(r#"0"#.into()) }, expr_from_projection(only(&select.projection)), ); @@ -6603,10 +6539,7 @@ fn parse_bignumeric_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString(r#"123456"#.into()), - span: Span::empty(), - } + value: Value::SingleQuotedString(r#"123456"#.into()) }, expr_from_projection(only(&select.projection)), ); @@ -6617,10 +6550,7 @@ fn parse_bignumeric_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString(r#"-3.14"#.into()), - span: Span::empty(), - } + value: Value::SingleQuotedString(r#"-3.14"#.into()) }, expr_from_projection(only(&select.projection)), ); @@ -6631,10 +6561,7 @@ fn parse_bignumeric_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString(r#"-0.54321"#.into()), - span: Span::empty(), - } + value: Value::SingleQuotedString(r#"-0.54321"#.into()) }, expr_from_projection(only(&select.projection)), ); @@ -6645,10 +6572,7 @@ fn parse_bignumeric_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString(r#"1.23456e05"#.into()), - span: Span::empty(), - } + value: Value::SingleQuotedString(r#"1.23456e05"#.into()) }, expr_from_projection(only(&select.projection)), ); @@ -6659,10 +6583,7 @@ fn parse_bignumeric_keyword() { assert_eq!( &Expr::TypedString { data_type: DataType::BigNumeric(ExactNumberInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString(r#"-9.876e-3"#.into()), - span: Span::empty(), - } + value: Value::SingleQuotedString(r#"-9.876e-3"#.into()) }, expr_from_projection(only(&select.projection)), ); @@ -7458,8 +7379,7 @@ fn parse_join_syntax_variants() { "SELECT c1 FROM t1 FULL JOIN t2 USING(c1)", ); - let dialects = all_dialects_except(|d| d.is_table_alias(&Keyword::OUTER, &mut Parser::new(d))); - let res = dialects.parse_sql_statements("SELECT * FROM a OUTER JOIN b ON 1"); + let res = parse_sql_statements("SELECT * FROM a OUTER JOIN b ON 1"); assert_eq!( ParserError::ParserError("Expected: APPLY, found: JOIN".to_string()), res.unwrap_err() @@ -7577,7 +7497,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)") { @@ -7585,7 +7505,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") { @@ -7593,7 +7513,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), } } @@ -7840,6 +7760,7 @@ fn parse_trim() { Box::new(MySqlDialect {}), //Box::new(BigQueryDialect {}), Box::new(SQLiteDialect {}), + Box::new(DuckDbDialect {}), ]); assert_eq!( @@ -8067,7 +7988,7 @@ fn parse_create_view_with_columns() { .map(|name| ViewColumnDef { name, data_type: None, - options: None, + options: None }) .collect::>() ); @@ -8624,7 +8545,6 @@ fn lateral_function() { distinct: None, top: None, projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions::default())], - exclude: None, top_before_distinct: false, into: None, from: vec![TableWithJoins { @@ -8672,11 +8592,8 @@ fn lateral_function() { #[test] fn parse_start_transaction() { let dialects = all_dialects_except(|d| - // BigQuery and Snowflake does not support this syntax - // - // BigQuery: - // Snowflake: - d.is::() || d.is::()); + // BigQuery does not support this syntax + d.is::()); match dialects .verified_stmt("START TRANSACTION READ ONLY, READ WRITE, ISOLATION LEVEL SERIALIZABLE") { @@ -9455,30 +9372,18 @@ fn parse_grant() { verified_stmt("GRANT SELECT ON ALL TABLES IN SCHEMA db1.sc1 TO APPLICATION role1"); verified_stmt("GRANT SELECT ON ALL TABLES IN SCHEMA db1.sc1 TO APPLICATION ROLE role1"); verified_stmt("GRANT SELECT ON ALL TABLES IN SCHEMA db1.sc1 TO SHARE share1"); - verified_stmt("GRANT SELECT ON ALL VIEWS IN SCHEMA db1.sc1 TO ROLE role1"); - verified_stmt("GRANT SELECT ON ALL MATERIALIZED VIEWS IN SCHEMA db1.sc1 TO ROLE role1"); - verified_stmt("GRANT SELECT ON ALL EXTERNAL TABLES IN SCHEMA db1.sc1 TO ROLE role1"); verified_stmt("GRANT USAGE ON SCHEMA sc1 TO a:b"); verified_stmt("GRANT USAGE ON SCHEMA sc1 TO GROUP group1"); verified_stmt("GRANT OWNERSHIP ON ALL TABLES IN SCHEMA DEV_STAS_ROGOZHIN TO ROLE ANALYST"); - verified_stmt("GRANT OWNERSHIP ON ALL TABLES IN SCHEMA DEV_STAS_ROGOZHIN TO ROLE ANALYST COPY CURRENT GRANTS"); - verified_stmt("GRANT OWNERSHIP ON ALL TABLES IN SCHEMA DEV_STAS_ROGOZHIN TO ROLE ANALYST REVOKE CURRENT GRANTS"); verified_stmt("GRANT USAGE ON DATABASE db1 TO ROLE role1"); verified_stmt("GRANT USAGE ON WAREHOUSE wh1 TO ROLE role1"); verified_stmt("GRANT OWNERSHIP ON INTEGRATION int1 TO ROLE role1"); 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 EXTERNAL TABLES IN SCHEMA db1.sc1 TO ROLE role1"); - verified_stmt("GRANT SELECT ON FUTURE VIEWS IN SCHEMA db1.sc1 TO ROLE role1"); - verified_stmt("GRANT SELECT ON FUTURE MATERIALIZED VIEWS IN SCHEMA db1.sc1 TO ROLE role1"); - verified_stmt("GRANT SELECT ON FUTURE SEQUENCES IN SCHEMA db1.sc1 TO ROLE role1"); - verified_stmt("GRANT USAGE ON PROCEDURE db1.sc1.foo(INT) TO ROLE role1"); - verified_stmt("GRANT USAGE ON FUNCTION db1.sc1.foo(INT) TO ROLE role1"); } #[test] @@ -9621,7 +9526,6 @@ fn parse_merge() { projection: vec![SelectItem::Wildcard( WildcardAdditionalOptions::default() )], - exclude: None, into: None, from: vec![TableWithJoins { relation: table_from_name(ObjectName::from(vec![ @@ -10134,7 +10038,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)]), @@ -10156,7 +10060,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!( @@ -11157,17 +11061,10 @@ fn parse_non_latin_identifiers() { Box::new(RedshiftSqlDialect {}), Box::new(MySqlDialect {}), ]); + supported_dialects.verified_stmt("SELECT a.説明 FROM test.public.inter01 AS a"); supported_dialects.verified_stmt("SELECT a.説明 FROM inter01 AS a, inter01_transactions AS b WHERE a.説明 = b.取引 GROUP BY a.説明"); supported_dialects.verified_stmt("SELECT 説明, hühnervögel, garçon, Москва, 東京 FROM inter01"); - - let supported_dialects = TestedDialects::new(vec![ - Box::new(GenericDialect {}), - Box::new(DuckDbDialect {}), - Box::new(PostgreSqlDialect {}), - Box::new(MsSqlDialect {}), - Box::new(MySqlDialect {}), - ]); assert!(supported_dialects .parse_sql_statements("SELECT 💝 FROM table1") .is_err()); @@ -11216,7 +11113,7 @@ fn parse_trailing_comma() { trailing_commas.verified_stmt(r#"SELECT "from" FROM "from""#); // doesn't allow any trailing commas - let trailing_commas = TestedDialects::new(vec![Box::new(PostgreSqlDialect {})]); + let trailing_commas = TestedDialects::new(vec![Box::new(GenericDialect {})]); assert_eq!( trailing_commas @@ -11540,7 +11437,6 @@ fn parse_unload() { top: None, top_before_distinct: false, projection: vec![UnnamedExpr(Expr::Identifier(Ident::new("cola"))),], - exclude: None, into: None, from: vec![TableWithJoins { relation: table_from_name(ObjectName::from(vec![Ident::new("tab")])), @@ -11741,7 +11637,6 @@ fn parse_connect_by() { SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("manager_id"))), SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("title"))), ], - exclude: None, from: vec![TableWithJoins { relation: table_from_name(ObjectName::from(vec![Ident::new("employees")])), joins: vec![], @@ -11823,7 +11718,6 @@ fn parse_connect_by() { SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("manager_id"))), SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("title"))), ], - exclude: None, from: vec![TableWithJoins { relation: table_from_name(ObjectName::from(vec![Ident::new("employees")])), joins: vec![], @@ -12757,7 +12651,6 @@ fn test_extract_seconds_ok() { format: None, }), })], - exclude: None, into: None, from: vec![], lateral_views: vec![], @@ -14408,7 +14301,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(); @@ -14708,7 +14601,7 @@ fn test_conditional_statement_span() { else_block.unwrap().span() ); } - stmt => panic!("Unexpected statement: {stmt:?}"), + stmt => panic!("Unexpected statement: {:?}", stmt), } } @@ -14830,7 +14723,6 @@ fn test_select_from_first() { distinct: None, top: None, projection, - exclude: None, top_before_distinct: false, into: None, from: vec![TableWithJoins { @@ -14929,10 +14821,7 @@ fn test_geometry_type() { all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), Expr::TypedString { data_type: DataType::GeometricType(GeometricTypeKind::Point), - value: ValueWithSpan { - value: Value::SingleQuotedString("1,2".to_string()), - span: Span::empty(), - }, + value: Value::SingleQuotedString("1,2".to_string()), } ); @@ -14941,10 +14830,7 @@ fn test_geometry_type() { all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), Expr::TypedString { data_type: DataType::GeometricType(GeometricTypeKind::Line), - value: ValueWithSpan { - value: Value::SingleQuotedString("1,2,3,4".to_string()), - span: Span::empty(), - }, + value: Value::SingleQuotedString("1,2,3,4".to_string()), } ); @@ -14953,10 +14839,7 @@ fn test_geometry_type() { all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), Expr::TypedString { data_type: DataType::GeometricType(GeometricTypeKind::GeometricPath), - value: ValueWithSpan { - value: Value::SingleQuotedString("1,2,3,4".to_string()), - span: Span::empty(), - }, + value: Value::SingleQuotedString("1,2,3,4".to_string()), } ); let sql = "box '1,2,3,4'"; @@ -14964,10 +14847,7 @@ fn test_geometry_type() { all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), Expr::TypedString { data_type: DataType::GeometricType(GeometricTypeKind::GeometricBox), - value: ValueWithSpan { - value: Value::SingleQuotedString("1,2,3,4".to_string()), - span: Span::empty(), - }, + value: Value::SingleQuotedString("1,2,3,4".to_string()), } ); @@ -14976,10 +14856,7 @@ fn test_geometry_type() { all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), Expr::TypedString { data_type: DataType::GeometricType(GeometricTypeKind::Circle), - value: ValueWithSpan { - value: Value::SingleQuotedString("1,2,3".to_string()), - span: Span::empty(), - }, + value: Value::SingleQuotedString("1,2,3".to_string()), } ); @@ -14988,10 +14865,7 @@ fn test_geometry_type() { all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), Expr::TypedString { data_type: DataType::GeometricType(GeometricTypeKind::Polygon), - value: ValueWithSpan { - value: Value::SingleQuotedString("1,2,3,4".to_string()), - span: Span::empty(), - }, + value: Value::SingleQuotedString("1,2,3,4".to_string()), } ); let sql = "lseg '1,2,3,4'"; @@ -14999,10 +14873,7 @@ fn test_geometry_type() { all_dialects_where(|d| d.supports_geometric_types()).verified_expr(sql), Expr::TypedString { data_type: DataType::GeometricType(GeometricTypeKind::LineSegment), - value: ValueWithSpan { - value: Value::SingleQuotedString("1,2,3,4".to_string()), - span: Span::empty(), - }, + value: Value::SingleQuotedString("1,2,3,4".to_string()), } ); } @@ -15339,426 +15210,10 @@ fn parse_pipeline_operator() { dialects.verified_stmt("SELECT * FROM tbl |> TABLESAMPLE SYSTEM (50 PERCENT)"); dialects.verified_stmt("SELECT * FROM tbl |> TABLESAMPLE SYSTEM (50) REPEATABLE (10)"); - // rename pipe operator - dialects.verified_stmt("SELECT * FROM users |> RENAME old_name AS new_name"); - dialects.verified_stmt("SELECT * FROM users |> RENAME id AS user_id, name AS user_name"); - dialects.verified_query_with_canonical( - "SELECT * FROM users |> RENAME id user_id", - "SELECT * FROM users |> RENAME id AS user_id", - ); - - // union pipe operator - dialects.verified_stmt("SELECT * FROM users |> UNION ALL (SELECT * FROM admins)"); - dialects.verified_stmt("SELECT * FROM users |> UNION DISTINCT (SELECT * FROM admins)"); - dialects.verified_stmt("SELECT * FROM users |> UNION (SELECT * FROM admins)"); - - // union pipe operator with multiple queries - dialects.verified_stmt( - "SELECT * FROM users |> UNION ALL (SELECT * FROM admins), (SELECT * FROM guests)", - ); - dialects.verified_stmt("SELECT * FROM users |> UNION DISTINCT (SELECT * FROM admins), (SELECT * FROM guests), (SELECT * FROM employees)"); - dialects.verified_stmt( - "SELECT * FROM users |> UNION (SELECT * FROM admins), (SELECT * FROM guests)", - ); - - // union pipe operator with BY NAME modifier - dialects.verified_stmt("SELECT * FROM users |> UNION BY NAME (SELECT * FROM admins)"); - dialects.verified_stmt("SELECT * FROM users |> UNION ALL BY NAME (SELECT * FROM admins)"); - dialects.verified_stmt("SELECT * FROM users |> UNION DISTINCT BY NAME (SELECT * FROM admins)"); - - // union pipe operator with BY NAME and multiple queries - dialects.verified_stmt( - "SELECT * FROM users |> UNION BY NAME (SELECT * FROM admins), (SELECT * FROM guests)", - ); - - // intersect pipe operator (BigQuery requires DISTINCT modifier for INTERSECT) - dialects.verified_stmt("SELECT * FROM users |> INTERSECT DISTINCT (SELECT * FROM admins)"); - - // intersect pipe operator with BY NAME modifier - dialects - .verified_stmt("SELECT * FROM users |> INTERSECT DISTINCT BY NAME (SELECT * FROM admins)"); - - // intersect pipe operator with multiple queries - dialects.verified_stmt( - "SELECT * FROM users |> INTERSECT DISTINCT (SELECT * FROM admins), (SELECT * FROM guests)", - ); - - // intersect pipe operator with BY NAME and multiple queries - dialects.verified_stmt("SELECT * FROM users |> INTERSECT DISTINCT BY NAME (SELECT * FROM admins), (SELECT * FROM guests)"); - - // except pipe operator (BigQuery requires DISTINCT modifier for EXCEPT) - dialects.verified_stmt("SELECT * FROM users |> EXCEPT DISTINCT (SELECT * FROM admins)"); - - // except pipe operator with BY NAME modifier - dialects.verified_stmt("SELECT * FROM users |> EXCEPT DISTINCT BY NAME (SELECT * FROM admins)"); - - // except pipe operator with multiple queries - dialects.verified_stmt( - "SELECT * FROM users |> EXCEPT DISTINCT (SELECT * FROM admins), (SELECT * FROM guests)", - ); - - // except pipe operator with BY NAME and multiple queries - dialects.verified_stmt("SELECT * FROM users |> EXCEPT DISTINCT BY NAME (SELECT * FROM admins), (SELECT * FROM guests)"); - - // call pipe operator - dialects.verified_stmt("SELECT * FROM users |> CALL my_function()"); - dialects.verified_stmt("SELECT * FROM users |> CALL process_data(5, 'test')"); - dialects.verified_stmt( - "SELECT * FROM users |> CALL namespace.function_name(col1, col2, 'literal')", - ); - - // call pipe operator with complex arguments - dialects.verified_stmt("SELECT * FROM users |> CALL transform_data(col1 + col2)"); - dialects.verified_stmt("SELECT * FROM users |> CALL analyze_data('param1', 100, true)"); - - // call pipe operator with aliases - dialects.verified_stmt("SELECT * FROM input_table |> CALL tvf1(arg1) AS al"); - dialects.verified_stmt("SELECT * FROM users |> CALL process_data(5) AS result_table"); - dialects.verified_stmt("SELECT * FROM users |> CALL namespace.func() AS my_alias"); - - // multiple call pipe operators in sequence - dialects.verified_stmt("SELECT * FROM input_table |> CALL tvf1(arg1) |> CALL tvf2(arg2, arg3)"); - dialects.verified_stmt( - "SELECT * FROM data |> CALL transform(col1) |> CALL validate() |> CALL process(param)", - ); - - // multiple call pipe operators with aliases - dialects.verified_stmt( - "SELECT * FROM input_table |> CALL tvf1(arg1) AS step1 |> CALL tvf2(arg2) AS step2", - ); - dialects.verified_stmt( - "SELECT * FROM data |> CALL preprocess() AS clean_data |> CALL analyze(mode) AS results", - ); - - // call pipe operators mixed with other pipe operators - dialects.verified_stmt( - "SELECT * FROM users |> CALL transform() |> WHERE status = 'active' |> CALL process(param)", - ); - dialects.verified_stmt( - "SELECT * FROM data |> CALL preprocess() AS clean |> SELECT col1, col2 |> CALL validate()", - ); - - // pivot pipe operator - dialects.verified_stmt( - "SELECT * FROM monthly_sales |> PIVOT(SUM(amount) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4'))", - ); - dialects.verified_stmt("SELECT * FROM sales_data |> PIVOT(AVG(revenue) FOR region IN ('North', 'South', 'East', 'West'))"); - - // pivot pipe operator with multiple aggregate functions - dialects.verified_stmt("SELECT * FROM data |> PIVOT(SUM(sales) AS total_sales, COUNT(*) AS num_transactions FOR month IN ('Jan', 'Feb', 'Mar'))"); - - // pivot pipe operator with compound column names - dialects.verified_stmt("SELECT * FROM sales |> PIVOT(SUM(amount) FOR product.category IN ('Electronics', 'Clothing'))"); - - // pivot pipe operator mixed with other pipe operators - dialects.verified_stmt("SELECT * FROM sales_data |> WHERE year = 2023 |> PIVOT(SUM(revenue) FOR quarter IN ('Q1', 'Q2', 'Q3', 'Q4'))"); - - // pivot pipe operator with aliases - dialects.verified_stmt("SELECT * FROM monthly_sales |> PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2')) AS quarterly_sales"); - dialects.verified_stmt("SELECT * FROM data |> PIVOT(AVG(price) FOR category IN ('A', 'B', 'C')) AS avg_by_category"); - dialects.verified_stmt("SELECT * FROM sales |> PIVOT(COUNT(*) AS transactions, SUM(amount) AS total FOR region IN ('North', 'South')) AS regional_summary"); - - // pivot pipe operator with implicit aliases (without AS keyword) - dialects.verified_query_with_canonical( - "SELECT * FROM monthly_sales |> PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2')) quarterly_sales", - "SELECT * FROM monthly_sales |> PIVOT(SUM(sales) FOR quarter IN ('Q1', 'Q2')) AS quarterly_sales", - ); - dialects.verified_query_with_canonical( - "SELECT * FROM data |> PIVOT(AVG(price) FOR category IN ('A', 'B', 'C')) avg_by_category", - "SELECT * FROM data |> PIVOT(AVG(price) FOR category IN ('A', 'B', 'C')) AS avg_by_category", - ); - - // unpivot pipe operator basic usage - dialects - .verified_stmt("SELECT * FROM sales |> UNPIVOT(revenue FOR quarter IN (Q1, Q2, Q3, Q4))"); - dialects.verified_stmt("SELECT * FROM data |> UNPIVOT(value FOR category IN (A, B, C))"); - dialects.verified_stmt( - "SELECT * FROM metrics |> UNPIVOT(measurement FOR metric_type IN (cpu, memory, disk))", - ); - - // unpivot pipe operator with multiple columns - dialects.verified_stmt("SELECT * FROM quarterly_sales |> UNPIVOT(amount FOR period IN (jan, feb, mar, apr, may, jun))"); - dialects.verified_stmt( - "SELECT * FROM report |> UNPIVOT(score FOR subject IN (math, science, english, history))", - ); - - // unpivot pipe operator mixed with other pipe operators - dialects.verified_stmt("SELECT * FROM sales_data |> WHERE year = 2023 |> UNPIVOT(revenue FOR quarter IN (Q1, Q2, Q3, Q4))"); - - // unpivot pipe operator with aliases - dialects.verified_stmt("SELECT * FROM quarterly_sales |> UNPIVOT(amount FOR period IN (Q1, Q2)) AS unpivoted_sales"); - dialects.verified_stmt( - "SELECT * FROM data |> UNPIVOT(value FOR category IN (A, B, C)) AS transformed_data", - ); - dialects.verified_stmt("SELECT * FROM metrics |> UNPIVOT(measurement FOR metric_type IN (cpu, memory)) AS metric_measurements"); - - // unpivot pipe operator with implicit aliases (without AS keyword) - dialects.verified_query_with_canonical( - "SELECT * FROM quarterly_sales |> UNPIVOT(amount FOR period IN (Q1, Q2)) unpivoted_sales", - "SELECT * FROM quarterly_sales |> UNPIVOT(amount FOR period IN (Q1, Q2)) AS unpivoted_sales", - ); - dialects.verified_query_with_canonical( - "SELECT * FROM data |> UNPIVOT(value FOR category IN (A, B, C)) transformed_data", - "SELECT * FROM data |> UNPIVOT(value FOR category IN (A, B, C)) AS transformed_data", - ); - // many pipes dialects.verified_stmt( "SELECT * FROM CustomerOrders |> AGGREGATE SUM(cost) AS total_cost GROUP BY customer_id, state, item_type |> EXTEND COUNT(*) OVER (PARTITION BY customer_id) AS num_orders |> WHERE num_orders > 1 |> AGGREGATE AVG(total_cost) AS average GROUP BY state DESC, item_type ASC", ); - - // join pipe operator - INNER JOIN - dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id"); - dialects.verified_stmt("SELECT * FROM users |> INNER JOIN orders ON users.id = orders.user_id"); - - // join pipe operator - LEFT JOIN - dialects.verified_stmt("SELECT * FROM users |> LEFT JOIN orders ON users.id = orders.user_id"); - dialects.verified_stmt( - "SELECT * FROM users |> LEFT OUTER JOIN orders ON users.id = orders.user_id", - ); - - // join pipe operator - RIGHT JOIN - dialects.verified_stmt("SELECT * FROM users |> RIGHT JOIN orders ON users.id = orders.user_id"); - dialects.verified_stmt( - "SELECT * FROM users |> RIGHT OUTER JOIN orders ON users.id = orders.user_id", - ); - - // join pipe operator - FULL JOIN - dialects.verified_stmt("SELECT * FROM users |> FULL JOIN orders ON users.id = orders.user_id"); - dialects.verified_query_with_canonical( - "SELECT * FROM users |> FULL OUTER JOIN orders ON users.id = orders.user_id", - "SELECT * FROM users |> FULL JOIN orders ON users.id = orders.user_id", - ); - - // join pipe operator - CROSS JOIN - dialects.verified_stmt("SELECT * FROM users |> CROSS JOIN orders"); - - // join pipe operator with USING - dialects.verified_query_with_canonical( - "SELECT * FROM users |> JOIN orders USING (user_id)", - "SELECT * FROM users |> JOIN orders USING(user_id)", - ); - dialects.verified_query_with_canonical( - "SELECT * FROM users |> LEFT JOIN orders USING (user_id, order_date)", - "SELECT * FROM users |> LEFT JOIN orders USING(user_id, order_date)", - ); - - // join pipe operator with alias - dialects.verified_query_with_canonical( - "SELECT * FROM users |> JOIN orders o ON users.id = o.user_id", - "SELECT * FROM users |> JOIN orders AS o ON users.id = o.user_id", - ); - dialects.verified_stmt("SELECT * FROM users |> LEFT JOIN orders AS o ON users.id = o.user_id"); - - // join pipe operator with complex ON condition - dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id AND orders.status = 'active'"); - dialects.verified_stmt("SELECT * FROM users |> LEFT JOIN orders ON users.id = orders.user_id AND orders.amount > 100"); - - // multiple join pipe operators - dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id |> JOIN products ON orders.product_id = products.id"); - dialects.verified_stmt("SELECT * FROM users |> LEFT JOIN orders ON users.id = orders.user_id |> RIGHT JOIN products ON orders.product_id = products.id"); - - // join pipe operator with other pipe operators - dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id |> WHERE orders.amount > 100"); - dialects.verified_stmt("SELECT * FROM users |> WHERE users.active = true |> LEFT JOIN orders ON users.id = orders.user_id"); - dialects.verified_stmt("SELECT * FROM users |> JOIN orders ON users.id = orders.user_id |> SELECT users.name, orders.amount"); -} - -#[test] -fn parse_pipeline_operator_negative_tests() { - let dialects = all_dialects_where(|d| d.supports_pipe_operator()); - - // Test that plain EXCEPT without DISTINCT fails - assert_eq!( - ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()), - dialects - .parse_sql_statements("SELECT * FROM users |> EXCEPT (SELECT * FROM admins)") - .unwrap_err() - ); - - // Test that EXCEPT ALL fails - assert_eq!( - ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()), - dialects - .parse_sql_statements("SELECT * FROM users |> EXCEPT ALL (SELECT * FROM admins)") - .unwrap_err() - ); - - // Test that EXCEPT BY NAME without DISTINCT fails - assert_eq!( - ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()), - dialects - .parse_sql_statements("SELECT * FROM users |> EXCEPT BY NAME (SELECT * FROM admins)") - .unwrap_err() - ); - - // Test that EXCEPT ALL BY NAME fails - assert_eq!( - ParserError::ParserError("EXCEPT pipe operator requires DISTINCT modifier".to_string()), - dialects - .parse_sql_statements( - "SELECT * FROM users |> EXCEPT ALL BY NAME (SELECT * FROM admins)" - ) - .unwrap_err() - ); - - // Test that plain INTERSECT without DISTINCT fails - assert_eq!( - ParserError::ParserError("INTERSECT pipe operator requires DISTINCT modifier".to_string()), - dialects - .parse_sql_statements("SELECT * FROM users |> INTERSECT (SELECT * FROM admins)") - .unwrap_err() - ); - - // Test that INTERSECT ALL fails - assert_eq!( - ParserError::ParserError("INTERSECT pipe operator requires DISTINCT modifier".to_string()), - dialects - .parse_sql_statements("SELECT * FROM users |> INTERSECT ALL (SELECT * FROM admins)") - .unwrap_err() - ); - - // Test that INTERSECT BY NAME without DISTINCT fails - assert_eq!( - ParserError::ParserError("INTERSECT pipe operator requires DISTINCT modifier".to_string()), - dialects - .parse_sql_statements("SELECT * FROM users |> INTERSECT BY NAME (SELECT * FROM admins)") - .unwrap_err() - ); - - // Test that INTERSECT ALL BY NAME fails - assert_eq!( - ParserError::ParserError("INTERSECT pipe operator requires DISTINCT modifier".to_string()), - dialects - .parse_sql_statements( - "SELECT * FROM users |> INTERSECT ALL BY NAME (SELECT * FROM admins)" - ) - .unwrap_err() - ); - - // Test that CALL without function name fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> CALL") - .is_err()); - - // Test that CALL without parentheses fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> CALL my_function") - .is_err()); - - // Test that CALL with invalid function syntax fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> CALL 123invalid") - .is_err()); - - // Test that CALL with malformed arguments fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> CALL my_function(,)") - .is_err()); - - // Test that CALL with invalid alias syntax fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> CALL my_function() AS") - .is_err()); - - // Test that PIVOT without parentheses fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> PIVOT SUM(amount) FOR month IN ('Jan')") - .is_err()); - - // Test that PIVOT without FOR keyword fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> PIVOT(SUM(amount) month IN ('Jan'))") - .is_err()); - - // Test that PIVOT without IN keyword fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> PIVOT(SUM(amount) FOR month ('Jan'))") - .is_err()); - - // Test that PIVOT with empty IN list fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> PIVOT(SUM(amount) FOR month IN ())") - .is_err()); - - // Test that PIVOT with invalid alias syntax fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> PIVOT(SUM(amount) FOR month IN ('Jan')) AS") - .is_err()); - - // Test UNPIVOT negative cases - - // Test that UNPIVOT without parentheses fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> UNPIVOT value FOR name IN col1, col2") - .is_err()); - - // Test that UNPIVOT without FOR keyword fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> UNPIVOT(value name IN (col1, col2))") - .is_err()); - - // Test that UNPIVOT without IN keyword fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> UNPIVOT(value FOR name (col1, col2))") - .is_err()); - - // Test that UNPIVOT with missing value column fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> UNPIVOT(FOR name IN (col1, col2))") - .is_err()); - - // Test that UNPIVOT with missing name column fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> UNPIVOT(value FOR IN (col1, col2))") - .is_err()); - - // Test that UNPIVOT with empty IN list fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> UNPIVOT(value FOR name IN ())") - .is_err()); - - // Test that UNPIVOT with invalid alias syntax fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> UNPIVOT(value FOR name IN (col1, col2)) AS") - .is_err()); - - // Test that UNPIVOT with missing closing parenthesis fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> UNPIVOT(value FOR name IN (col1, col2)") - .is_err()); - - // Test that JOIN without table name fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> JOIN ON users.id = orders.user_id") - .is_err()); - - // Test that CROSS JOIN with ON condition fails - assert!(dialects - .parse_sql_statements( - "SELECT * FROM users |> CROSS JOIN orders ON users.id = orders.user_id" - ) - .is_err()); - - // Test that CROSS JOIN with USING condition fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> CROSS JOIN orders USING (user_id)") - .is_err()); - - // Test that JOIN with empty USING list fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> JOIN orders USING ()") - .is_err()); - - // Test that JOIN with malformed ON condition fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> JOIN orders ON") - .is_err()); - - // Test that JOIN with invalid USING syntax fails - assert!(dialects - .parse_sql_statements("SELECT * FROM users |> JOIN orders USING user_id") - .is_err()); } #[test] @@ -15845,11 +15300,6 @@ fn parse_return() { let _ = all_dialects().verified_stmt("RETURN 1"); } -#[test] -fn parse_subquery_limit() { - let _ = all_dialects().verified_stmt("SELECT t1_id, t1_name FROM t1 WHERE t1_id IN (SELECT t2_id FROM t2 WHERE t1_name = t2_name LIMIT 10)"); -} - #[test] fn test_open() { let open_cursor = "OPEN Employee_Cursor"; @@ -15896,240 +15346,3 @@ fn check_enforced() { "CREATE TABLE t (a INT, b INT, c INT, CHECK (a > 0) NOT ENFORCED, CHECK (b > 0) ENFORCED, CHECK (c > 0))", ); } - -#[test] -fn join_precedence() { - all_dialects_except(|d| !d.supports_left_associative_joins_without_parens()) - .verified_query_with_canonical( - "SELECT * - FROM t1 - NATURAL JOIN t5 - INNER JOIN t0 ON (t0.v1 + t5.v0) > 0 - WHERE t0.v1 = t1.v0", - // canonical string without parentheses - "SELECT * FROM t1 NATURAL JOIN t5 INNER JOIN t0 ON (t0.v1 + t5.v0) > 0 WHERE t0.v1 = t1.v0", - ); - all_dialects_except(|d| d.supports_left_associative_joins_without_parens()).verified_query_with_canonical( - "SELECT * - FROM t1 - NATURAL JOIN t5 - INNER JOIN t0 ON (t0.v1 + t5.v0) > 0 - WHERE t0.v1 = t1.v0", - // canonical string with parentheses - "SELECT * FROM t1 NATURAL JOIN (t5 INNER JOIN t0 ON (t0.v1 + t5.v0) > 0) WHERE t0.v1 = t1.v0", - ); -} - -#[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"#; - match verified_stmt(sql) { - Statement::CreateProcedure { - or_alter, - name, - params, - .. - } => { - assert_eq!(or_alter, false); - assert_eq!(name.to_string(), "test_proc"); - let fake_span = Span { - start: Location { line: 0, column: 0 }, - end: Location { line: 0, column: 0 }, - }; - assert_eq!( - params, - Some(vec![ - ProcedureParam { - name: Ident { - value: "a".into(), - quote_style: None, - span: fake_span, - }, - data_type: DataType::Integer(None), - mode: Some(ArgMode::In) - }, - ProcedureParam { - name: Ident { - value: "b".into(), - quote_style: None, - span: fake_span, - }, - data_type: DataType::Text, - mode: Some(ArgMode::Out) - }, - ProcedureParam { - name: Ident { - value: "c".into(), - quote_style: None, - span: fake_span, - }, - data_type: DataType::Timestamp(None, TimezoneInfo::None), - mode: Some(ArgMode::InOut) - }, - ProcedureParam { - name: Ident { - value: "d".into(), - quote_style: None, - span: fake_span, - }, - data_type: DataType::Bool, - mode: None - }, - ]) - ); - } - _ => unreachable!(), - } -} - -#[test] -fn test_select_exclude() { - let dialects = all_dialects_where(|d| d.supports_select_wildcard_exclude()); - match &dialects - .verified_only_select("SELECT * EXCLUDE c1 FROM test") - .projection[0] - { - SelectItem::Wildcard(WildcardAdditionalOptions { opt_exclude, .. }) => { - assert_eq!( - *opt_exclude, - Some(ExcludeSelectItem::Single(Ident::new("c1"))) - ); - } - _ => unreachable!(), - } - match &dialects - .verified_only_select("SELECT * EXCLUDE (c1, c2) FROM test") - .projection[0] - { - SelectItem::Wildcard(WildcardAdditionalOptions { opt_exclude, .. }) => { - assert_eq!( - *opt_exclude, - Some(ExcludeSelectItem::Multiple(vec![ - Ident::new("c1"), - Ident::new("c2") - ])) - ); - } - _ => unreachable!(), - } - let select = dialects.verified_only_select("SELECT * EXCLUDE c1, c2 FROM test"); - match &select.projection[0] { - SelectItem::Wildcard(WildcardAdditionalOptions { opt_exclude, .. }) => { - assert_eq!( - *opt_exclude, - Some(ExcludeSelectItem::Single(Ident::new("c1"))) - ); - } - _ => unreachable!(), - } - match &select.projection[1] { - SelectItem::UnnamedExpr(Expr::Identifier(ident)) => { - assert_eq!(*ident, Ident::new("c2")); - } - _ => unreachable!(), - } - - let dialects = all_dialects_where(|d| d.supports_select_exclude()); - let select = dialects.verified_only_select("SELECT *, c1 EXCLUDE c1 FROM test"); - match &select.projection[0] { - SelectItem::Wildcard(additional_options) => { - assert_eq!(*additional_options, WildcardAdditionalOptions::default()); - } - _ => unreachable!(), - } - assert_eq!( - select.exclude, - Some(ExcludeSelectItem::Single(Ident::new("c1"))) - ); - - let dialects = all_dialects_where(|d| { - d.supports_select_wildcard_exclude() && !d.supports_select_exclude() - }); - let select = dialects.verified_only_select("SELECT * EXCLUDE c1 FROM test"); - match &select.projection[0] { - SelectItem::Wildcard(WildcardAdditionalOptions { opt_exclude, .. }) => { - assert_eq!( - *opt_exclude, - Some(ExcludeSelectItem::Single(Ident::new("c1"))) - ); - } - _ => unreachable!(), - } - - // Dialects that only support the wildcard form and do not accept EXCLUDE as an implicity alias - // will fail when encountered with the `c2` ident - let dialects = all_dialects_where(|d| { - d.supports_select_wildcard_exclude() - && !d.supports_select_exclude() - && d.is_column_alias(&Keyword::EXCLUDE, &mut Parser::new(d)) - }); - assert_eq!( - dialects - .parse_sql_statements("SELECT *, c1 EXCLUDE c2 FROM test") - .err() - .unwrap(), - ParserError::ParserError("Expected: end of statement, found: c2".to_string()) - ); - - // Dialects that only support the wildcard form and accept EXCLUDE as an implicity alias - // will fail when encountered with the `EXCLUDE` keyword - let dialects = all_dialects_where(|d| { - d.supports_select_wildcard_exclude() - && !d.supports_select_exclude() - && !d.is_column_alias(&Keyword::EXCLUDE, &mut Parser::new(d)) - }); - assert_eq!( - dialects - .parse_sql_statements("SELECT *, c1 EXCLUDE c2 FROM test") - .err() - .unwrap(), - ParserError::ParserError("Expected: end of statement, found: EXCLUDE".to_string()) - ); -} - -#[test] -fn test_no_semicolon_required_between_statements() { - let sql = r#" -SELECT * FROM tbl1 -SELECT * FROM tbl2 - "#; - - let dialects = all_dialects_with_options(ParserOptions { - trailing_commas: false, - unescape: true, - require_semicolon_stmt_delimiter: false, - }); - let stmts = dialects.parse_sql_statements(sql).unwrap(); - assert_eq!(stmts.len(), 2); - assert!(stmts.iter().all(|s| matches!(s, Statement::Query { .. }))); -} diff --git a/tests/sqlparser_databricks.rs b/tests/sqlparser_databricks.rs index a27e0699..99b7eecd 100644 --- a/tests/sqlparser_databricks.rs +++ b/tests/sqlparser_databricks.rs @@ -19,7 +19,6 @@ use sqlparser::ast::helpers::attached_token::AttachedToken; use sqlparser::ast::*; use sqlparser::dialect::{DatabricksDialect, GenericDialect}; use sqlparser::parser::ParserError; -use sqlparser::tokenizer::Span; use test_utils::*; #[macro_use] @@ -214,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() )]))) @@ -222,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 {quote}{object_name}{quote}")), + databricks().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)), Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote( quote, object_name.to_string(), @@ -234,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 {quote}my_catalog{quote}")), + databricks().verified_stmt(&format!("USE CATALOG {0}my_catalog{0}", quote)), Statement::Use(Use::Catalog(ObjectName::from(vec![Ident::with_quote( quote, "my_catalog".to_string(), )]))) ); assert_eq!( - databricks().verified_stmt(&format!("USE DATABASE {quote}my_database{quote}")), + databricks().verified_stmt(&format!("USE DATABASE {0}my_database{0}", quote)), Statement::Use(Use::Database(ObjectName::from(vec![Ident::with_quote( quote, "my_database".to_string(), )]))) ); assert_eq!( - databricks().verified_stmt(&format!("USE SCHEMA {quote}my_schema{quote}")), + databricks().verified_stmt(&format!("USE SCHEMA {0}my_schema{0}", quote)), Statement::Use(Use::Schema(ObjectName::from(vec![Ident::with_quote( quote, "my_schema".to_string(), @@ -329,10 +328,7 @@ fn data_type_timestamp_ntz() { databricks().verified_expr("TIMESTAMP_NTZ '2025-03-29T18:52:00'"), Expr::TypedString { data_type: DataType::TimestampNtz, - value: ValueWithSpan { - value: Value::SingleQuotedString("2025-03-29T18:52:00".to_owned()), - span: Span::empty(), - } + value: Value::SingleQuotedString("2025-03-29T18:52:00".to_owned()) } ); @@ -361,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 fe14b7ba..8e498365 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -24,7 +24,6 @@ use test_utils::*; use sqlparser::ast::*; use sqlparser::dialect::{DuckDbDialect, GenericDialect}; -use sqlparser::parser::ParserError; fn duckdb() -> TestedDialects { TestedDialects::new(vec![Box::new(DuckDbDialect {})]) @@ -45,12 +44,10 @@ fn test_struct() { StructField { field_name: Some(Ident::new("v")), field_type: DataType::Varchar(None), - options: None, }, StructField { field_name: Some(Ident::new("i")), field_type: DataType::Integer(None), - options: None, }, ], StructBracketKind::Parentheses, @@ -87,7 +84,6 @@ fn test_struct() { StructField { field_name: Some(Ident::new("v")), field_type: DataType::Varchar(None), - options: None, }, StructField { field_name: Some(Ident::new("s")), @@ -96,17 +92,14 @@ fn test_struct() { StructField { field_name: Some(Ident::new("a1")), field_type: DataType::Integer(None), - options: None, }, StructField { field_name: Some(Ident::new("a2")), field_type: DataType::Varchar(None), - options: None, }, ], StructBracketKind::Parentheses, ), - options: None, }, ], StructBracketKind::Parentheses, @@ -269,7 +262,6 @@ fn test_select_union_by_name() { distinct: None, top: None, projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions::default())], - exclude: None, top_before_distinct: false, into: None, from: vec![TableWithJoins { @@ -300,7 +292,6 @@ fn test_select_union_by_name() { distinct: None, top: None, projection: vec![SelectItem::Wildcard(WildcardAdditionalOptions::default())], - exclude: None, top_before_distinct: false, into: None, from: vec![TableWithJoins { @@ -371,7 +362,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 { @@ -795,7 +786,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() )]))) @@ -803,7 +794,7 @@ fn parse_use() { for "e in "e_styles { // Test single identifier with different type of quotes assert_eq!( - duckdb().verified_stmt(&format!("USE {quote}{object_name}{quote}")), + duckdb().verified_stmt(&format!("USE {0}{1}{0}", quote, object_name)), Statement::Use(Use::Object(ObjectName::from(vec![Ident::with_quote( quote, object_name.to_string(), @@ -815,9 +806,7 @@ fn parse_use() { for "e in "e_styles { // Test double identifier with different type of quotes assert_eq!( - duckdb().verified_stmt(&format!( - "USE {quote}CATALOG{quote}.{quote}my_schema{quote}" - )), + duckdb().verified_stmt(&format!("USE {0}CATALOG{0}.{0}my_schema{0}", quote)), Statement::Use(Use::Object(ObjectName::from(vec![ Ident::with_quote(quote, "CATALOG"), Ident::with_quote(quote, "my_schema") @@ -833,32 +822,3 @@ fn parse_use() { ]))) ); } - -#[test] -fn test_duckdb_trim() { - let real_sql = r#"SELECT customer_id, TRIM(item_price_id, '"', "a") AS item_price_id FROM models_staging.subscriptions"#; - assert_eq!(duckdb().verified_stmt(real_sql).to_string(), real_sql); - - let sql_only_select = "SELECT TRIM('xyz', 'a')"; - let select = duckdb().verified_only_select(sql_only_select); - assert_eq!( - &Expr::Trim { - expr: Box::new(Expr::Value( - Value::SingleQuotedString("xyz".to_owned()).with_empty_span() - )), - trim_where: None, - trim_what: None, - trim_characters: Some(vec![Expr::Value( - Value::SingleQuotedString("a".to_owned()).with_empty_span() - )]), - }, - expr_from_projection(only(&select.projection)) - ); - - // missing comma separation - let error_sql = "SELECT TRIM('xyz' 'a')"; - assert_eq!( - ParserError::ParserError("Expected: ), found: 'a'".to_owned()), - duckdb().parse_sql_statements(error_sql).unwrap_err() - ); -} diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index 56a72ec8..fd52b773 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 50c6448d..2a314502 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -32,7 +32,7 @@ use sqlparser::ast::DeclareAssignment::MsSqlAssignment; use sqlparser::ast::Value::SingleQuotedString; use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, MsSqlDialect}; -use sqlparser::parser::{Parser, ParserError, ParserOptions}; +use sqlparser::parser::{Parser, ParserError}; #[test] fn parse_mssql_identifiers() { @@ -126,7 +126,6 @@ fn parse_create_procedure() { projection: vec![SelectItem::UnnamedExpr(Expr::Value( (number("1")).with_empty_span() ))], - exclude: None, into: None, from: vec![], lateral_views: vec![], @@ -154,8 +153,7 @@ fn parse_create_procedure() { quote_style: None, span: Span::empty(), }, - data_type: DataType::Int(None), - mode: None, + data_type: DataType::Int(None) }, ProcedureParam { name: Ident { @@ -166,16 +164,14 @@ fn parse_create_procedure() { data_type: DataType::Varchar(Some(CharacterLength::IntegerLength { length: 256, unit: None - })), - mode: None, + })) } ]), name: ObjectName::from(vec![Ident { value: "test".into(), quote_style: None, span: Span::empty(), - }]), - language: None, + }]) } ) } @@ -1369,7 +1365,6 @@ fn parse_substring_in_select() { special: true, shorthand: false, })], - exclude: None, into: None, from: vec![TableWithJoins { relation: table_from_name(ObjectName::from(vec![Ident { @@ -1518,7 +1513,6 @@ fn parse_mssql_declare() { (Value::Number("4".parse().unwrap(), false)).with_empty_span() )), })], - exclude: None, into: None, from: vec![], lateral_views: vec![], @@ -1676,7 +1670,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() )]))) @@ -1684,7 +1678,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(), @@ -2190,7 +2184,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), } } @@ -2240,7 +2234,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 @@ -2261,7 +2255,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), } } @@ -2327,18 +2321,6 @@ fn ms() -> TestedDialects { TestedDialects::new(vec![Box::new(MsSqlDialect {})]) } -// MS SQL dialect with support for optional semi-colon statement delimiters -fn tsql() -> TestedDialects { - TestedDialects::new_with_options( - vec![Box::new(MsSqlDialect {})], - ParserOptions { - trailing_commas: false, - unescape: true, - require_semicolon_stmt_delimiter: false, - }, - ) -} - fn ms_and_generic() -> TestedDialects { TestedDialects::new(vec![Box::new(MsSqlDialect {}), Box::new(GenericDialect {})]) } @@ -2495,15 +2477,3 @@ fn parse_mssql_grant() { fn parse_mssql_deny() { ms().verified_stmt("DENY SELECT ON my_table TO public, db_admin"); } - -#[test] -fn test_tsql_no_semicolon_delimiter() { - let sql = r#" -DECLARE @X AS NVARCHAR(MAX)='x' -DECLARE @Y AS NVARCHAR(MAX)='y' - "#; - - let stmts = tsql().parse_sql_statements(sql).unwrap(); - assert_eq!(stmts.len(), 2); - assert!(stmts.iter().all(|s| matches!(s, Statement::Declare { .. }))); -} diff --git a/tests/sqlparser_mysql.rs b/tests/sqlparser_mysql.rs index 9068ed9c..540348ff 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,7 +601,8 @@ 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(), @@ -669,20 +670,6 @@ fn table_constraint_unique_primary_ctor( characteristics: Option, unique_index_type_display: Option, ) -> TableConstraint { - let columns = columns - .into_iter() - .map(|ident| IndexColumn { - column: OrderByExpr { - expr: Expr::Identifier(ident), - options: OrderByOptions { - asc: None, - nulls_first: None, - }, - with_fill: None, - }, - operator_class: None, - }) - .collect(); match unique_index_type_display { Some(index_type_display) => TableConstraint::Unique { name, @@ -808,67 +795,6 @@ fn parse_create_table_primary_and_unique_key_with_index_options() { } } -#[test] -fn parse_prefix_key_part() { - let expected = vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::value( - number("10"), - )))]; - for sql in [ - "CREATE INDEX idx_index ON t(textcol(10))", - "ALTER TABLE tab ADD INDEX idx_index (textcol(10))", - "ALTER TABLE tab ADD PRIMARY KEY (textcol(10))", - "ALTER TABLE tab ADD UNIQUE KEY (textcol(10))", - "ALTER TABLE tab ADD UNIQUE KEY (textcol(10))", - "ALTER TABLE tab ADD FULLTEXT INDEX (textcol(10))", - "CREATE TABLE t (textcol TEXT, INDEX idx_index (textcol(10)))", - ] { - match index_column(mysql_and_generic().verified_stmt(sql)) { - Expr::Function(Function { - name, - args: FunctionArguments::List(FunctionArgumentList { args, .. }), - .. - }) => { - assert_eq!(name.to_string(), "textcol"); - assert_eq!(args, expected); - } - expr => panic!("unexpected expression {expr} for {sql}"), - } - } -} - -#[test] -fn test_functional_key_part() { - assert_eq!( - index_column( - mysql_and_generic() - .verified_stmt("CREATE INDEX idx_index ON t((col COLLATE utf8mb4_bin) DESC)") - ), - Expr::Nested(Box::new(Expr::Collate { - expr: Box::new(Expr::Identifier("col".into())), - collation: ObjectName(vec![sqlparser::ast::ObjectNamePart::Identifier( - Ident::new("utf8mb4_bin") - )]), - })) - ); - assert_eq!( - index_column(mysql_and_generic().verified_stmt( - r#"CREATE TABLE t (jsoncol JSON, PRIMARY KEY ((CAST(col ->> '$.id' AS UNSIGNED)) ASC))"# - )), - Expr::Nested(Box::new(Expr::Cast { - kind: CastKind::Cast, - expr: Box::new(Expr::BinaryOp { - left: Box::new(Expr::Identifier(Ident::new("col"))), - op: BinaryOperator::LongArrow, - right: Box::new(Expr::Value( - Value::SingleQuotedString("$.id".to_string()).with_empty_span() - )), - }), - data_type: DataType::Unsigned, - format: None, - })), - ); -} - #[test] fn parse_create_table_primary_and_unique_key_with_index_type() { let sqls = ["UNIQUE", "PRIMARY KEY"].map(|key_ty| { @@ -1403,7 +1329,6 @@ fn parse_escaped_quote_identifiers_with_escape() { quote_style: Some('`'), span: Span::empty(), }))], - exclude: None, into: None, from: vec![], lateral_views: vec![], @@ -1442,7 +1367,6 @@ fn parse_escaped_quote_identifiers_with_no_escape() { ParserOptions { trailing_commas: false, unescape: false, - require_semicolon_stmt_delimiter: true, } ) .verified_stmt(sql), @@ -1458,7 +1382,6 @@ fn parse_escaped_quote_identifiers_with_no_escape() { quote_style: Some('`'), span: Span::empty(), }))], - exclude: None, into: None, from: vec![], lateral_views: vec![], @@ -1506,7 +1429,6 @@ fn parse_escaped_backticks_with_escape() { quote_style: Some('`'), span: Span::empty(), }))], - exclude: None, into: None, from: vec![], lateral_views: vec![], @@ -1558,7 +1480,6 @@ fn parse_escaped_backticks_with_no_escape() { quote_style: Some('`'), span: Span::empty(), }))], - exclude: None, into: None, from: vec![], lateral_views: vec![], @@ -2230,7 +2151,6 @@ fn parse_select_with_numeric_prefix_column_name() { projection: vec![SelectItem::UnnamedExpr(Expr::Identifier(Ident::new( "123col_$@123abc" )))], - exclude: None, into: None, from: vec![TableWithJoins { relation: table_from_name(ObjectName::from(vec![Ident::with_quote( @@ -2268,11 +2188,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. @@ -2282,11 +2202,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. @@ -2300,11 +2220,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. @@ -2314,11 +2234,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. @@ -2331,11 +2251,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. @@ -2352,11 +2272,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. @@ -2373,11 +2293,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), } } @@ -2398,6 +2318,7 @@ fn parse_select_with_concatenation_of_exp_number_and_numeric_prefix_column() { q.body, Box::new(SetExpr::Select(Box::new(Select { select_token: AttachedToken::empty(), + distinct: None, top: None, top_before_distinct: false, @@ -2405,7 +2326,6 @@ fn parse_select_with_concatenation_of_exp_number_and_numeric_prefix_column() { SelectItem::UnnamedExpr(Expr::value(number("123e4"))), SelectItem::UnnamedExpr(Expr::Identifier(Ident::new("123col_$@123abc"))) ], - exclude: None, into: None, from: vec![TableWithJoins { relation: table_from_name(ObjectName::from(vec![Ident::with_quote( @@ -2882,7 +2802,7 @@ fn parse_alter_table_with_algorithm() { vec![ AlterTableOperation::DropColumn { has_column_keyword: true, - column_names: vec![Ident::new("password_digest")], + column_name: Ident::new("password_digest"), if_exists: false, drop_behavior: None, }, @@ -2930,7 +2850,7 @@ fn parse_alter_table_with_lock() { vec![ AlterTableOperation::DropColumn { has_column_keyword: true, - column_names: vec![Ident::new("password_digest")], + column_name: Ident::new("password_digest"), if_exists: false, drop_behavior: None, }, @@ -3049,7 +2969,6 @@ fn parse_substring_in_select() { special: true, shorthand: false, })], - exclude: None, into: None, from: vec![TableWithJoins { relation: table_from_name(ObjectName::from(vec![Ident { @@ -3364,7 +3283,6 @@ fn parse_hex_string_introducer() { ) .into(), })], - exclude: None, from: vec![], lateral_views: vec![], prewhere: None, @@ -3624,7 +3542,6 @@ fn parse_grant() { with_grant_option, as_grantor: _, granted_by, - current_grants: _, } = stmt { assert_eq!( @@ -4118,28 +4035,3 @@ fn parse_alter_table_drop_index() { AlterTableOperation::DropIndex { name } if name.value == "idx_index" ); } - -#[test] -fn parse_json_member_of() { - mysql().verified_stmt(r#"SELECT 17 MEMBER OF('[23, "abc", 17, "ab", 10]')"#); - let sql = r#"SELECT 'ab' MEMBER OF('[23, "abc", 17, "ab", 10]')"#; - let stmt = mysql().verified_stmt(sql); - match stmt { - Statement::Query(query) => { - let select = query.body.as_select().unwrap(); - assert_eq!( - select.projection, - vec![SelectItem::UnnamedExpr(Expr::MemberOf(MemberOf { - value: Box::new(Expr::Value( - Value::SingleQuotedString("ab".to_string()).into() - )), - array: Box::new(Expr::Value( - Value::SingleQuotedString(r#"[23, "abc", 17, "ab", 10]"#.to_string()) - .into() - )), - }))] - ); - } - _ => panic!("Unexpected statement {stmt}"), - } -} diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index 0d1d138c..6f0ba9c6 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -606,10 +606,9 @@ fn parse_alter_table_constraints_unique_nulls_distinct() { .verified_stmt("ALTER TABLE t ADD CONSTRAINT b UNIQUE NULLS NOT DISTINCT (c)") { Statement::AlterTable { operations, .. } => match &operations[0] { - AlterTableOperation::AddConstraint { - constraint: TableConstraint::Unique { nulls_distinct, .. }, - .. - } => { + AlterTableOperation::AddConstraint(TableConstraint::Unique { + nulls_distinct, .. + }) => { assert_eq!(nulls_distinct, &NullsDistinctOption::NotDistinct) } _ => unreachable!(), @@ -765,7 +764,10 @@ fn parse_drop_extension() { #[test] fn parse_alter_table_alter_column() { - pg().verified_stmt("ALTER TABLE tab ALTER COLUMN is_active TYPE TEXT USING 'text'"); + pg().one_statement_parses_to( + "ALTER TABLE tab ALTER COLUMN is_active TYPE TEXT USING 'text'", + "ALTER TABLE tab ALTER COLUMN is_active SET DATA TYPE TEXT USING 'text'", + ); match alter_table_op( pg().verified_stmt( @@ -781,7 +783,6 @@ fn parse_alter_table_alter_column() { AlterColumnOperation::SetDataType { data_type: DataType::Text, using: Some(using_expr), - had_set: true, } ); } @@ -1305,7 +1306,6 @@ fn parse_copy_to() { }, } ], - exclude: None, into: None, from: vec![], lateral_views: vec![], @@ -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() ); @@ -2949,7 +2949,6 @@ fn parse_array_subquery_expr() { projection: vec![SelectItem::UnnamedExpr(Expr::Value( (number("1")).with_empty_span() ))], - exclude: None, into: None, from: vec![], lateral_views: vec![], @@ -2975,7 +2974,6 @@ fn parse_array_subquery_expr() { projection: vec![SelectItem::UnnamedExpr(Expr::Value( (number("2")).with_empty_span() ))], - exclude: None, into: None, from: vec![], lateral_views: vec![], @@ -3275,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:?}"), } @@ -5260,10 +5258,7 @@ fn parse_at_time_zone() { left: Box::new(Expr::AtTimeZone { timestamp: Box::new(Expr::TypedString { data_type: DataType::Timestamp(None, TimezoneInfo::None), - value: ValueWithSpan { - value: Value::SingleQuotedString("2001-09-28 01:00".to_string()), - span: Span::empty(), - }, + value: Value::SingleQuotedString("2001-09-28 01:00".to_string()), }), time_zone: Box::new(Expr::Cast { kind: CastKind::DoubleColon, @@ -5684,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!( @@ -5778,7 +5773,8 @@ 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(); @@ -6205,153 +6201,3 @@ fn parse_alter_table_replica_identity() { _ => unreachable!(), } } - -#[test] -fn parse_ts_datatypes() { - match pg_and_generic().verified_stmt("CREATE TABLE foo (x TSVECTOR)") { - Statement::CreateTable(CreateTable { columns, .. }) => { - assert_eq!( - columns, - vec![ColumnDef { - name: "x".into(), - data_type: DataType::TsVector, - options: vec![], - }] - ); - } - _ => unreachable!(), - } - - match pg_and_generic().verified_stmt("CREATE TABLE foo (x TSQUERY)") { - Statement::CreateTable(CreateTable { columns, .. }) => { - assert_eq!( - columns, - vec![ColumnDef { - name: "x".into(), - data_type: DataType::TsQuery, - options: vec![], - }] - ); - } - _ => unreachable!(), - } -} - -#[test] -fn parse_alter_table_constraint_not_valid() { - match pg_and_generic().verified_stmt( - "ALTER TABLE foo ADD CONSTRAINT bar FOREIGN KEY (baz) REFERENCES other(ref) NOT VALID", - ) { - Statement::AlterTable { operations, .. } => { - assert_eq!( - operations, - vec![AlterTableOperation::AddConstraint { - constraint: TableConstraint::ForeignKey { - name: Some("bar".into()), - index_name: None, - columns: vec!["baz".into()], - foreign_table: ObjectName::from(vec!["other".into()]), - referred_columns: vec!["ref".into()], - on_delete: None, - on_update: None, - characteristics: None, - }, - not_valid: true, - }] - ); - } - _ => unreachable!(), - } -} - -#[test] -fn parse_alter_table_validate_constraint() { - match pg_and_generic().verified_stmt("ALTER TABLE foo VALIDATE CONSTRAINT bar") { - Statement::AlterTable { operations, .. } => { - assert_eq!( - operations, - vec![AlterTableOperation::ValidateConstraint { name: "bar".into() }] - ); - } - _ => unreachable!(), - } -} - -#[test] -fn parse_create_server() { - let test_cases = vec![ - ( - "CREATE SERVER myserver FOREIGN DATA WRAPPER postgres_fdw", - CreateServerStatement { - name: ObjectName::from(vec!["myserver".into()]), - if_not_exists: false, - server_type: None, - version: None, - foreign_data_wrapper: ObjectName::from(vec!["postgres_fdw".into()]), - options: None, - }, - ), - ( - "CREATE SERVER IF NOT EXISTS myserver TYPE 'server_type' VERSION 'server_version' FOREIGN DATA WRAPPER postgres_fdw", - CreateServerStatement { - name: ObjectName::from(vec!["myserver".into()]), - if_not_exists: true, - server_type: Some(Ident { - value: "server_type".to_string(), - quote_style: Some('\''), - span: Span::empty(), - }), - version: Some(Ident { - value: "server_version".to_string(), - quote_style: Some('\''), - span: Span::empty(), - }), - foreign_data_wrapper: ObjectName::from(vec!["postgres_fdw".into()]), - options: None, - } - ), - ( - "CREATE SERVER myserver2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host 'foo', dbname 'foodb', port '5432')", - CreateServerStatement { - name: ObjectName::from(vec!["myserver2".into()]), - if_not_exists: false, - server_type: None, - version: None, - foreign_data_wrapper: ObjectName::from(vec!["postgres_fdw".into()]), - options: Some(vec![ - CreateServerOption { - key: "host".into(), - value: Ident { - value: "foo".to_string(), - quote_style: Some('\''), - span: Span::empty(), - }, - }, - CreateServerOption { - key: "dbname".into(), - value: Ident { - value: "foodb".to_string(), - quote_style: Some('\''), - span: Span::empty(), - }, - }, - CreateServerOption { - key: "port".into(), - value: Ident { - value: "5432".to_string(), - quote_style: Some('\''), - span: Span::empty(), - }, - }, - ]), - } - ) - ]; - - for (sql, expected) in test_cases { - let Statement::CreateServer(stmt) = pg_and_generic().verified_stmt(sql) else { - unreachable!() - }; - assert_eq!(stmt, expected); - } -} diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index d539adf6..be2b6722 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -402,8 +402,3 @@ fn parse_extract_single_quotes() { fn parse_string_literal_backslash_escape() { redshift().one_statement_parses_to(r#"SELECT 'l\'auto'"#, "SELECT 'l''auto'"); } - -#[test] -fn parse_utf8_multibyte_idents() { - redshift().verified_stmt("SELECT 🚀.city AS 🎸 FROM customers AS 🚀"); -} diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index 65546bee..7d734ca2 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -270,8 +270,8 @@ fn test_snowflake_create_table_with_tag() { assert_eq!("my_table", name.to_string()); assert_eq!( Some(vec![ - Tag::new(ObjectName::from(vec![Ident::new("A")]), "TAG A".to_string()), - Tag::new(ObjectName::from(vec![Ident::new("B")]), "TAG B".to_string()) + Tag::new("A".into(), "TAG A".to_string()), + Tag::new("B".into(), "TAG B".to_string()) ]), with_tags ); @@ -291,8 +291,8 @@ fn test_snowflake_create_table_with_tag() { assert_eq!("my_table", name.to_string()); assert_eq!( Some(vec![ - Tag::new(ObjectName::from(vec![Ident::new("A")]), "TAG A".to_string()), - Tag::new(ObjectName::from(vec![Ident::new("B")]), "TAG B".to_string()) + Tag::new("A".into(), "TAG A".to_string()), + Tag::new("B".into(), "TAG B".to_string()) ]), with_tags ); @@ -731,7 +731,7 @@ fn test_snowflake_create_table_with_columns_masking_policy() { option: ColumnOption::Policy(ColumnPolicy::MaskingPolicy( ColumnPolicyProperty { with, - policy_name: ObjectName::from(vec![Ident::new("p")]), + policy_name: "p".into(), using_columns, } )) @@ -765,7 +765,7 @@ fn test_snowflake_create_table_with_columns_projection_policy() { option: ColumnOption::Policy(ColumnPolicy::ProjectionPolicy( ColumnPolicyProperty { with, - policy_name: ObjectName::from(vec![Ident::new("p")]), + policy_name: "p".into(), using_columns: None, } )) @@ -802,14 +802,8 @@ fn test_snowflake_create_table_with_columns_tags() { option: ColumnOption::Tags(TagsColumnOption { with, tags: vec![ - Tag::new( - ObjectName::from(vec![Ident::new("A")]), - "TAG A".into() - ), - Tag::new( - ObjectName::from(vec![Ident::new("B")]), - "TAG B".into() - ), + Tag::new("A".into(), "TAG A".into()), + Tag::new("B".into(), "TAG B".into()), ] }), }], @@ -852,7 +846,7 @@ fn test_snowflake_create_table_with_several_column_options() { option: ColumnOption::Policy(ColumnPolicy::MaskingPolicy( ColumnPolicyProperty { with: true, - policy_name: ObjectName::from(vec![Ident::new("p1")]), + policy_name: "p1".into(), using_columns: Some(vec!["a".into(), "b".into()]), } )), @@ -862,14 +856,8 @@ fn test_snowflake_create_table_with_several_column_options() { option: ColumnOption::Tags(TagsColumnOption { with: true, tags: vec![ - Tag::new( - ObjectName::from(vec![Ident::new("A")]), - "TAG A".into() - ), - Tag::new( - ObjectName::from(vec![Ident::new("B")]), - "TAG B".into() - ), + Tag::new("A".into(), "TAG A".into()), + Tag::new("B".into(), "TAG B".into()), ] }), } @@ -890,7 +878,7 @@ fn test_snowflake_create_table_with_several_column_options() { option: ColumnOption::Policy(ColumnPolicy::ProjectionPolicy( ColumnPolicyProperty { with: false, - policy_name: ObjectName::from(vec![Ident::new("p2")]), + policy_name: "p2".into(), using_columns: None, } )), @@ -900,14 +888,8 @@ fn test_snowflake_create_table_with_several_column_options() { option: ColumnOption::Tags(TagsColumnOption { with: false, tags: vec![ - Tag::new( - ObjectName::from(vec![Ident::new("C")]), - "TAG C".into() - ), - Tag::new( - ObjectName::from(vec![Ident::new("D")]), - "TAG D".into() - ), + Tag::new("C".into(), "TAG C".into()), + Tag::new("D".into(), "TAG D".into()), ] }), } @@ -960,8 +942,8 @@ fn test_snowflake_create_iceberg_table_all_options() { with_aggregation_policy.map(|name| name.to_string()) ); assert_eq!(Some(vec![ - Tag::new(ObjectName::from(vec![Ident::new("A")]), "TAG A".into()), - Tag::new(ObjectName::from(vec![Ident::new("B")]), "TAG B".into()), + Tag::new("A".into(), "TAG A".into()), + Tag::new("B".into(), "TAG B".into()), ]), with_tags); } @@ -2528,7 +2510,10 @@ 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 {formatted_name} FROM 'gcs://mybucket/./../a.csv'"); + let sql = format!( + "COPY INTO {} FROM 'gcs://mybucket/./../a.csv'", + formatted_name + ); match snowflake().verified_stmt(&sql) { Statement::CopyIntoSnowflake { into, .. } => { assert_eq!(into.0, object_name.0) @@ -2551,7 +2536,10 @@ 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 {formatted_name} FROM 'gcs://mybucket/./../a.csv'"); + let sql = format!( + "COPY INTO {} FROM 'gcs://mybucket/./../a.csv'", + formatted_name + ); match snowflake().verified_stmt(&sql) { Statement::CopyIntoSnowflake { into, .. } => { assert_eq!(into.0, object_name.0) @@ -2581,26 +2569,6 @@ fn test_snowflake_copy_into() { } _ => unreachable!(), } - - // Test for non-ident characters in stage names - let sql = "COPY INTO a.b FROM @namespace.stage_name/x@x~x%x+"; - assert_eq!(snowflake().verified_stmt(sql).to_string(), sql); - match snowflake().verified_stmt(sql) { - Statement::CopyIntoSnowflake { into, from_obj, .. } => { - assert_eq!( - into, - ObjectName::from(vec![Ident::new("a"), Ident::new("b")]) - ); - assert_eq!( - from_obj, - Some(ObjectName::from(vec![ - Ident::new("@namespace"), - Ident::new("stage_name/x@x~x%x+") - ])) - ) - } - _ => unreachable!(), - } } #[test] @@ -3052,7 +3020,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() )]))) @@ -3060,7 +3028,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(), @@ -3072,9 +3040,7 @@ fn parse_use() { for "e in "e_styles { // Test double identifier with different type of quotes assert_eq!( - snowflake().verified_stmt(&format!( - "USE {quote}CATALOG{quote}.{quote}my_schema{quote}" - )), + snowflake().verified_stmt(&format!("USE {0}CATALOG{0}.{0}my_schema{0}", quote)), Statement::Use(Use::Object(ObjectName::from(vec![ Ident::with_quote(quote, "CATALOG"), Ident::with_quote(quote, "my_schema") @@ -3093,37 +3059,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 {quote}my_database{quote}")), + snowflake().verified_stmt(&format!("USE DATABASE {0}my_database{0}", quote)), Statement::Use(Use::Database(ObjectName::from(vec![Ident::with_quote( quote, "my_database".to_string(), )]))) ); assert_eq!( - snowflake().verified_stmt(&format!("USE SCHEMA {quote}my_schema{quote}")), + snowflake().verified_stmt(&format!("USE SCHEMA {0}my_schema{0}", quote)), Statement::Use(Use::Schema(ObjectName::from(vec![Ident::with_quote( quote, "my_schema".to_string(), )]))) ); assert_eq!( - snowflake().verified_stmt(&format!( - "USE SCHEMA {quote}CATALOG{quote}.{quote}my_schema{quote}" - )), + snowflake().verified_stmt(&format!("USE SCHEMA {0}CATALOG{0}.{0}my_schema{0}", 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 {quote}my_role{quote}")), + snowflake().verified_stmt(&format!("USE ROLE {0}my_role{0}", quote)), Statement::Use(Use::Role(ObjectName::from(vec![Ident::with_quote( quote, "my_role".to_string(), )]))) ); assert_eq!( - snowflake().verified_stmt(&format!("USE WAREHOUSE {quote}my_wh{quote}")), + snowflake().verified_stmt(&format!("USE WAREHOUSE {0}my_wh{0}", quote)), Statement::Use(Use::Warehouse(ObjectName::from(vec![Ident::with_quote( quote, "my_wh".to_string(), @@ -3160,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() + snowflake_and_generic() .verified_stmt(sql); } } @@ -3169,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().verified_stmt(sql) { + match snowflake_and_generic().verified_stmt(sql) { Statement::CreateView { name, columns, .. } => { assert_eq!(name.to_string(), "v"); assert_eq!( @@ -3178,9 +3142,7 @@ fn parse_view_column_descriptions() { ViewColumnDef { name: Ident::new("a"), data_type: None, - options: Some(ColumnOptions::SpaceSeparated(vec![ColumnOption::Comment( - "Comment".to_string() - )])), + options: Some(vec![ColumnOption::Comment("Comment".to_string())]), }, ViewColumnDef { name: Ident::new("b"), @@ -3435,38 +3397,10 @@ fn parse_ls_and_rm() { .unwrap(); } -#[test] -fn test_sql_keywords_as_select_item_ident() { - // Some keywords that should be parsed as an alias - let unreserved_kws = vec!["CLUSTER", "FETCH", "RETURNING", "LIMIT", "EXCEPT", "SORT"]; - for kw in unreserved_kws { - snowflake().verified_stmt(&format!("SELECT 1, {kw}")); - } - - // Some keywords that should not be parsed as an alias - let reserved_kws = vec![ - "FROM", - "GROUP", - "HAVING", - "INTERSECT", - "INTO", - "ORDER", - "SELECT", - "UNION", - "WHERE", - "WITH", - ]; - for kw in reserved_kws { - assert!(snowflake() - .parse_sql_statements(&format!("SELECT 1, {kw}")) - .is_err()); - } -} - #[test] fn test_sql_keywords_as_select_item_aliases() { // Some keywords that should be parsed as an alias - let unreserved_kws = vec!["CLUSTER", "FETCH", "RETURNING", "LIMIT", "EXCEPT", "SORT"]; + let unreserved_kws = vec!["CLUSTER", "FETCH", "RETURNING", "LIMIT", "EXCEPT"]; for kw in unreserved_kws { snowflake() .one_statement_parses_to(&format!("SELECT 1 {kw}"), &format!("SELECT 1 AS {kw}")); @@ -3492,57 +3426,6 @@ fn test_sql_keywords_as_select_item_aliases() { } } -#[test] -fn test_sql_keywords_as_table_aliases() { - // Some keywords that should be parsed as an alias implicitly - let unreserved_kws = vec![ - "VIEW", - "EXPLAIN", - "ANALYZE", - "SORT", - "PIVOT", - "UNPIVOT", - "TOP", - "LIMIT", - "OFFSET", - "FETCH", - "EXCEPT", - "CLUSTER", - "DISTRIBUTE", - "GLOBAL", - "ANTI", - "SEMI", - "RETURNING", - "OUTER", - "WINDOW", - "END", - "PARTITION", - "PREWHERE", - "SETTINGS", - "FORMAT", - "MATCH_RECOGNIZE", - "OPEN", - ]; - - for kw in unreserved_kws { - snowflake().verified_stmt(&format!("SELECT * FROM tbl AS {kw}")); - snowflake().one_statement_parses_to( - &format!("SELECT * FROM tbl {kw}"), - &format!("SELECT * FROM tbl AS {kw}"), - ); - } - - // Some keywords that should not be parsed as an alias implicitly - let reserved_kws = vec![ - "FROM", "GROUP", "HAVING", "ORDER", "SELECT", "UNION", "WHERE", "WITH", - ]; - for kw in reserved_kws { - assert!(snowflake() - .parse_sql_statements(&format!("SELECT * FROM tbl {kw}")) - .is_err()); - } -} - #[test] fn test_timetravel_at_before() { snowflake().verified_only_select("SELECT * FROM tbl AT(TIMESTAMP => '2024-12-15 00:00:00')"); @@ -3744,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), } } @@ -4199,242 +4082,3 @@ fn parse_connect_by_root_operator() { "sql parser error: Expected an expression, found: FROM" ); } - -#[test] -fn test_begin_exception_end() { - for sql in [ - "BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END", - "BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE EX_1; END", - "BEGIN SELECT 1; EXCEPTION WHEN FOO THEN SELECT 2; WHEN OTHER THEN SELECT 3; RAISE; END", - "BEGIN BEGIN SELECT 1; EXCEPTION WHEN OTHER THEN SELECT 2; RAISE; END; END", - ] { - snowflake().verified_stmt(sql); - } - - let sql = r#" -DECLARE - EXCEPTION_1 EXCEPTION (-20001, 'I caught the expected exception.'); - EXCEPTION_2 EXCEPTION (-20002, 'Not the expected exception!'); - EXCEPTION_3 EXCEPTION (-20003, 'The worst exception...'); -BEGIN - BEGIN - SELECT 1; - EXCEPTION - WHEN EXCEPTION_1 THEN - SELECT 1; - WHEN EXCEPTION_2 OR EXCEPTION_3 THEN - SELECT 2; - SELECT 3; - WHEN OTHER THEN - SELECT 4; - RAISE; - END; -END -"#; - - // Outer `BEGIN` of the two nested `BEGIN` statements. - let Statement::StartTransaction { mut statements, .. } = snowflake() - .parse_sql_statements(sql) - .unwrap() - .pop() - .unwrap() - else { - unreachable!(); - }; - - // Inner `BEGIN` of the two nested `BEGIN` statements. - let Statement::StartTransaction { - statements, - exception, - has_end_keyword, - .. - } = statements.pop().unwrap() - else { - unreachable!(); - }; - - assert_eq!(1, statements.len()); - assert!(has_end_keyword); - - let exception = exception.unwrap(); - assert_eq!(3, exception.len()); - assert_eq!(1, exception[0].idents.len()); - assert_eq!(1, exception[0].statements.len()); - assert_eq!(2, exception[1].idents.len()); - assert_eq!(2, exception[1].statements.len()); -} - -#[test] -fn test_snowflake_fetch_clause_syntax() { - let canonical = "SELECT c1 FROM fetch_test FETCH FIRST 2 ROWS ONLY"; - snowflake().verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH 2", canonical); - - snowflake() - .verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH FIRST 2", canonical); - snowflake() - .verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH NEXT 2", canonical); - - snowflake() - .verified_only_select_with_canonical("SELECT c1 FROM fetch_test FETCH 2 ROW", canonical); - - snowflake().verified_only_select_with_canonical( - "SELECT c1 FROM fetch_test FETCH FIRST 2 ROWS", - 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); -} - -#[test] -fn test_snowflake_create_view_with_composite_tag() { - let create_view_with_tag = - r#"CREATE VIEW X (COL WITH TAG (foo.bar.baz.pii='email')) AS SELECT * FROM Y"#; - snowflake().verified_stmt(create_view_with_tag); -} - -#[test] -fn test_snowflake_create_view_with_composite_policy_name() { - let create_view_with_tag = - r#"CREATE VIEW X (COL WITH MASKING POLICY foo.bar.baz) AS SELECT * FROM Y"#; - snowflake().verified_stmt(create_view_with_tag); -} - -#[test] -fn test_snowflake_identifier_function() { - // Using IDENTIFIER to reference a column - match &snowflake() - .verified_only_select("SELECT identifier('email') FROM customers") - .projection[0] - { - SelectItem::UnnamedExpr(Expr::Function(Function { name, args, .. })) => { - assert_eq!(*name, ObjectName::from(vec![Ident::new("identifier")])); - assert_eq!( - *args, - FunctionArguments::List(FunctionArgumentList { - args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( - Value::SingleQuotedString("email".to_string()).into() - )))], - clauses: vec![], - duplicate_treatment: None - }) - ); - } - _ => unreachable!(), - } - - // Using IDENTIFIER to reference a case-sensitive column - match &snowflake() - .verified_only_select(r#"SELECT identifier('"Email"') FROM customers"#) - .projection[0] - { - SelectItem::UnnamedExpr(Expr::Function(Function { name, args, .. })) => { - assert_eq!(*name, ObjectName::from(vec![Ident::new("identifier")])); - assert_eq!( - *args, - FunctionArguments::List(FunctionArgumentList { - args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( - Value::SingleQuotedString("\"Email\"".to_string()).into() - )))], - clauses: vec![], - duplicate_treatment: None - }) - ); - } - _ => unreachable!(), - } - - // Using IDENTIFIER to reference an alias of a table - match &snowflake() - .verified_only_select("SELECT identifier('alias1').* FROM tbl AS alias1") - .projection[0] - { - SelectItem::QualifiedWildcard( - SelectItemQualifiedWildcardKind::Expr(Expr::Function(Function { name, args, .. })), - _, - ) => { - assert_eq!(*name, ObjectName::from(vec![Ident::new("identifier")])); - assert_eq!( - *args, - FunctionArguments::List(FunctionArgumentList { - args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( - Value::SingleQuotedString("alias1".to_string()).into() - )))], - clauses: vec![], - duplicate_treatment: None - }) - ); - } - _ => unreachable!(), - } - - // Using IDENTIFIER to reference a database - match snowflake().verified_stmt("CREATE DATABASE IDENTIFIER('tbl')") { - Statement::CreateDatabase { db_name, .. } => { - assert_eq!( - db_name, - ObjectName(vec![ObjectNamePart::Function(ObjectNamePartFunction { - name: Ident::new("IDENTIFIER"), - args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( - Value::SingleQuotedString("tbl".to_string()).into() - )))] - })]) - ); - } - _ => unreachable!(), - } - - // Using IDENTIFIER to reference a schema - match snowflake().verified_stmt("CREATE SCHEMA IDENTIFIER('db1.sc1')") { - Statement::CreateSchema { schema_name, .. } => { - assert_eq!( - schema_name, - SchemaName::Simple(ObjectName(vec![ObjectNamePart::Function( - ObjectNamePartFunction { - name: Ident::new("IDENTIFIER"), - args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( - Value::SingleQuotedString("db1.sc1".to_string()).into() - )))] - } - )])) - ); - } - _ => unreachable!(), - } - - // Using IDENTIFIER to reference a table - match snowflake().verified_stmt("CREATE TABLE IDENTIFIER('tbl') (id INT)") { - Statement::CreateTable(CreateTable { name, .. }) => { - assert_eq!( - name, - ObjectName(vec![ObjectNamePart::Function(ObjectNamePartFunction { - name: Ident::new("IDENTIFIER"), - args: vec![FunctionArg::Unnamed(FunctionArgExpr::Expr(Expr::Value( - Value::SingleQuotedString("tbl".to_string()).into() - )))] - })]) - ); - } - _ => unreachable!(), - } - - // Cannot have more than one IDENTIFIER part in an object name - assert_eq!( - snowflake() - .parse_sql_statements( - "CREATE TABLE IDENTIFIER('db1').IDENTIFIER('sc1').IDENTIFIER('tbl') (id INT)" - ) - .is_err(), - true - ); - assert_eq!( - snowflake() - .parse_sql_statements("CREATE TABLE IDENTIFIER('db1')..IDENTIFIER('tbl') (id INT)") - .is_err(), - true - ); -} diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index 06496f0c..b759065f 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 {func_name}(x) FILTER (WHERE y) OVER () FROM t"); + let sql = format!("SELECT {}(x) FILTER (WHERE y) OVER () FROM t", func_name); 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,