diff --git a/Cargo.toml b/Cargo.toml index ed94bbbd..177ab3db 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -18,7 +18,7 @@ [package] name = "sqlparser" description = "Extensible SQL Lexer and Parser with support for ANSI SQL:2011" -version = "0.59.0" +version = "0.60.0" authors = ["Apache DataFusion "] homepage = "https://github.com/apache/datafusion-sqlparser-rs" documentation = "https://docs.rs/sqlparser/" diff --git a/changelog/0.60.0.md b/changelog/0.60.0.md new file mode 100644 index 00000000..c3297bda --- /dev/null +++ b/changelog/0.60.0.md @@ -0,0 +1,98 @@ + + +# sqlparser-rs 0.60.0 Changelog + +This release consists of 37 commits from 20 contributors. See credits at the end of this changelog for more information. + +**Implemented enhancements:** + +- feat: Add RESET to the base dialect #2078 [#2079](https://github.com/apache/datafusion-sqlparser-rs/pull/2079) (watford-ep) +- feat: Add support for SET SESSION AUTHORIZATION #2086 [#2087](https://github.com/apache/datafusion-sqlparser-rs/pull/2087) (watford-ep) + +**Fixed bugs:** + +- fix: parse error on unnamed arg with default syntax [#2091](https://github.com/apache/datafusion-sqlparser-rs/pull/2091) (r1b) + +**Other:** + +- Add support for INVISIBLE columns in MySQL [#2033](https://github.com/apache/datafusion-sqlparser-rs/pull/2033) (altmannmarcelo) +- Link to actual change logs in CHANGELOG.md [#2040](https://github.com/apache/datafusion-sqlparser-rs/pull/2040) (lovasoa) +- Snowflake: ALTER USER and KeyValueOptions Refactoring [#2035](https://github.com/apache/datafusion-sqlparser-rs/pull/2035) (yoavcloud) +- Correctly tokenize nested comments in Databricks, ClickHouse, and ANSI [#2044](https://github.com/apache/datafusion-sqlparser-rs/pull/2044) (jmhain) +- MySQL: `CREATE INDEX`: allow `USING` clause before `ON` [#2029](https://github.com/apache/datafusion-sqlparser-rs/pull/2029) (MohamedAbdeen21) +- [databricks] update dialect to support grouping by with modifier [#2047](https://github.com/apache/datafusion-sqlparser-rs/pull/2047) (n-young) +- Moved constraint variant outside of `TableConstraint` enum [#2054](https://github.com/apache/datafusion-sqlparser-rs/pull/2054) (LucaCappelletti94) +- Add support for procedure parameter default values [#2041](https://github.com/apache/datafusion-sqlparser-rs/pull/2041) (aharpervc) +- Support updating PRs using github UI [#2052](https://github.com/apache/datafusion-sqlparser-rs/pull/2052) (blaginin) +- Increase version of sqlparser_derive from 0.3.0 to 0.4.0 [#2060](https://github.com/apache/datafusion-sqlparser-rs/pull/2060) (jjbayer) +- Moving several struct variants out of `Statement` enum to allow for trait impls for specific sub-variants [#2057](https://github.com/apache/datafusion-sqlparser-rs/pull/2057) (LucaCappelletti94) +- Added support for SQLite triggers [#2037](https://github.com/apache/datafusion-sqlparser-rs/pull/2037) (LucaCappelletti94) +- Added support for MATCH syntax and unified column option ForeignKey [#2062](https://github.com/apache/datafusion-sqlparser-rs/pull/2062) (LucaCappelletti94) +- chore: add stack overflow warning for Visitor and VisitorMut [#2068](https://github.com/apache/datafusion-sqlparser-rs/pull/2068) (niebayes) +- Reused `CheckConstraint` in `ColumnOption` [#2063](https://github.com/apache/datafusion-sqlparser-rs/pull/2063) (LucaCappelletti94) +- Refactored `ColumnOption::Unique` to reuse `UniqueConstraint` and `PrimaryKeyConstraint` [#2064](https://github.com/apache/datafusion-sqlparser-rs/pull/2064) (LucaCappelletti94) +- Redshift: more copy options [#2072](https://github.com/apache/datafusion-sqlparser-rs/pull/2072) (yoavcloud) +- SQLite: make period optional for CREATE TRIGGER [#2071](https://github.com/apache/datafusion-sqlparser-rs/pull/2071) (takluyver) +- Added TIMESTAMP_NTZ type support with precision to Snowflake dialect [#2080](https://github.com/apache/datafusion-sqlparser-rs/pull/2080) (romanoff) +- Make `BitwiseNot` ("~") available for all dialects, not just PostgreSQL [#2081](https://github.com/apache/datafusion-sqlparser-rs/pull/2081) (alexander-beedie) +- Add snowflake dynamic table parsing [#2083](https://github.com/apache/datafusion-sqlparser-rs/pull/2083) (romanoff) +- Add support for `INSERT INTO VALUE` [#2085](https://github.com/apache/datafusion-sqlparser-rs/pull/2085) (etgarperets) +- Complete PostgreSQL `CREATE TYPE` Support [#2094](https://github.com/apache/datafusion-sqlparser-rs/pull/2094) (LucaCappelletti94) +- Include DML keyword in statement span [#2090](https://github.com/apache/datafusion-sqlparser-rs/pull/2090) (xitep) +- Add PostgreSQL Operator DDL Support [#2096](https://github.com/apache/datafusion-sqlparser-rs/pull/2096) (LucaCappelletti94) +- impl `Spanned` for MERGE statements [#2100](https://github.com/apache/datafusion-sqlparser-rs/pull/2100) (xitep) +- Preserve optional `AS` keyword in aliases [#2103](https://github.com/apache/datafusion-sqlparser-rs/pull/2103) (xitep) +- Added support for `DROP OPERATOR` syntax [#2102](https://github.com/apache/datafusion-sqlparser-rs/pull/2102) (LucaCappelletti94) +- Only set `hive_formats` on `CreateTable` if formats are present [#2105](https://github.com/apache/datafusion-sqlparser-rs/pull/2105) (mvzink) +- Support PostgreSQL C Functions with Multiple AS Parameters [#2095](https://github.com/apache/datafusion-sqlparser-rs/pull/2095) (LucaCappelletti94) +- Added support for `DROP OPERATOR FAMILY` [#2106](https://github.com/apache/datafusion-sqlparser-rs/pull/2106) (LucaCappelletti94) +- Update criterion requirement from 0.7 to 0.8 in /sqlparser_bench [#2111](https://github.com/apache/datafusion-sqlparser-rs/pull/2111) (dependabot[bot]) +- Added support for `DROP OPERATOR CLASS` syntax [#2109](https://github.com/apache/datafusion-sqlparser-rs/pull/2109) (LucaCappelletti94) +- Introduce Oracle dialect [#2113](https://github.com/apache/datafusion-sqlparser-rs/pull/2113) (xitep) + +## Credits + +Thank you to everyone who contributed to this release. Here is a breakdown of commits (PRs merged) per contributor. + +``` + 12 Luca Cappelletti + 4 xitep + 2 Andriy Romanov + 2 Christopher Watford + 2 Yoav Cohen + 1 Alexander Beedie + 1 Andrew Harper + 1 Dmitrii Blaginin + 1 Joey Hain + 1 Joris Bayer + 1 Marcelo Altmann + 1 Michael Victor Zink + 1 Mohamed Abdeen + 1 Ophir LOJKINE + 1 Thomas Kluyver + 1 dependabot[bot] + 1 etgarperets + 1 nick young + 1 niebayes + 1 r1b +``` + +Thank you also to everyone who contributed in other ways such as filing issues, reviewing PRs, and providing feedback on this release. + diff --git a/dev/release/verify-release-candidate.sh b/dev/release/verify-release-candidate.sh index 9ff7e17b..4e97c6e2 100755 --- a/dev/release/verify-release-candidate.sh +++ b/dev/release/verify-release-candidate.sh @@ -124,13 +124,15 @@ test_source_distribution() { cargo build cargo test --all-features - if ( find -iname 'Cargo.toml' | xargs grep SNAPSHOT ); then + if ( find . -iname 'Cargo.toml' | xargs grep SNAPSHOT ); then echo "Cargo.toml version should not contain SNAPSHOT for releases" exit 1 fi + # Can't test using dry-run because sqlparser depends on sqlparser_derive + # see https://github.com/crate-ci/cargo-release/issues/691#issuecomment-2059866021 # Check that publish works - cargo publish --dry-run + # cargo publish --dry-run } TEST_SUCCESS=no diff --git a/examples/cli.rs b/examples/cli.rs index 08a40a6d..3c4299b2 100644 --- a/examples/cli.rs +++ b/examples/cli.rs @@ -58,6 +58,7 @@ $ cargo run --example cli - [--dialectname] "--clickhouse" => Box::new(ClickHouseDialect {}), "--duckdb" => Box::new(DuckDbDialect {}), "--sqlite" => Box::new(SQLiteDialect {}), + "--oracle" => Box::new(OracleDialect {}), "--generic" | "" => Box::new(GenericDialect {}), s => panic!("Unexpected parameter: {s}"), }; diff --git a/src/ast/ddl.rs b/src/ast/ddl.rs index bbca694b..3516c64a 100644 --- a/src/ast/ddl.rs +++ b/src/ast/ddl.rs @@ -2900,7 +2900,9 @@ impl fmt::Display for CreateTable { if let Some(file_format) = self.file_format { write!(f, " STORED AS {file_format}")?; } - write!(f, " LOCATION '{}'", self.location.as_ref().unwrap())?; + if let Some(location) = &self.location { + write!(f, " LOCATION '{location}'")?; + } } match &self.table_options { diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index ef4e1cdd..83c6da48 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -24,6 +24,7 @@ mod generic; mod hive; mod mssql; mod mysql; +mod oracle; mod postgresql; mod redshift; mod snowflake; @@ -45,6 +46,7 @@ pub use self::generic::GenericDialect; pub use self::hive::HiveDialect; pub use self::mssql::MsSqlDialect; pub use self::mysql::MySqlDialect; +pub use self::oracle::OracleDialect; pub use self::postgresql::PostgreSqlDialect; pub use self::redshift::RedshiftSqlDialect; pub use self::snowflake::SnowflakeDialect; @@ -1260,6 +1262,7 @@ pub fn dialect_from_str(dialect_name: impl AsRef) -> Option Some(Box::new(AnsiDialect {})), "duckdb" => Some(Box::new(DuckDbDialect {})), "databricks" => Some(Box::new(DatabricksDialect {})), + "oracle" => Some(Box::new(OracleDialect {})), _ => None, } } diff --git a/src/dialect/oracle.rs b/src/dialect/oracle.rs new file mode 100644 index 00000000..0d6aee5e --- /dev/null +++ b/src/dialect/oracle.rs @@ -0,0 +1,81 @@ +// Licensed to the Apache Software Foundation (ASF) under one +// or more contributor license agreements. See the NOTICE file +// distributed with this work for additional information +// regarding copyright ownership. The ASF licenses this file +// to you under the Apache License, Version 2.0 (the +// "License"); you may not use this file except in compliance +// with the License. You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +use super::Dialect; + +/// A [`Dialect`] for [Oracle Databases](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/index.html) +#[derive(Debug)] +pub struct OracleDialect; + +impl Dialect for OracleDialect { + // ~ appears not to be called anywhere + fn identifier_quote_style(&self, _identifier: &str) -> Option { + Some('"') + } + + fn is_delimited_identifier_start(&self, ch: char) -> bool { + ch == '"' + } + + fn is_identifier_start(&self, ch: char) -> bool { + ch.is_alphabetic() + } + + fn is_identifier_part(&self, ch: char) -> bool { + ch.is_alphanumeric() || ch == '_' || ch == '$' || ch == '#' || ch == '@' + } + + fn supports_outer_join_operator(&self) -> bool { + true + } + + fn supports_connect_by(&self) -> bool { + true + } + + fn supports_execute_immediate(&self) -> bool { + true + } + + fn supports_match_recognize(&self) -> bool { + true + } + + fn supports_window_function_null_treatment_arg(&self) -> bool { + true + } + + fn supports_boolean_literals(&self) -> bool { + false + } + + fn supports_comment_on(&self) -> bool { + true + } + + fn supports_create_table_select(&self) -> bool { + true + } + + fn supports_set_stmt_without_operator(&self) -> bool { + true + } + + fn supports_group_by_expr(&self) -> bool { + true + } +} diff --git a/src/keywords.rs b/src/keywords.rs index f98e4f21..f06842ec 100644 --- a/src/keywords.rs +++ b/src/keywords.rs @@ -229,6 +229,7 @@ define_keywords!( COMMITTED, COMMUTATOR, COMPATIBLE, + COMPRESS, COMPRESSION, COMPUPDATE, COMPUTE, @@ -473,6 +474,7 @@ define_keywords!( IAM_ROLE, ICEBERG, ID, + IDENTIFIED, IDENTITY, IDENTITY_INSERT, IF, @@ -576,6 +578,7 @@ define_keywords!( LOG, LOGIN, LOGS, + LONG, LONGBLOB, LONGTEXT, LOWCARDINALITY, @@ -661,6 +664,7 @@ define_keywords!( NFKD, NO, NOBYPASSRLS, + NOCOMPRESS, NOCREATEDB, NOCREATEROLE, NOINHERIT, @@ -684,6 +688,7 @@ define_keywords!( NULLABLE, NULLIF, NULLS, + NUMBER, NUMERIC, NVARCHAR, OBJECT, @@ -750,6 +755,7 @@ define_keywords!( PAST, PATH, PATTERN, + PCTFREE, PER, PERCENT, PERCENTILE_CONT, @@ -922,6 +928,7 @@ define_keywords!( SIGNED, SIMILAR, SIMPLE, + SIZE, SKIP, SLOW, SMALLINT, @@ -983,6 +990,7 @@ define_keywords!( SWAP, SYMMETRIC, SYNC, + SYNONYM, SYSTEM, SYSTEM_TIME, SYSTEM_USER, @@ -1094,6 +1102,7 @@ define_keywords!( VARBINARY, VARBIT, VARCHAR, + VARCHAR2, VARIABLE, VARIABLES, VARYING, diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 26106cd4..ce83adaf 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -12400,7 +12400,7 @@ impl<'a> Parser<'a> { let (tables, with_from_keyword) = if !self.parse_keyword(Keyword::FROM) { // `FROM` keyword is optional in BigQuery SQL. // https://cloud.google.com/bigquery/docs/reference/standard-sql/dml-syntax#delete_statement - if dialect_of!(self is BigQueryDialect | GenericDialect) { + if dialect_of!(self is BigQueryDialect | OracleDialect | GenericDialect) { (vec![], false) } else { let tables = self.parse_comma_separated(|p| p.parse_object_name(false))?; diff --git a/src/test_utils.rs b/src/test_utils.rs index 73d29312..9ba5960e 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -291,6 +291,7 @@ pub fn all_dialects() -> TestedDialects { Box::new(DuckDbDialect {}), Box::new(DatabricksDialect {}), Box::new(ClickHouseDialect {}), + Box::new(OracleDialect {}), ]) } diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 91952b8c..ccad67e3 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -34,8 +34,8 @@ use sqlparser::ast::TableFactor::{Pivot, Unpivot}; use sqlparser::ast::*; use sqlparser::dialect::{ AnsiDialect, BigQueryDialect, ClickHouseDialect, DatabricksDialect, Dialect, DuckDbDialect, - GenericDialect, HiveDialect, MsSqlDialect, MySqlDialect, PostgreSqlDialect, RedshiftSqlDialect, - SQLiteDialect, SnowflakeDialect, + GenericDialect, HiveDialect, MsSqlDialect, MySqlDialect, OracleDialect, PostgreSqlDialect, + RedshiftSqlDialect, SQLiteDialect, SnowflakeDialect, }; use sqlparser::keywords::{Keyword, ALL_KEYWORDS}; use sqlparser::parser::{Parser, ParserError, ParserOptions}; @@ -712,7 +712,9 @@ fn parse_delete_statement() { fn parse_delete_without_from_error() { let sql = "DELETE \"table\" WHERE 1"; - let dialects = all_dialects_except(|d| d.is::() || d.is::()); + let dialects = all_dialects_except(|d| { + d.is::() || d.is::() || d.is::() + }); let res = dialects.parse_sql_statements(sql); assert_eq!( ParserError::ParserError("Expected: FROM, found: WHERE".to_string()), @@ -723,7 +725,9 @@ fn parse_delete_without_from_error() { #[test] fn parse_delete_statement_for_multi_tables() { let sql = "DELETE schema1.table1, schema2.table2 FROM schema1.table1 JOIN schema2.table2 ON schema2.table2.col1 = schema1.table1.col1 WHERE schema2.table2.col2 = 1"; - let dialects = all_dialects_except(|d| d.is::() || d.is::()); + let dialects = all_dialects_except(|d| { + d.is::() || d.is::() || d.is::() + }); match dialects.verified_stmt(sql) { Statement::Delete(Delete { tables, @@ -12943,7 +12947,7 @@ fn test_match_recognize_patterns() { fn check(pattern: &str, expect: MatchRecognizePattern) { let select = all_dialects_where(|d| d.supports_match_recognize()).verified_only_select(&format!( - "SELECT * FROM my_table MATCH_RECOGNIZE(PATTERN ({pattern}) DEFINE DUMMY AS true)" // "select * from my_table match_recognize (" + "SELECT * FROM my_table MATCH_RECOGNIZE(PATTERN ({pattern}) DEFINE DUMMY AS 1 = 1)" // "select * from my_table match_recognize (" )); let TableFactor::MatchRecognize { pattern: actual, .. diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index 386bab7f..1b094851 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs @@ -34,10 +34,12 @@ fn parse_table_create() { let sql = r#"CREATE TABLE IF NOT EXISTS db.table (a BIGINT, b STRING, c TIMESTAMP) PARTITIONED BY (d STRING, e TIMESTAMP) STORED AS ORC LOCATION 's3://...' TBLPROPERTIES ("prop" = "2", "asdf" = '1234', 'asdf' = "1234", "asdf" = 2)"#; let iof = r#"CREATE TABLE IF NOT EXISTS db.table (a BIGINT, b STRING, c TIMESTAMP) PARTITIONED BY (d STRING, e TIMESTAMP) STORED AS INPUTFORMAT 'org.apache.hadoop.hive.ql.io.orc.OrcInputFormat' OUTPUTFORMAT 'org.apache.hadoop.hive.ql.io.orc.OrcOutputFormat' LOCATION 's3://...'"#; let serdeproperties = r#"CREATE EXTERNAL TABLE IF NOT EXISTS db.table (a STRING, b STRING, c STRING) PARTITIONED BY (d STRING, e STRING) ROW FORMAT SERDE 'org.apache.hadoop.hive.serde.config' WITH SERDEPROPERTIES ('prop_a' = 'a', 'prop_b' = 'b') STORED AS TEXTFILE LOCATION 's3://...' TBLPROPERTIES ('prop_c' = 'c')"#; + let externaltable = r#"CREATE EXTERNAL TABLE t (c INT)"#; hive().verified_stmt(sql); hive().verified_stmt(iof); hive().verified_stmt(serdeproperties); + hive().verified_stmt(externaltable); } #[test]