Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions examples/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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}"),
};
Expand Down
9 changes: 9 additions & 0 deletions src/ast/query.rs
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,15 @@ impl SetExpr {
None
}
}

/// If this `SetExpr` is a `SELECT`, returns a mutable [`Select`].
pub fn as_select_mut(&mut self) -> Option<&mut Select> {
if let Self::Select(select) = self {
Some(&mut **select)
} else {
None
}
}
}

impl fmt::Display for SetExpr {
Expand Down
10 changes: 10 additions & 0 deletions src/ast/value.rs
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,12 @@ pub enum Value {
TripleDoubleQuotedRawStringLiteral(String),
/// N'string value'
NationalStringLiteral(String),
/// Quote delimited literal. Examples `Q'{ab'c}'`, `Q'|ab'c|'`, `Q'|ab|c|'`
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Literals.html)
QuoteDelimitedStringLiteral(char, String, char),
/// "National" quote delimited literal. Examples `Q'{ab'c}'`, `Q'|ab'c|'`, `Q'|ab|c|'`
/// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Literals.html)
NationalQuoteDelimitedStringLiteral(char, String, char),
/// X'hex value'
HexStringLiteral(String),

Expand Down Expand Up @@ -205,6 +211,8 @@ impl Value {
| Value::EscapedStringLiteral(s)
| Value::UnicodeStringLiteral(s)
| Value::NationalStringLiteral(s)
| Value::QuoteDelimitedStringLiteral(_, s, _)
| Value::NationalQuoteDelimitedStringLiteral(_, s, _)
| Value::HexStringLiteral(s) => Some(s),
Value::DollarQuotedString(s) => Some(s.value),
_ => None,
Expand Down Expand Up @@ -242,6 +250,8 @@ impl fmt::Display for Value {
Value::EscapedStringLiteral(v) => write!(f, "E'{}'", escape_escaped_string(v)),
Value::UnicodeStringLiteral(v) => write!(f, "U&'{}'", escape_unicode_string(v)),
Value::NationalStringLiteral(v) => write!(f, "N'{v}'"),
Value::QuoteDelimitedStringLiteral(q1, s, q2) => write!(f, "Q'{q1}{s}{q2}'"),
Value::NationalQuoteDelimitedStringLiteral(q1, s, q2) => write!(f, "NQ'{q1}{s}{q2}'"),
Value::HexStringLiteral(v) => write!(f, "X'{v}'"),
Value::Boolean(v) => write!(f, "{v}"),
Value::SingleQuotedByteStringLiteral(v) => write!(f, "B'{v}'"),
Expand Down
83 changes: 53 additions & 30 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ mod generic;
mod hive;
mod mssql;
mod mysql;
mod oracle;
mod postgresql;
mod redshift;
mod snowflake;
Expand All @@ -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;
Expand Down Expand Up @@ -84,6 +86,26 @@ macro_rules! dialect_is {
}
}

const DEFAULT_PREC_VALUE_PERIOD: u8 = 100;
const DEFAULT_PREC_VALUE_DOUBLE_COLON: u8 = 50;
const DEFAULT_PREC_VALUE_AT_TZ: u8 = 41;
const DEFAULT_PREC_VALUE_MUL_DIV_MOD_OP: u8 = 40;
const DEFAULT_PREC_VALUE_PLUS_MINUS: u8 = 30;
const DEFAULT_PREC_VALUE_XOR: u8 = 24;
const DEFAULT_PREC_VALUE_AMPERSAND: u8 = 23;
const DEFAULT_PREC_VALUE_CARET: u8 = 22;
const DEFAULT_PREC_VALUE_PIPE: u8 = 21;
const DEFAULT_PREC_VALUE_BETWEEN: u8 = 20;
const DEFAULT_PREC_VALUE_EQ: u8 = 20;
const DEFAULT_PREC_VALUE_LIKE: u8 = 19;
const DEFAULT_PREC_VALUE_IS: u8 = 17;
const DEFAULT_PREC_VALUE_PG_OTHER: u8 = 16;
const DEFAULT_PREC_VALUE_UNARY_NOT: u8 = 15;
const DEFAULT_PREC_VALUE_AND: u8 = 10;
const DEFAULT_PREC_VALUE_OR: u8 = 5;

const DEFAULT_PREC_VALUE_UNKNOWN: u8 = 0;

/// Encapsulates the differences between SQL implementations.
///
/// # SQL Dialects
Expand Down Expand Up @@ -773,6 +795,36 @@ pub trait Dialect: Debug + Any {
}
}

/// Decide the lexical Precedence of operators.
///
/// Uses (APPROXIMATELY) <https://www.postgresql.org/docs/7.0/operators.htm#AEN2026> as a reference
fn prec_value(&self, prec: Precedence) -> u8 {
match prec {
Precedence::Period => DEFAULT_PREC_VALUE_PERIOD,
Precedence::DoubleColon => DEFAULT_PREC_VALUE_DOUBLE_COLON,
Precedence::AtTz => DEFAULT_PREC_VALUE_AT_TZ,
Precedence::MulDivModOp => DEFAULT_PREC_VALUE_MUL_DIV_MOD_OP,
Precedence::PlusMinus => DEFAULT_PREC_VALUE_PLUS_MINUS,
Precedence::Xor => DEFAULT_PREC_VALUE_XOR,
Precedence::Ampersand => DEFAULT_PREC_VALUE_AMPERSAND,
Precedence::Caret => DEFAULT_PREC_VALUE_CARET,
Precedence::Pipe => DEFAULT_PREC_VALUE_PIPE,
Precedence::Between => DEFAULT_PREC_VALUE_BETWEEN,
Precedence::Eq => DEFAULT_PREC_VALUE_EQ,
Precedence::Like => DEFAULT_PREC_VALUE_LIKE,
Precedence::Is => DEFAULT_PREC_VALUE_IS,
Precedence::PgOther => DEFAULT_PREC_VALUE_PG_OTHER,
Precedence::UnaryNot => DEFAULT_PREC_VALUE_UNARY_NOT,
Precedence::And => DEFAULT_PREC_VALUE_AND,
Precedence::Or => DEFAULT_PREC_VALUE_OR,
}
}

/// Returns the precedence when the precedence is otherwise unknown
fn prec_unknown(&self) -> u8 {
DEFAULT_PREC_VALUE_UNKNOWN
}

/// Dialect-specific statement parser override
///
/// This method is called to parse the next statement.
Expand All @@ -796,36 +848,6 @@ pub trait Dialect: Debug + Any {
Ok(None)
}

/// Decide the lexical Precedence of operators.
///
/// Uses (APPROXIMATELY) <https://www.postgresql.org/docs/7.0/operators.htm#AEN2026> as a reference
fn prec_value(&self, prec: Precedence) -> u8 {
match prec {
Precedence::Period => 100,
Precedence::DoubleColon => 50,
Precedence::AtTz => 41,
Precedence::MulDivModOp => 40,
Precedence::PlusMinus => 30,
Precedence::Xor => 24,
Precedence::Ampersand => 23,
Precedence::Caret => 22,
Precedence::Pipe => 21,
Precedence::Between => 20,
Precedence::Eq => 20,
Precedence::Like => 19,
Precedence::Is => 17,
Precedence::PgOther => 16,
Precedence::UnaryNot => 15,
Precedence::And => 10,
Precedence::Or => 5,
}
}

/// Returns the precedence when the precedence is otherwise unknown
fn prec_unknown(&self) -> u8 {
0
}

/// Returns true if this dialect requires the `TABLE` keyword after `DESCRIBE`
///
/// Defaults to false.
Expand Down Expand Up @@ -1260,6 +1282,7 @@ pub fn dialect_from_str(dialect_name: impl AsRef<str>) -> Option<Box<dyn Dialect
"ansi" => Some(Box::new(AnsiDialect {})),
"duckdb" => Some(Box::new(DuckDbDialect {})),
"databricks" => Some(Box::new(DatabricksDialect {})),
"oracle" => Some(Box::new(OracleDialect {})),
_ => None,
}
}
Expand Down
102 changes: 102 additions & 0 deletions src/dialect/oracle.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
// 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 log::debug;

