Introduce Oracle dialect (#2113)
diff --git a/examples/cli.rs b/examples/cli.rs
index 08a40a6..3c4299b 100644
--- a/examples/cli.rs
+++ b/examples/cli.rs
@@ -58,6 +58,7 @@
"--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/dialect/mod.rs b/src/dialect/mod.rs
index ef4e1cd..83c6da4 100644
--- a/src/dialect/mod.rs
+++ b/src/dialect/mod.rs
@@ -24,6 +24,7 @@
mod hive;
mod mssql;
mod mysql;
+mod oracle;
mod postgresql;
mod redshift;
mod snowflake;
@@ -45,6 +46,7 @@
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 @@
"ansi" => 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 0000000..0d6aee5
--- /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<char> {
+ 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 834d349..827df1c 100644
--- a/src/keywords.rs
+++ b/src/keywords.rs
@@ -220,6 +220,7 @@
COMMITTED,
COMMUTATOR,
COMPATIBLE,
+ COMPRESS,
COMPRESSION,
COMPUPDATE,
COMPUTE,
@@ -464,6 +465,7 @@
IAM_ROLE,
ICEBERG,
ID,
+ IDENTIFIED,
IDENTITY,
IDENTITY_INSERT,
IF,
@@ -567,6 +569,7 @@
LOG,
LOGIN,
LOGS,
+ LONG,
LONGBLOB,
LONGTEXT,
LOWCARDINALITY,
@@ -652,6 +655,7 @@
NFKD,
NO,
NOBYPASSRLS,
+ NOCOMPRESS,
NOCREATEDB,
NOCREATEROLE,
NOINHERIT,
@@ -675,6 +679,7 @@
NULLABLE,
NULLIF,
NULLS,
+ NUMBER,
NUMERIC,
NVARCHAR,
OBJECT,
@@ -741,6 +746,7 @@
PAST,
PATH,
PATTERN,
+ PCTFREE,
PER,
PERCENT,
PERCENTILE_CONT,
@@ -913,6 +919,7 @@
SIGNED,
SIMILAR,
SIMPLE,
+ SIZE,
SKIP,
SLOW,
SMALLINT,
@@ -974,6 +981,7 @@
SWAP,
SYMMETRIC,
SYNC,
+ SYNONYM,
SYSTEM,
SYSTEM_TIME,
SYSTEM_USER,
@@ -1085,6 +1093,7 @@
VARBINARY,
VARBIT,
VARCHAR,
+ VARCHAR2,
VARIABLE,
VARIABLES,
VARYING,
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 13c3c5b..b2fa3b1 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -12247,7 +12247,7 @@
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 73d2931..9ba5960 100644
--- a/src/test_utils.rs
+++ b/src/test_utils.rs
@@ -291,6 +291,7 @@
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 91952b8..ccad67e 100644
--- a/tests/sqlparser_common.rs
+++ b/tests/sqlparser_common.rs
@@ -34,8 +34,8 @@
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_without_from_error() {
let sql = "DELETE \"table\" WHERE 1";
- let dialects = all_dialects_except(|d| d.is::<BigQueryDialect>() || d.is::<GenericDialect>());
+ let dialects = all_dialects_except(|d| {
+ d.is::<BigQueryDialect>() || d.is::<OracleDialect>() || d.is::<GenericDialect>()
+ });
let res = dialects.parse_sql_statements(sql);
assert_eq!(
ParserError::ParserError("Expected: FROM, found: WHERE".to_string()),
@@ -723,7 +725,9 @@
#[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::<BigQueryDialect>() || d.is::<GenericDialect>());
+ let dialects = all_dialects_except(|d| {
+ d.is::<BigQueryDialect>() || d.is::<OracleDialect>() || d.is::<GenericDialect>()
+ });
match dialects.verified_stmt(sql) {
Statement::Delete(Delete {
tables,
@@ -12943,7 +12947,7 @@
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, ..