Guard quote delimited string by explicit dialect setting

This commit is contained in:
Petr Novotnik 2025-12-14 10:52:18 +01:00
parent 0cd577e558
commit d9466f0391
5 changed files with 38 additions and 10 deletions

View file

@ -195,4 +195,8 @@ impl Dialect for GenericDialect {
fn supports_interval_options(&self) -> bool {
true
}
fn supports_quote_delimited_string(&self) -> bool {
true
}
}

View file

@ -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

View file

@ -95,4 +95,8 @@ impl Dialect for OracleDialect {
fn supports_group_by_expr(&self) -> bool {
true
}
fn supports_quote_delimited_string(&self) -> bool {
true
}
}

View file

@ -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])

View file

@ -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]