use crate::{dialect::DEFAULT_PREC_VALUE_MUL_DIV_MOD_OP, tokenizer::Token};

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 get_next_precedence(
&self,
_parser: &crate::parser::Parser,
) -> Option<Result<u8, crate::parser::ParserError>> {
let t = _parser.peek_token();
debug!("get_next_precedence() {t:?}");

match t.token {
Token::StringConcat => {
// ~ overriding the default precedence to the same level as mul-div-mod
// ~ see: https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/About-SQL-Operators.html
Some(Ok(DEFAULT_PREC_VALUE_MUL_DIV_MOD_OP))
}
_ => None,
}
}

fn supports_group_by_expr(&self) -> bool {
true
}
}
9 changes: 9 additions & 0 deletions src/keywords.rs
Original file line number Diff line number Diff line change
Expand Up @@ -220,6 +220,7 @@ define_keywords!(
COMMITTED,
COMMUTATOR,
COMPATIBLE,
COMPRESS,
COMPRESSION,
COMPUPDATE,
COMPUTE,
Expand Down Expand Up @@ -464,6 +465,7 @@ define_keywords!(
IAM_ROLE,
ICEBERG,
ID,
IDENTIFIED,
IDENTITY,
IDENTITY_INSERT,
IF,
Expand Down Expand Up @@ -567,6 +569,7 @@ define_keywords!(
LOG,
LOGIN,
LOGS,
LONG,
LONGBLOB,
LONGTEXT,
LOWCARDINALITY,
Expand Down Expand Up @@ -652,6 +655,7 @@ define_keywords!(
NFKD,
NO,
NOBYPASSRLS,
NOCOMPRESS,
NOCREATEDB,
NOCREATEROLE,
NOINHERIT,
Expand All @@ -675,6 +679,7 @@ define_keywords!(
NULLABLE,
NULLIF,
NULLS,
NUMBER,
NUMERIC,
NVARCHAR,
OBJECT,
Expand Down Expand Up @@ -741,6 +746,7 @@ define_keywords!(
PAST,
PATH,
PATTERN,
PCTFREE,
PER,
PERCENT,
PERCENTILE_CONT,
Expand Down Expand Up @@ -913,6 +919,7 @@ define_keywords!(
SIGNED,
SIMILAR,
SIMPLE,
SIZE,
SKIP,
SLOW,
SMALLINT,
Expand Down Expand Up @@ -974,6 +981,7 @@ define_keywords!(
SWAP,
SYMMETRIC,
SYNC,
SYNONYM,
SYSTEM,
SYSTEM_TIME,
SYSTEM_USER,
Expand Down Expand Up @@ -1085,6 +1093,7 @@ define_keywords!(
VARBINARY,
VARBIT,
VARCHAR,
VARCHAR2,
VARIABLE,
VARIABLES,
VARYING,
Expand Down
12 changes: 11 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1704,6 +1704,8 @@ impl<'a> Parser<'a> {
| Token::TripleSingleQuotedRawStringLiteral(_)
| Token::TripleDoubleQuotedRawStringLiteral(_)
| Token::NationalStringLiteral(_)
| Token::QuoteDelimitedStringLiteral(_, _, _)
| Token::NationalQuoteDelimitedStringLiteral(_, _, _)
| Token::HexStringLiteral(_) => {
self.prev_token();
Ok(Expr::Value(self.parse_value()?))
Expand Down Expand Up @@ -2716,6 +2718,8 @@ impl<'a> Parser<'a> {
| Token::EscapedStringLiteral(_)
| Token::UnicodeStringLiteral(_)
| Token::NationalStringLiteral(_)
| Token::QuoteDelimitedStringLiteral(_, _, _)
| Token::NationalQuoteDelimitedStringLiteral(_, _, _)
| Token::HexStringLiteral(_) => Some(Box::new(self.parse_expr()?)),
_ => self.expected(
"either filler, WITH, or WITHOUT in LISTAGG",
Expand Down Expand Up @@ -10483,6 +10487,12 @@ impl<'a> Parser<'a> {
Token::NationalStringLiteral(ref s) => {
ok_value(Value::NationalStringLiteral(s.to_string()))
}
Token::QuoteDelimitedStringLiteral(q1, s, q2) => {
ok_value(Value::QuoteDelimitedStringLiteral(q1, s, q2))
}
Token::NationalQuoteDelimitedStringLiteral(q1, s, q2) => {
ok_value(Value::NationalQuoteDelimitedStringLiteral(q1, s, q2))
}
Token::EscapedStringLiteral(ref s) => {
ok_value(Value::EscapedStringLiteral(s.to_string()))
}
Expand Down Expand Up @@ -12228,7 +12238,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))?;
Expand Down
Loading