From d9466f0391ba19e0691580225767adb6f90f5478 Mon Sep 17 00:00:00 2001 From: Petr Novotnik Date: Sun, 14 Dec 2025 10:52:18 +0100 Subject: [PATCH] Guard quote delimited string by explicit dialect setting --- src/dialect/generic.rs | 4 ++++ src/dialect/mod.rs | 7 +++++++ src/dialect/oracle.rs | 4 ++++ src/tokenizer.rs | 7 ++++--- tests/sqlparser_oracle.rs | 26 +++++++++++++++++++------- 5 files changed, 38 insertions(+), 10 deletions(-) diff --git a/src/dialect/generic.rs b/src/dialect/generic.rs index dffc5b52..bbedbc05 100644 --- a/src/dialect/generic.rs +++ b/src/dialect/generic.rs @@ -195,4 +195,8 @@ impl Dialect for GenericDialect { fn supports_interval_options(&self) -> bool { true } + + fn supports_quote_delimited_string(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 1d99d863..1a416e4d 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1209,6 +1209,13 @@ pub trait Dialect: Debug + Any { fn supports_semantic_view_table_factor(&self) -> bool { false } + + /// Support quote delimited string literals, e.g. `Q'{...}'` + /// + /// [Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/19/sqlrf/Literals.html#GUID-1824CBAA-6E16-4921-B2A6-112FB02248DA) + fn supports_quote_delimited_string(&self) -> bool { + false + } } /// This represents the operators for which precedence must be defined diff --git a/src/dialect/oracle.rs b/src/dialect/oracle.rs index f8bb0e15..54c2ace5 100644 --- a/src/dialect/oracle.rs +++ b/src/dialect/oracle.rs @@ -95,4 +95,8 @@ impl Dialect for OracleDialect { fn supports_group_by_expr(&self) -> bool { true } + + fn supports_quote_delimited_string(&self) -> bool { + true + } } diff --git a/src/tokenizer.rs b/src/tokenizer.rs index d11c2fcd..238bf233 100644 --- a/src/tokenizer.rs +++ b/src/tokenizer.rs @@ -40,11 +40,11 @@ use serde::{Deserialize, Serialize}; #[cfg(feature = "visitor")] use sqlparser_derive::{Visit, VisitMut}; +use crate::dialect::Dialect; use crate::dialect::{ BigQueryDialect, DuckDbDialect, GenericDialect, MySqlDialect, PostgreSqlDialect, SnowflakeDialect, }; -use crate::dialect::{Dialect, OracleDialect}; use crate::keywords::{Keyword, ALL_KEYWORDS, ALL_KEYWORDS_INDEX}; use crate::{ ast::{DollarQuotedString, QuoteDelimitedString}, @@ -1043,7 +1043,8 @@ impl<'a> Tokenizer<'a> { self.tokenize_single_quoted_string(chars, '\'', backslash_escape)?; Ok(Some(Token::NationalStringLiteral(s))) } - Some(&q @ 'q') | Some(&q @ 'Q') if dialect_of!(self is OracleDialect | GenericDialect) => + Some(&q @ 'q') | Some(&q @ 'Q') + if self.dialect.supports_quote_delimited_string() => { chars.next(); // consume and check the next char if let Some('\'') = chars.peek() { @@ -1061,7 +1062,7 @@ impl<'a> Tokenizer<'a> { } } } - q @ 'Q' | q @ 'q' if dialect_of!(self is OracleDialect | GenericDialect) => { + q @ 'Q' | q @ 'q' if self.dialect.supports_quote_delimited_string() => { chars.next(); // consume and check the next char if let Some('\'') = chars.peek() { self.tokenize_quote_delimited_string(chars, &[q]) diff --git a/tests/sqlparser_oracle.rs b/tests/sqlparser_oracle.rs index 8a0d8721..ee5f209e 100644 --- a/tests/sqlparser_oracle.rs +++ b/tests/sqlparser_oracle.rs @@ -21,7 +21,10 @@ use pretty_assertions::assert_eq; use sqlparser::{ - ast::{BinaryOperator, Expr, Ident, QuoteDelimitedString, Value, ValueWithSpan}, dialect::OracleDialect, parser::ParserError, tokenizer::Span + ast::{BinaryOperator, Expr, Ident, QuoteDelimitedString, Value, ValueWithSpan}, + dialect::OracleDialect, + parser::ParserError, + tokenizer::Span, }; use test_utils::{expr_from_projection, number, TestedDialects}; @@ -188,19 +191,28 @@ fn parse_invalid_quote_delimited_strings() { for q in [' ', '\t', '\r', '\n'] { assert_eq!( oracle().parse_sql_statements(&format!("SELECT Q'{q}abc{q}' FROM dual")), - Err(ParserError::TokenizerError("Invalid space, tab, newline, or EOF after 'Q'' at Line: 1, Column: 10".into())), - "with quote char {q:?}"); + Err(ParserError::TokenizerError( + "Invalid space, tab, newline, or EOF after 'Q'' at Line: 1, Column: 10".into() + )), + "with quote char {q:?}" + ); } // ~ invalid eof after quote assert_eq!( oracle().parse_sql_statements("SELECT Q'"), - Err(ParserError::TokenizerError("Invalid space, tab, newline, or EOF after 'Q'' at Line: 1, Column: 10".into())), - "with EOF quote char"); + Err(ParserError::TokenizerError( + "Invalid space, tab, newline, or EOF after 'Q'' at Line: 1, Column: 10".into() + )), + "with EOF quote char" + ); // ~ unterminated string assert_eq!( oracle().parse_sql_statements("SELECT Q'|asdfa...."), - Err(ParserError::TokenizerError("Unterminated string literal at Line: 1, Column: 9".into())), - "with EOF quote char"); + Err(ParserError::TokenizerError( + "Unterminated string literal at Line: 1, Column: 9".into() + )), + "with EOF quote char" + ); } #[test]