diff --git a/.github/actions/setup-builder/action.yaml b/.github/actions/setup-builder/action.yaml new file mode 100644 index 000000000..61faa055b --- /dev/null +++ b/.github/actions/setup-builder/action.yaml @@ -0,0 +1,42 @@ +# 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: Prepare Rust Builder +description: 'Prepare Rust Build Environment' +inputs: + rust-version: + description: 'version of rust to install (e.g. stable)' + required: true + default: 'stable' + targets: + description: 'The toolchain targets to add, comma-separated' + default: '' + +runs: + using: "composite" + steps: + - name: Setup Rust Toolchain + shell: bash + run: | + echo "Installing ${{ inputs.rust-version }}" + if [ -n "${{ inputs.targets}}" ]; then + rustup toolchain install ${{ inputs.rust-version }} -t ${{ inputs.targets }} + else + rustup toolchain install ${{ inputs.rust-version }} + fi + rustup default ${{ inputs.rust-version }} + rustup component add rustfmt clippy diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml index 64c4d114a..146ea3120 100644 --- a/.github/workflows/rust.yml +++ b/.github/workflows/rust.yml @@ -7,32 +7,29 @@ jobs: codestyle: runs-on: ubuntu-latest steps: - - name: Set up Rust - uses: hecrj/setup-rust-action@v1 + - uses: actions/checkout@v4 + - name: Setup Rust Toolchain + uses: ./.github/actions/setup-builder with: - components: rustfmt # Note that `nightly` is required for `license_template_path`, as # it's an unstable feature. rust-version: nightly - - uses: actions/checkout@v2 - run: cargo +nightly fmt -- --check --config-path <(echo 'license_template_path = "HEADER"') lint: runs-on: ubuntu-latest steps: - - name: Set up Rust - uses: hecrj/setup-rust-action@v1 - with: - components: clippy - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + - name: Setup Rust Toolchain + uses: ./.github/actions/setup-builder - run: cargo clippy --all-targets --all-features -- -D warnings compile: runs-on: ubuntu-latest steps: - - name: Set up Rust - uses: hecrj/setup-rust-action@v1 - - uses: actions/checkout@master + - uses: actions/checkout@v4 + - name: Setup Rust Toolchain + uses: ./.github/actions/setup-builder - run: cargo check --all-targets --all-features docs: @@ -40,19 +37,19 @@ jobs: env: RUSTDOCFLAGS: "-Dwarnings" steps: - - name: Set up Rust - uses: hecrj/setup-rust-action@v1 - - uses: actions/checkout@master + - uses: actions/checkout@v4 + - name: Setup Rust Toolchain + uses: ./.github/actions/setup-builder - run: cargo doc --document-private-items --no-deps --workspace --all-features compile-no-std: runs-on: ubuntu-latest steps: - - name: Set up Rust - uses: hecrj/setup-rust-action@v1 + - uses: actions/checkout@v4 + - name: Setup Rust Toolchain + uses: ./.github/actions/setup-builder with: targets: 'thumbv6m-none-eabi' - - uses: actions/checkout@master - run: cargo check --no-default-features --target thumbv6m-none-eabi test: @@ -61,8 +58,10 @@ jobs: rust: [stable, beta, nightly] runs-on: ubuntu-latest steps: - - name: Setup Rust - uses: hecrj/setup-rust-action@v1 + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Rust Toolchain + uses: ./.github/actions/setup-builder with: rust-version: ${{ matrix.rust }} - name: Install Tarpaulin @@ -71,16 +70,16 @@ jobs: crate: cargo-tarpaulin version: 0.14.2 use-tool-cache: true - - name: Checkout - uses: actions/checkout@v2 - name: Test run: cargo test --all-features test-coverage: runs-on: ubuntu-latest steps: - - name: Setup Rust - uses: hecrj/setup-rust-action@v1 + - name: Checkout + uses: actions/checkout@v4 + - name: Setup Rust Toolchain + uses: ./.github/actions/setup-builder with: rust-version: stable - name: Install Tarpaulin @@ -89,8 +88,6 @@ jobs: crate: cargo-tarpaulin version: 0.14.2 use-tool-cache: true - - name: Checkout - uses: actions/checkout@v2 - name: Coverage run: cargo tarpaulin -o Lcov --output-dir ./coverage - name: Coveralls @@ -103,9 +100,9 @@ jobs: runs-on: ubuntu-latest needs: [test] steps: - - name: Set up Rust - uses: hecrj/setup-rust-action@v1 - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 + - name: Setup Rust Toolchain + uses: ./.github/actions/setup-builder - name: Publish shell: bash run: | diff --git a/.gitignore b/.gitignore index d41369207..4c6821d47 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,5 @@ Cargo.lock .vscode *.swp + +.DS_store \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index ed5c9ecb4..c77755d59 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,96 @@ changes that break via addition as "Added". ## [Unreleased] Check https://github.com/sqlparser-rs/sqlparser-rs/commits/main for undocumented changes. + +## [0.51.0] 2024-09-11 +As always, huge props to @iffyio @jmhain and @lovasoa for their help reviewing and merging PRs πŸ™. +Without them this project would not be possible. + +Reminder: we are in the final phases of moving sqlparser-rs into the Apache +DataFusion project: https://github.com/sqlparser-rs/sqlparser-rs/issues/1294 + +### Fixed +* Fix Hive table comment should be after table column definitions (#1413) - Thanks @git-hulk +* Fix stack overflow in `parse_subexpr` (#1410) - Thanks @eejbyfeldt +* Fix `INTERVAL` parsing to support expressions and units via dialect (#1398) - Thanks @samuelcolvin +* Fix identifiers starting with `$` should be regarded as a placeholder in SQLite (#1402) - Thanks @git-hulk + +### Added +* Support for MSSQL table options (#1414) - Thanks @bombsimon +* Test showing how negative constants are parsed (#1421) - Thanks @alamb +* Support databricks dialect to dialect_from_str (#1416) - Thanks @milenkovicmalamb +* Support `DROP|CLEAR|MATERIALIZE PROJECTION` syntax for ClickHouse (#1417) - Thanks @git-hulk +* Support postgres `TRUNCATE` syntax (#1406) - Thanks @tobyhede +* Support `CREATE INDEX` with clause (#1389) - Thanks @lewiszlw +* Support parsing `CLUSTERED BY` clause for Hive (#1397) - Thanks @git-hulk +* Support different `USE` statement syntaxes (#1387) - Thanks @kacpermuda +* Support `ADD PROJECTION` syntax for ClickHouse (#1390) - Thanks @git-hulk + +### Changed +* Implement common traits for OneOrManyWithParens (#1368) - Thanks @gstvg +* Cleanup parse_statement (#1407) - Thanks @samuelcolvin +* Allow `DateTimeField::Custom` with `EXTRACT` in Postgres (#1394) - Thanks @samuelcolvin + + +## [0.50.0] 2024-08-15 +Again, huge props to @iffyio @jmhain and @lovasoa for their help reviewing and merging PRs πŸ™. +Without them this project would not be possible. + +Reminder: are in the process of moving sqlparser to governed as part of the Apache +DataFusion project: https://github.com/sqlparser-rs/sqlparser-rs/issues/1294 + +### Fixed +* Clippy 1.80 warnings (#1357) - Thanks @lovasoa + +### Added +* Support `STRUCT` and list of structs for DuckDB dialect (#1372) - Thanks @jayzhan211 +* Support custom lexical precedence in PostgreSQL dialect (#1379) - Thanks @samuelcolvin +* Support `FREEZE|UNFREEZE PARTITION` syntax for ClickHouse (#1380) - Thanks @git-hulk +* Support scale in `CEIL` and `FLOOR` functions (#1377) - Thanks @seve-martinez +* Support `CREATE TRIGGER` and `DROP TRIGGER` statements (#1352) - Thanks @LucaCappelletti94 +* Support `EXTRACT` syntax for snowflake (#1374) - Thanks @seve-martinez +* Support `ATTACH` / `DETACH PARTITION` for ClickHouse (#1362) - Thanks @git-hulk +* Support Dialect level precedence, update Postgres Dialect to match Postgres (#1360) - Thanks @samuelcolvin +* Support parsing empty map literal syntax for DuckDB and Generic dialects (#1361) - Thanks @goldmedal +* Support `SETTINGS` clause for ClickHouse table-valued functions (#1358) - Thanks @Jesse-Bakker +* Support `OPTIMIZE TABLE` statement for ClickHouse (#1359) - Thanks @git-hulk +* Support `ON CLUSTER` in `ALTER TABLE` for ClickHouse (#1342) - Thanks @git-hulk +* Support `GLOBAL` keyword before the join operator (#1353) - Thanks @git-hulk +* Support postgres String Constants with Unicode Escapes (#1355) - Thanks @lovasoa +* Support position with normal function call syntax for Snowflake (#1341) - Thanks @jmhain +* Support `TABLE` keyword in `DESC|DESCRIBE|EXPLAIN TABLE` statement (#1351) - Thanks @git-hulk + +### Changed +* Only require `DESCRIBE TABLE` for Snowflake and ClickHouse dialect (#1386) - Thanks @ alamb +* Rename (unreleased) `get_next_precedence_full` to `get_next_precedence_default` (#1378) - Thanks @samuelcolvin +* Use local GitHub Action to replace setup-rust-action (#1371) - Thanks @git-hulk +* Simplify arrow_cast tests (#1367) - Thanks @alamb +* Update version of GitHub Actions (#1363) - Thanks @git-hulk +* Make `Parser::maybe_parse` pub (#1364) - Thanks @Jesse-Bakker +* Improve comments on 1Dialect` (#1366) - Thanks @alamb + + +## [0.49.0] 2024-07-23 +As always, huge props to @iffyio @jmhain and @lovasoa for their help reviewing and merging PRs! + +We are in the process of moving sqlparser to governed as part of the Apache +DataFusion project: https://github.com/sqlparser-rs/sqlparser-rs/issues/1294 + +### Fixed +* Fix quoted identifier regression edge-case with "from" in SELECT (#1346) - Thanks @alexander-beedie +* Fix `AS` query clause should be after the create table options (#1339) - Thanks @git-hulk + +### Added + +* Support `MATERIALIZED`/`ALIAS`/`EPHERMERAL` default column options for ClickHouse (#1348) - Thanks @git-hulk +* Support `()` as the `GROUP BY` nothing (#1347) - Thanks @git-hulk +* Support Map literal syntax for DuckDB and Generic (#1344) - Thanks @goldmedal +* Support subquery expression in `SET` expressions (#1343) - Thanks @iffyio +* Support `WITH FILL` for ClickHouse (#1330) - Thanks @nickpresta +* Support `PARTITION BY` for PostgreSQL in `CREATE TABLE` statement (#1338) - Thanks @git-hulk +* Support of table function `WITH ORDINALITY` modifier for Postgres (#1337) - Thanks @git-hulk + + ## [0.48.0] 2024-07-09 Huge shout out to @iffyio @jmhain and @lovasoa for their help reviewing and merging PRs! diff --git a/Cargo.toml b/Cargo.toml index b0bee003e..2448b67ce 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "sqlparser" description = "Extensible SQL Lexer and Parser with support for ANSI SQL:2011" -version = "0.48.0" +version = "0.51.0" authors = ["Andy Grove "] homepage = "https://github.com/sqlparser-rs/sqlparser-rs" documentation = "https://docs.rs/sqlparser/" diff --git a/src/ast/data_type.rs b/src/ast/data_type.rs index e6477f56b..f3ebd16da 100644 --- a/src/ast/data_type.rs +++ b/src/ast/data_type.rs @@ -302,7 +302,7 @@ pub enum DataType { /// /// [hive]: https://docs.cloudera.com/cdw-runtime/cloud/impala-sql-reference/topics/impala-struct.html /// [bigquery]: https://cloud.google.com/bigquery/docs/reference/standard-sql/data-types#struct_type - Struct(Vec), + Struct(Vec, StructBracketKind), /// Union /// /// [duckdb]: https://duckdb.org/docs/sql/data_types/union.html @@ -319,6 +319,10 @@ pub enum DataType { /// [`SQLiteDialect`](crate::dialect::SQLiteDialect), from statements such /// as `CREATE TABLE t1 (a)`. Unspecified, + /// Trigger data type, returned by functions associated with triggers + /// + /// [postgresql]: https://www.postgresql.org/docs/current/plpgsql-trigger.html + Trigger, } impl fmt::Display for DataType { @@ -513,9 +517,16 @@ impl fmt::Display for DataType { } write!(f, ")") } - DataType::Struct(fields) => { + DataType::Struct(fields, bracket) => { if !fields.is_empty() { - write!(f, "STRUCT<{}>", display_comma_separated(fields)) + match bracket { + StructBracketKind::Parentheses => { + write!(f, "STRUCT({})", display_comma_separated(fields)) + } + StructBracketKind::AngleBrackets => { + write!(f, "STRUCT<{}>", display_comma_separated(fields)) + } + } } else { write!(f, "STRUCT") } @@ -543,6 +554,7 @@ impl fmt::Display for DataType { write!(f, "Nested({})", display_comma_separated(fields)) } DataType::Unspecified => Ok(()), + DataType::Trigger => write!(f, "TRIGGER"), } } } @@ -613,6 +625,17 @@ fn format_clickhouse_datetime_precision_and_timezone( Ok(()) } +/// Type of brackets used for `STRUCT` literals. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum StructBracketKind { + /// Example: `STRUCT(a INT, b STRING)` + Parentheses, + /// Example: `STRUCT` + AngleBrackets, +} + /// Timestamp and Time data types information about TimeZone formatting. /// /// This is more related to a display information than real differences between each variant. To diff --git a/src/ast/dcl.rs b/src/ast/dcl.rs index f90de34d4..1b0a77095 100644 --- a/src/ast/dcl.rs +++ b/src/ast/dcl.rs @@ -193,3 +193,30 @@ impl fmt::Display for AlterRoleOperation { } } } + +/// A `USE` (`Statement::Use`) operation +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum Use { + Catalog(ObjectName), // e.g. `USE CATALOG foo.bar` + Schema(ObjectName), // e.g. `USE SCHEMA foo.bar` + Database(ObjectName), // e.g. `USE DATABASE foo.bar` + Warehouse(ObjectName), // e.g. `USE WAREHOUSE foo.bar` + Object(ObjectName), // e.g. `USE foo.bar` + Default, // e.g. `USE DEFAULT` +} + +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::Object(name) => write!(f, "{}", name), + Use::Default => write!(f, "DEFAULT"), + } + } +} diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index 1ed3857d7..b5444b8da 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -26,7 +26,7 @@ use sqlparser_derive::{Visit, VisitMut}; use crate::ast::value::escape_single_quote_string; use crate::ast::{ display_comma_separated, display_separated, DataType, Expr, Ident, MySQLColumnPosition, - ObjectName, SequenceOptions, SqlOption, + ObjectName, OrderByExpr, ProjectionSelect, SequenceOptions, SqlOption, Value, }; use crate::tokenizer::Token; @@ -48,6 +48,42 @@ pub enum AlterTableOperation { /// MySQL `ALTER TABLE` only [FIRST | AFTER column_name] column_position: Option, }, + /// `ADD PROJECTION [IF NOT EXISTS] name ( SELECT [GROUP BY] [ORDER BY])` + /// + /// Note: this is a ClickHouse-specific operation. + /// Please refer to [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/projection#add-projection) + AddProjection { + if_not_exists: bool, + name: Ident, + select: ProjectionSelect, + }, + + /// `DROP PROJECTION [IF EXISTS] name` + /// + /// Note: this is a ClickHouse-specific operation. + /// Please refer to [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/projection#drop-projection) + DropProjection { if_exists: bool, name: Ident }, + + /// `MATERIALIZE PROJECTION [IF EXISTS] name [IN PARTITION partition_name]` + /// + /// Note: this is a ClickHouse-specific operation. + /// Please refer to [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/projection#materialize-projection) + MaterializeProjection { + if_exists: bool, + name: Ident, + partition: Option, + }, + + /// `CLEAR PROJECTION [IF EXISTS] name [IN PARTITION partition_name]` + /// + /// Note: this is a ClickHouse-specific operation. + /// Please refer to [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/projection#clear-projection) + ClearProjection { + if_exists: bool, + name: Ident, + partition: Option, + }, + /// `DISABLE ROW LEVEL SECURITY` /// /// Note: this is a PostgreSQL-specific operation. @@ -72,6 +108,35 @@ pub enum AlterTableOperation { if_exists: bool, cascade: bool, }, + /// `ATTACH PART|PARTITION ` + /// Note: this is a ClickHouse-specific operation, please refer to + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/pakrtition#attach-partitionpart) + AttachPartition { + // PART is not a short form of PARTITION, it's a separate keyword + // which represents a physical file on disk and partition is a logical entity. + partition: Partition, + }, + /// `DETACH PART|PARTITION ` + /// Note: this is a ClickHouse-specific operation, please refer to + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#detach-partitionpart) + DetachPartition { + // See `AttachPartition` for more details + partition: Partition, + }, + /// `FREEZE PARTITION ` + /// Note: this is a ClickHouse-specific operation, please refer to + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#freeze-partition) + FreezePartition { + partition: Partition, + with_name: Option, + }, + /// `UNFREEZE PARTITION ` + /// Note: this is a ClickHouse-specific operation, please refer to + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#unfreeze-partition) + UnfreezePartition { + partition: Partition, + with_name: Option, + }, /// `DROP PRIMARY KEY` /// /// Note: this is a MySQL-specific operation. @@ -226,6 +291,54 @@ impl fmt::Display for AlterTableOperation { Ok(()) } + AlterTableOperation::AddProjection { + if_not_exists, + name, + select: query, + } => { + write!(f, "ADD PROJECTION")?; + if *if_not_exists { + write!(f, " IF NOT EXISTS")?; + } + write!(f, " {} ({})", name, query) + } + AlterTableOperation::DropProjection { if_exists, name } => { + write!(f, "DROP PROJECTION")?; + if *if_exists { + write!(f, " IF EXISTS")?; + } + write!(f, " {}", name) + } + AlterTableOperation::MaterializeProjection { + if_exists, + name, + partition, + } => { + write!(f, "MATERIALIZE PROJECTION")?; + if *if_exists { + write!(f, " IF EXISTS")?; + } + write!(f, " {}", name)?; + if let Some(partition) = partition { + write!(f, " IN PARTITION {}", partition)?; + } + Ok(()) + } + AlterTableOperation::ClearProjection { + if_exists, + name, + partition, + } => { + write!(f, "CLEAR PROJECTION")?; + if *if_exists { + write!(f, " IF EXISTS")?; + } + write!(f, " {}", name)?; + if let Some(partition) = partition { + write!(f, " IN PARTITION {}", partition)?; + } + Ok(()) + } AlterTableOperation::AlterColumn { column_name, op } => { write!(f, "ALTER COLUMN {column_name} {op}") } @@ -272,6 +385,12 @@ impl fmt::Display for AlterTableOperation { column_name, if *cascade { " CASCADE" } else { "" } ), + AlterTableOperation::AttachPartition { partition } => { + write!(f, "ATTACH {partition}") + } + AlterTableOperation::DetachPartition { partition } => { + write!(f, "DETACH {partition}") + } AlterTableOperation::EnableAlwaysRule { name } => { write!(f, "ENABLE ALWAYS RULE {name}") } @@ -358,6 +477,26 @@ impl fmt::Display for AlterTableOperation { display_comma_separated(table_properties) ) } + AlterTableOperation::FreezePartition { + partition, + with_name, + } => { + write!(f, "FREEZE {partition}")?; + if let Some(name) = with_name { + write!(f, " WITH NAME {name}")?; + } + Ok(()) + } + AlterTableOperation::UnfreezePartition { + partition, + with_name, + } => { + write!(f, "UNFREEZE {partition}")?; + if let Some(name) = with_name { + write!(f, " WITH NAME {name}")?; + } + Ok(()) + } } } } @@ -923,6 +1062,18 @@ pub enum ColumnOption { NotNull, /// `DEFAULT ` Default(Expr), + + /// ClickHouse supports `MATERIALIZE`, `EPHEMERAL` and `ALIAS` expr to generate default values. + /// Syntax: `b INT MATERIALIZE (a + 1)` + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/create/table#default_values) + + /// `MATERIALIZE ` + Materialized(Expr), + /// `EPHEMERAL []` + Ephemeral(Option), + /// `ALIAS ` + Alias(Expr), + /// `{ PRIMARY KEY | UNIQUE } []` Unique { is_primary: bool, @@ -978,6 +1129,15 @@ impl fmt::Display for ColumnOption { Null => write!(f, "NULL"), NotNull => write!(f, "NOT NULL"), Default(expr) => write!(f, "DEFAULT {expr}"), + Materialized(expr) => write!(f, "MATERIALIZED {expr}"), + Ephemeral(expr) => { + if let Some(e) = expr { + write!(f, "EPHEMERAL {e}") + } else { + write!(f, "EPHEMERAL") + } + } + Alias(expr) => write!(f, "ALIAS {expr}"), Unique { is_primary, characteristics, @@ -1133,7 +1293,7 @@ fn display_option_spaced(option: &Option) -> impl fmt::Displ /// ` = [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY DEFERRED | INITIALLY IMMEDIATE ] [ ENFORCED | NOT ENFORCED ]` /// /// Used in UNIQUE and foreign key constraints. The individual settings may occur in any order. -#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[derive(Debug, Copy, Clone, PartialEq, PartialOrd, Default, Eq, Ord, Hash)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] pub struct ConstraintCharacteristics { @@ -1275,20 +1435,76 @@ impl fmt::Display for UserDefinedTypeCompositeAttributeDef { } } -/// PARTITION statement used in ALTER TABLE et al. such as in Hive SQL +/// PARTITION statement used in ALTER TABLE et al. such as in Hive and ClickHouse SQL. +/// For example, ClickHouse's OPTIMIZE TABLE supports syntax like PARTITION ID 'partition_id' and PARTITION expr. +/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/optimize) #[derive(Debug, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)] #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] #[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] -pub struct Partition { - pub partitions: Vec, +pub enum Partition { + Identifier(Ident), + Expr(Expr), + /// ClickHouse supports PART expr which represents physical partition in disk. + /// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/alter/partition#attach-partitionpart) + Part(Expr), + Partitions(Vec), } impl fmt::Display for Partition { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Partition::Identifier(id) => write!(f, "PARTITION ID {id}"), + Partition::Expr(expr) => write!(f, "PARTITION {expr}"), + Partition::Part(expr) => write!(f, "PART {expr}"), + Partition::Partitions(partitions) => { + write!(f, "PARTITION ({})", display_comma_separated(partitions)) + } + } + } +} + +/// DEDUPLICATE statement used in OPTIMIZE TABLE et al. such as in ClickHouse SQL +/// [ClickHouse](https://clickhouse.com/docs/en/sql-reference/statements/optimize) +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum Deduplicate { + All, + ByExpression(Expr), +} + +impl fmt::Display for Deduplicate { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match self { + Deduplicate::All => write!(f, "DEDUPLICATE"), + Deduplicate::ByExpression(expr) => write!(f, "DEDUPLICATE BY {expr}"), + } + } +} + +/// Hive supports `CLUSTERED BY` statement in `CREATE TABLE`. +/// Syntax: `CLUSTERED BY (col_name, ...) [SORTED BY (col_name [ASC|DESC], ...)] INTO num_buckets BUCKETS` +/// +/// [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable) +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct ClusteredBy { + pub columns: Vec, + pub sorted_by: Option>, + pub num_buckets: Value, +} + +impl fmt::Display for ClusteredBy { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { write!( f, - "PARTITION ({})", - display_comma_separated(&self.partitions) - ) + "CLUSTERED BY ({})", + display_comma_separated(&self.columns) + )?; + if let Some(ref sorted_by) = self.sorted_by { + write!(f, " SORTED BY ({})", display_comma_separated(sorted_by))?; + } + write!(f, " INTO {} BUCKETS", self.num_buckets) } } diff --git a/src/ast/dml.rs b/src/ast/dml.rs index b35b2b970..c0e58e21a 100644 --- a/src/ast/dml.rs +++ b/src/ast/dml.rs @@ -22,11 +22,11 @@ use sqlparser_derive::{Visit, VisitMut}; pub use super::ddl::{ColumnDef, TableConstraint}; use super::{ - display_comma_separated, display_separated, CommentDef, Expr, FileFormat, FromTable, - HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat, Ident, InsertAliases, - MysqlInsertPriority, ObjectName, OnCommit, OnInsert, OneOrManyWithParens, OrderByExpr, Query, - RowAccessPolicy, SelectItem, SqlOption, SqliteOnConflict, TableEngine, TableWithJoins, Tag, - WrappedCollection, + display_comma_separated, display_separated, ClusteredBy, CommentDef, Expr, FileFormat, + FromTable, HiveDistributionStyle, HiveFormat, HiveIOFormat, HiveRowFormat, Ident, + InsertAliases, MysqlInsertPriority, ObjectName, OnCommit, OnInsert, OneOrManyWithParens, + OrderByExpr, Query, RowAccessPolicy, SelectItem, SqlOption, SqliteOnConflict, TableEngine, + TableWithJoins, Tag, WrappedCollection, }; /// CREATE INDEX statement. @@ -45,6 +45,8 @@ pub struct CreateIndex { pub if_not_exists: bool, pub include: Vec, pub nulls_distinct: Option, + /// WITH clause: + pub with: Vec, pub predicate: Option, } @@ -83,6 +85,9 @@ impl Display for CreateIndex { write!(f, " NULLS NOT DISTINCT")?; } } + if !self.with.is_empty() { + write!(f, " WITH ({})", display_comma_separated(&self.with))?; + } if let Some(predicate) = &self.predicate { write!(f, " WHERE {predicate}")?; } @@ -126,7 +131,7 @@ pub struct CreateTable { pub on_commit: Option, /// ClickHouse "ON CLUSTER" clause: /// - pub on_cluster: Option, + pub on_cluster: Option, /// ClickHouse "PRIMARY KEY " clause. /// pub primary_key: Option>, @@ -140,6 +145,9 @@ pub struct CreateTable { /// BigQuery: Table clustering column list. /// pub cluster_by: Option>>, + /// Hive: Table clustering column list. + /// + pub clustered_by: Option, /// BigQuery: Table options list. /// pub options: Option>, @@ -206,11 +214,7 @@ impl Display for CreateTable { name = self.name, )?; if let Some(on_cluster) = &self.on_cluster { - write!( - f, - " ON CLUSTER {}", - on_cluster.replace('{', "'{").replace('}', "}'") - )?; + write!(f, " ON CLUSTER {}", on_cluster)?; } if !self.columns.is_empty() || !self.constraints.is_empty() { write!(f, " ({}", display_comma_separated(&self.columns))?; @@ -222,6 +226,13 @@ impl Display for CreateTable { // PostgreSQL allows `CREATE TABLE t ();`, but requires empty parens write!(f, " ()")?; } + + // Hive table comment should be after column definitions, please refer to: + // [Hive](https://cwiki.apache.org/confluence/display/Hive/LanguageManual+DDL#LanguageManualDDL-CreateTable) + if let Some(CommentDef::AfterColumnDefsWithoutEq(comment)) = &self.comment { + write!(f, " COMMENT '{comment}'")?; + } + // Only for SQLite if self.without_rowid { write!(f, " WITHOUT ROWID")?; @@ -240,19 +251,6 @@ impl Display for CreateTable { HiveDistributionStyle::PARTITIONED { columns } => { write!(f, " PARTITIONED BY ({})", display_comma_separated(columns))?; } - HiveDistributionStyle::CLUSTERED { - columns, - sorted_by, - num_buckets, - } => { - write!(f, " CLUSTERED BY ({})", display_comma_separated(columns))?; - if !sorted_by.is_empty() { - write!(f, " SORTED BY ({})", display_comma_separated(sorted_by))?; - } - if *num_buckets > 0 { - write!(f, " INTO {num_buckets} BUCKETS")?; - } - } HiveDistributionStyle::SKEWED { columns, on, @@ -271,6 +269,10 @@ impl Display for CreateTable { _ => (), } + if let Some(clustered_by) = &self.clustered_by { + write!(f, " {clustered_by}")?; + } + if let Some(HiveFormat { row_format, serde_properties, @@ -341,6 +343,8 @@ impl Display for CreateTable { CommentDef::WithoutEq(comment) => { write!(f, " COMMENT '{comment}'")?; } + // For CommentDef::AfterColumnDefsWithoutEq will be displayed after column definition + CommentDef::AfterColumnDefsWithoutEq(_) => (), } } @@ -418,9 +422,6 @@ impl Display for CreateTable { write!(f, " WITH TAG ({})", display_comma_separated(tag.as_slice()))?; } - if let Some(query) = &self.query { - write!(f, " AS {query}")?; - } if let Some(default_charset) = &self.default_charset { write!(f, " DEFAULT CHARSET={default_charset}")?; } @@ -440,6 +441,9 @@ impl Display for CreateTable { if self.strict { write!(f, " STRICT")?; } + if let Some(query) = &self.query { + write!(f, " AS {query}")?; + } Ok(()) } } diff --git a/src/ast/helpers/stmt_create_table.rs b/src/ast/helpers/stmt_create_table.rs index d862a36ae..82532b291 100644 --- a/src/ast/helpers/stmt_create_table.rs +++ b/src/ast/helpers/stmt_create_table.rs @@ -9,9 +9,9 @@ use sqlparser_derive::{Visit, VisitMut}; use super::super::dml::CreateTable; use crate::ast::{ - ColumnDef, CommentDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident, ObjectName, - OnCommit, OneOrManyWithParens, Query, RowAccessPolicy, SqlOption, Statement, TableConstraint, - TableEngine, Tag, WrappedCollection, + ClusteredBy, ColumnDef, CommentDef, Expr, FileFormat, HiveDistributionStyle, HiveFormat, Ident, + ObjectName, OnCommit, OneOrManyWithParens, Query, RowAccessPolicy, SqlOption, Statement, + TableConstraint, TableEngine, Tag, WrappedCollection, }; use crate::parser::ParserError; @@ -73,11 +73,12 @@ pub struct CreateTableBuilder { pub default_charset: Option, pub collation: Option, pub on_commit: Option, - pub on_cluster: Option, + pub on_cluster: Option, pub primary_key: Option>, pub order_by: Option>, pub partition_by: Option>, pub cluster_by: Option>>, + pub clustered_by: Option, pub options: Option>, pub strict: bool, pub copy_grants: bool, @@ -125,6 +126,7 @@ impl CreateTableBuilder { order_by: None, partition_by: None, cluster_by: None, + clustered_by: None, options: None, strict: false, copy_grants: false, @@ -261,7 +263,7 @@ impl CreateTableBuilder { self } - pub fn on_cluster(mut self, on_cluster: Option) -> Self { + pub fn on_cluster(mut self, on_cluster: Option) -> Self { self.on_cluster = on_cluster; self } @@ -286,6 +288,11 @@ impl CreateTableBuilder { self } + pub fn clustered_by(mut self, clustered_by: Option) -> Self { + self.clustered_by = clustered_by; + self + } + pub fn options(mut self, options: Option>) -> Self { self.options = options; self @@ -380,6 +387,7 @@ impl CreateTableBuilder { order_by: self.order_by, partition_by: self.partition_by, cluster_by: self.cluster_by, + clustered_by: self.clustered_by, options: self.options, strict: self.strict, copy_grants: self.copy_grants, @@ -434,6 +442,7 @@ impl TryFrom for CreateTableBuilder { order_by, partition_by, cluster_by, + clustered_by, options, strict, copy_grants, @@ -476,6 +485,7 @@ impl TryFrom for CreateTableBuilder { order_by, partition_by, cluster_by, + clustered_by, options, strict, copy_grants, @@ -496,9 +506,9 @@ impl TryFrom for CreateTableBuilder { } } -/// Helper return type when parsing configuration for a BigQuery `CREATE TABLE` statement. +/// Helper return type when parsing configuration for a `CREATE TABLE` statement. #[derive(Default)] -pub(crate) struct BigQueryTableConfiguration { +pub(crate) struct CreateTableConfiguration { pub partition_by: Option>, pub cluster_by: Option>>, pub options: Option>, diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 4b1685d83..fab919267 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -20,6 +20,7 @@ use alloc::{ }; use core::fmt::{self, Display}; +use core::ops::Deref; #[cfg(feature = "serde")] use serde::{Deserialize, Serialize}; @@ -28,30 +29,38 @@ use serde::{Deserialize, Serialize}; use sqlparser_derive::{Visit, VisitMut}; pub use self::data_type::{ - ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, TimezoneInfo, + ArrayElemTypeDef, CharLengthUnits, CharacterLength, DataType, ExactNumberInfo, + StructBracketKind, TimezoneInfo, }; -pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue}; +pub use self::dcl::{AlterRoleOperation, ResetConfig, RoleOption, SetConfigValue, Use}; pub use self::ddl::{ - AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ColumnDef, ColumnOption, - ColumnOptionDef, ConstraintCharacteristics, DeferrableInitial, GeneratedAs, - GeneratedExpressionMode, IndexOption, IndexType, KeyOrIndexDisplay, Owner, Partition, - ProcedureParam, ReferentialAction, TableConstraint, UserDefinedTypeCompositeAttributeDef, - UserDefinedTypeRepresentation, ViewColumnDef, + AlterColumnOperation, AlterIndexOperation, AlterTableOperation, ClusteredBy, ColumnDef, + ColumnOption, ColumnOptionDef, ConstraintCharacteristics, Deduplicate, DeferrableInitial, + GeneratedAs, GeneratedExpressionMode, IndexOption, IndexType, KeyOrIndexDisplay, Owner, + Partition, ProcedureParam, ReferentialAction, TableConstraint, + UserDefinedTypeCompositeAttributeDef, UserDefinedTypeRepresentation, ViewColumnDef, }; pub use self::dml::{CreateIndex, CreateTable, Delete, Insert}; pub use self::operator::{BinaryOperator, UnaryOperator}; pub use self::query::{ AfterMatchSkip, ConnectBy, Cte, CteAsMaterialized, Distinct, EmptyMatchesMode, ExceptSelectItem, ExcludeSelectItem, ExprWithAlias, Fetch, ForClause, ForJson, ForXml, - FormatClause, GroupByExpr, GroupByWithModifier, IdentWithAlias, IlikeSelectItem, Join, - JoinConstraint, JoinOperator, JsonTableColumn, JsonTableColumnErrorHandling, LateralView, - LockClause, LockType, MatchRecognizePattern, MatchRecognizeSymbol, Measure, - NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, OffsetRows, OrderByExpr, - PivotValueSource, Query, RenameSelectItem, RepetitionQuantifier, ReplaceSelectElement, - ReplaceSelectItem, RowsPerMatch, Select, SelectInto, SelectItem, SetExpr, SetOperator, - SetQuantifier, Setting, SymbolDefinition, Table, TableAlias, TableFactor, TableVersion, - TableWithJoins, Top, TopQuantity, ValueTableMode, Values, WildcardAdditionalOptions, With, + FormatClause, GroupByExpr, GroupByWithModifier, IdentWithAlias, IlikeSelectItem, Interpolate, + InterpolateExpr, Join, JoinConstraint, JoinOperator, JsonTableColumn, + JsonTableColumnErrorHandling, LateralView, LockClause, LockType, MatchRecognizePattern, + MatchRecognizeSymbol, Measure, NamedWindowDefinition, NamedWindowExpr, NonBlock, Offset, + OffsetRows, OrderBy, OrderByExpr, PivotValueSource, ProjectionSelect, Query, RenameSelectItem, + RepetitionQuantifier, ReplaceSelectElement, ReplaceSelectItem, RowsPerMatch, Select, + SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, Setting, SymbolDefinition, Table, + TableAlias, TableFactor, TableFunctionArgs, TableVersion, TableWithJoins, Top, TopQuantity, + ValueTableMode, Values, WildcardAdditionalOptions, With, WithFill, }; + +pub use self::trigger::{ + TriggerEvent, TriggerExecBody, TriggerExecBodyType, TriggerObject, TriggerPeriod, + TriggerReferencing, TriggerReferencingType, +}; + pub use self::value::{ escape_double_quote_string, escape_quoted_string, DateTimeField, DollarQuotedString, TrimWhereField, Value, @@ -70,6 +79,7 @@ mod dml; pub mod helpers; mod operator; mod query; +mod trigger; mod value; #[cfg(feature = "visitor")] @@ -328,6 +338,37 @@ impl fmt::Display for DictionaryField { } } +/// Represents a Map expression. +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct Map { + pub entries: Vec, +} + +impl Display for Map { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "MAP {{{}}}", display_comma_separated(&self.entries)) + } +} + +/// A map field within a map. +/// +/// [duckdb]: https://duckdb.org/docs/sql/data_types/map.html#creating-maps +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub struct MapEntry { + pub key: Box, + pub value: Box, +} + +impl fmt::Display for MapEntry { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}: {}", self.key, self.value) + } +} + /// Options for `CAST` / `TRY_CAST` /// BigQuery: #[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] @@ -445,6 +486,40 @@ pub enum CastKind { DoubleColon, } +/// `EXTRACT` syntax variants. +/// +/// In Snowflake dialect, the `EXTRACT` expression can support either the `from` syntax +/// or the comma syntax. +/// +/// See +#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))] +pub enum ExtractSyntax { + /// `EXTRACT( FROM )` + From, + /// `EXTRACT( , )` + Comma, +} + +/// The syntax used in a CEIL or FLOOR expression. +/// +/// The `CEIL/FLOOR( TO