This is an automated email from the ASF dual-hosted git repository.
github-bot pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/datafusion-sqlparser-rs.git
The following commit(s) were added to refs/heads/main by this push:
new f84887d0 Oracle: Support for quote delimited strings (#2130)
f84887d0 is described below
commit f84887d0049105c7b84621d65b71e1ee640e18e9
Author: xitep <[email protected]>
AuthorDate: Tue Dec 16 19:04:11 2025 +0100
Oracle: Support for quote delimited strings (#2130)
---
src/ast/mod.rs | 2 +-
src/ast/value.rs | 32 +++++++
src/dialect/generic.rs | 4 +
src/dialect/mod.rs | 7 ++
src/dialect/oracle.rs | 4 +
src/parser/merge.rs | 2 +-
src/parser/mod.rs | 10 ++
src/tokenizer.rs | 92 +++++++++++++++++-
tests/sqlparser_oracle.rs | 234 +++++++++++++++++++++++++++++++++++++++++++++-
9 files changed, 381 insertions(+), 6 deletions(-)
diff --git a/src/ast/mod.rs b/src/ast/mod.rs
index 23cde478..f1e79b0d 100644
--- a/src/ast/mod.rs
+++ b/src/ast/mod.rs
@@ -110,7 +110,7 @@ pub use self::trigger::{
pub use self::value::{
escape_double_quote_string, escape_quoted_string, DateTimeField,
DollarQuotedString,
- NormalizationForm, TrimWhereField, Value, ValueWithSpan,
+ NormalizationForm, QuoteDelimitedString, TrimWhereField, Value,
ValueWithSpan,
};
use crate::ast::helpers::key_value_options::KeyValueOptions;
diff --git a/src/ast/value.rs b/src/ast/value.rs
index fdfa6a67..ccbb12a3 100644
--- a/src/ast/value.rs
+++ b/src/ast/value.rs
@@ -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#GUID-1824CBAA-6E16-4921-B2A6-112FB02248DA)
+ QuoteDelimitedStringLiteral(QuoteDelimitedString),
+ /// "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#GUID-1824CBAA-6E16-4921-B2A6-112FB02248DA)
+ NationalQuoteDelimitedStringLiteral(QuoteDelimitedString),
/// X'hex value'
HexStringLiteral(String),
@@ -207,6 +213,8 @@ impl Value {
| Value::NationalStringLiteral(s)
| Value::HexStringLiteral(s) => Some(s),
Value::DollarQuotedString(s) => Some(s.value),
+ Value::QuoteDelimitedStringLiteral(s) => Some(s.value),
+ Value::NationalQuoteDelimitedStringLiteral(s) => Some(s.value),
_ => None,
}
}
@@ -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(v) => v.fmt(f),
+ Value::NationalQuoteDelimitedStringLiteral(v) => write!(f, "N{v}"),
Value::HexStringLiteral(v) => write!(f, "X'{v}'"),
Value::Boolean(v) => write!(f, "{v}"),
Value::SingleQuotedByteStringLiteral(v) => write!(f, "B'{v}'"),
@@ -279,6 +289,28 @@ impl fmt::Display for DollarQuotedString {
}
}
+/// A quote delimited string literal, e.g. `Q'_abc_'`.
+///
+/// See [Value::QuoteDelimitedStringLiteral] and/or
+/// [Value::NationalQuoteDelimitedStringLiteral].
+#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
+#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
+#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
+pub struct QuoteDelimitedString {
+ /// the quote start character; i.e. the character _after_ the opening `Q'`
+ pub start_quote: char,
+ /// the string literal value itself
+ pub value: String,
+ /// the quote end character; i.e. the character _before_ the closing `'`
+ pub end_quote: char,
+}
+
+impl fmt::Display for QuoteDelimitedString {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ write!(f, "Q'{}{}{}'", self.start_quote, self.value, self.end_quote)
+ }
+}
+
#[derive(Debug, Clone, PartialEq, Eq, Ord, PartialOrd, Hash)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "visitor", derive(Visit, VisitMut))]
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/parser/merge.rs b/src/parser/merge.rs
index b2283b67..2bc1544f 100644
--- a/src/parser/merge.rs
+++ b/src/parser/merge.rs
@@ -13,7 +13,7 @@
//! SQL Parser for a `MERGE` statement
#[cfg(not(feature = "std"))]
-use alloc::{boxed::Box, format, string::ToString, vec, vec::Vec};
+use alloc::{boxed::Box, format, vec, vec::Vec};
use crate::{
ast::{
diff --git a/src/parser/mod.rs b/src/parser/mod.rs
index 3ba4ba57..ade3c250 100644
--- a/src/parser/mod.rs
+++ b/src/parser/mod.rs
@@ -1754,6 +1754,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()?))
@@ -2770,6 +2772,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",
@@ -10697,6 +10701,12 @@ impl<'a> Parser<'a> {
Token::NationalStringLiteral(ref s) => {
ok_value(Value::NationalStringLiteral(s.to_string()))
}
+ Token::QuoteDelimitedStringLiteral(v) => {
+ ok_value(Value::QuoteDelimitedStringLiteral(v))
+ }
+ Token::NationalQuoteDelimitedStringLiteral(v) => {
+ ok_value(Value::NationalQuoteDelimitedStringLiteral(v))
+ }
Token::EscapedStringLiteral(ref s) => {
ok_value(Value::EscapedStringLiteral(s.to_string()))
}
diff --git a/src/tokenizer.rs b/src/tokenizer.rs
index 54a158c1..2ae17cf4 100644
--- a/src/tokenizer.rs
+++ b/src/tokenizer.rs
@@ -29,10 +29,10 @@ use alloc::{
vec,
vec::Vec,
};
-use core::iter::Peekable;
use core::num::NonZeroU8;
use core::str::Chars;
use core::{cmp, fmt};
+use core::{iter::Peekable, str};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
@@ -46,7 +46,10 @@ use crate::dialect::{
SnowflakeDialect,
};
use crate::keywords::{Keyword, ALL_KEYWORDS, ALL_KEYWORDS_INDEX};
-use crate::{ast::DollarQuotedString, dialect::HiveDialect};
+use crate::{
+ ast::{DollarQuotedString, QuoteDelimitedString},
+ dialect::HiveDialect,
+};
/// SQL Token enumeration
#[derive(Debug, Clone, PartialEq, PartialOrd, Eq, Ord, Hash)]
@@ -98,6 +101,12 @@ pub enum Token {
TripleDoubleQuotedRawStringLiteral(String),
/// "National" string literal: i.e: N'string'
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#GUID-1824CBAA-6E16-4921-B2A6-112FB02248DA)
+ QuoteDelimitedStringLiteral(QuoteDelimitedString),
+ /// "Nationa" quote delimited literal. Examples `NQ'{ab'c}'`,
`NQ'|ab'c|'`, `NQ'|ab|c|'`
+ ///
[Oracle](https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Literals.html#GUID-1824CBAA-6E16-4921-B2A6-112FB02248DA)
+ NationalQuoteDelimitedStringLiteral(QuoteDelimitedString),
/// "escaped" string literal, which are an extension to the SQL standard:
i.e: e'first \n second' or E 'first \n second'
EscapedStringLiteral(String),
/// Unicode string literal: i.e: U&'first \000A second'
@@ -292,6 +301,8 @@ impl fmt::Display for Token {
Token::TripleDoubleQuotedString(ref s) => write!(f,
"\"\"\"{s}\"\"\""),
Token::DollarQuotedString(ref s) => write!(f, "{s}"),
Token::NationalStringLiteral(ref s) => write!(f, "N'{s}'"),
+ Token::QuoteDelimitedStringLiteral(ref s) => s.fmt(f),
+ Token::NationalQuoteDelimitedStringLiteral(ref s) => write!(f,
"N{s}"),
Token::EscapedStringLiteral(ref s) => write!(f, "E'{s}'"),
Token::UnicodeStringLiteral(ref s) => write!(f, "U&'{s}'"),
Token::HexStringLiteral(ref s) => write!(f, "X'{s}'"),
@@ -1032,6 +1043,18 @@ impl<'a> Tokenizer<'a> {
self.tokenize_single_quoted_string(chars,
'\'', backslash_escape)?;
Ok(Some(Token::NationalStringLiteral(s)))
}
+ 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() {
+ self.tokenize_quote_delimited_string(chars,
&[n, q])
+ .map(|s|
Some(Token::NationalQuoteDelimitedStringLiteral(s)))
+ } else {
+ let s =
self.tokenize_word(String::from_iter([n, q]), chars);
+ Ok(Some(Token::make_word(&s, None)))
+ }
+ }
_ => {
// regular identifier starting with an "N"
let s = self.tokenize_word(n, chars);
@@ -1039,6 +1062,16 @@ impl<'a> Tokenizer<'a> {
}
}
}
+ 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])
+ .map(|s|
Some(Token::QuoteDelimitedStringLiteral(s)))
+ } else {
+ let s = self.tokenize_word(q, chars);
+ Ok(Some(Token::make_word(&s, None)))
+ }
+ }
// PostgreSQL accepts "escape" string constants, which are an
extension to the SQL standard.
x @ 'e' | x @ 'E' if
self.dialect.supports_string_escape_constant() => {
let starting_loc = chars.location();
@@ -1994,6 +2027,61 @@ impl<'a> Tokenizer<'a> {
)
}
+ /// Reads a quote delimited string expecting `chars.next()` to deliver a
quote.
+ ///
+ /// See
<https://docs.oracle.com/en/database/oracle/oracle-database/21/sqlrf/Literals.html#GUID-1824CBAA-6E16-4921-B2A6-112FB02248DA>
+ fn tokenize_quote_delimited_string(
+ &self,
+ chars: &mut State,
+ // the prefix that introduced the possible literal or word,
+ // e.g. "Q" or "nq"
+ literal_prefix: &[char],
+ ) -> Result<QuoteDelimitedString, TokenizerError> {
+ let literal_start_loc = chars.location();
+ chars.next();
+
+ let start_quote_loc = chars.location();
+ let (start_quote, end_quote) = match chars.next() {
+ None | Some(' ') | Some('\t') | Some('\r') | Some('\n') => {
+ return self.tokenizer_error(
+ start_quote_loc,
+ format!(
+ "Invalid space, tab, newline, or EOF after '{}''",
+ String::from_iter(literal_prefix)
+ ),
+ );
+ }
+ Some(c) => (
+ c,
+ match c {
+ '[' => ']',
+ '{' => '}',
+ '<' => '>',
+ '(' => ')',
+ c => c,
+ },
+ ),
+ };
+
+ // read the string literal until the "quote character" following a by
literal quote
+ let mut value = String::new();
+ while let Some(ch) = chars.next() {
+ if ch == end_quote {
+ if let Some('\'') = chars.peek() {
+ chars.next(); // ~ consume the quote
+ return Ok(QuoteDelimitedString {
+ start_quote,
+ value,
+ end_quote,
+ });
+ }
+ }
+ value.push(ch);
+ }
+
+ self.tokenizer_error(literal_start_loc, "Unterminated string literal")
+ }
+
/// Read a quoted string.
fn tokenize_quoted_string(
&self,
diff --git a/tests/sqlparser_oracle.rs b/tests/sqlparser_oracle.rs
index 09fd4191..68366036 100644
--- a/tests/sqlparser_oracle.rs
+++ b/tests/sqlparser_oracle.rs
@@ -21,11 +21,12 @@
use pretty_assertions::assert_eq;
use sqlparser::{
- ast::{BinaryOperator, Expr, Value, ValueWithSpan},
+ ast::{BinaryOperator, Expr, Ident, QuoteDelimitedString, Value,
ValueWithSpan},
dialect::OracleDialect,
+ parser::ParserError,
tokenizer::Span,
};
-use test_utils::{expr_from_projection, number, TestedDialects};
+use test_utils::{all_dialects_where, expr_from_projection, number,
TestedDialects};
mod test_utils;
@@ -33,6 +34,19 @@ fn oracle() -> TestedDialects {
TestedDialects::new(vec![Box::new(OracleDialect)])
}
+/// Convenience constructor for [QuoteDelimitedstring].
+fn quote_delimited_string(
+ start_quote: char,
+ value: &'static str,
+ end_quote: char,
+) -> QuoteDelimitedString {
+ QuoteDelimitedString {
+ start_quote,
+ value: value.into(),
+ end_quote,
+ }
+}
+
/// Oracle: `||` has a lower precedence than `*` and `/`
#[test]
fn muldiv_have_higher_precedence_than_strconcat() {
@@ -103,3 +117,219 @@ fn plusminus_have_same_precedence_as_strconcat() {
}
);
}
+
+#[test]
+fn parse_quote_delimited_string() {
+ let dialect = all_dialects_where(|d| d.supports_quote_delimited_string());
+ let sql = "SELECT Q'.abc.', \
+ Q'Xab'cX', \
+ Q'|abc'''|', \
+ Q'{abc}d}', \
+ Q'[]abc[]', \
+ Q'<a'bc>', \
+ Q'<<<a'bc>', \
+ Q'('abc'('abc)', \
+ Q'(abc'def))', \
+ Q'(abc'def)))' \
+ FROM dual";
+ let select = dialect.verified_only_select(sql);
+ assert_eq!(10, select.projection.len());
+ assert_eq!(
+ &Expr::Value(
+ Value::QuoteDelimitedStringLiteral(quote_delimited_string('.',
"abc", '.'))
+ .with_empty_span()
+ ),
+ expr_from_projection(&select.projection[0])
+ );
+ assert_eq!(
+ &Expr::Value(
+ (Value::QuoteDelimitedStringLiteral(quote_delimited_string('X',
"ab'c", 'X')))
+ .with_empty_span()
+ ),
+ expr_from_projection(&select.projection[1])
+ );
+ assert_eq!(
+ &Expr::Value(
+ (Value::QuoteDelimitedStringLiteral(quote_delimited_string('|',
"abc'''", '|')))
+ .with_empty_span()
+ ),
+ expr_from_projection(&select.projection[2])
+ );
+ assert_eq!(
+ &Expr::Value(
+ (Value::QuoteDelimitedStringLiteral(quote_delimited_string('{',
"abc}d", '}')))
+ .with_empty_span()
+ ),
+ expr_from_projection(&select.projection[3])
+ );
+ assert_eq!(
+ &Expr::Value(
+ (Value::QuoteDelimitedStringLiteral(quote_delimited_string('[',
"]abc[", ']')))
+ .with_empty_span()
+ ),
+ expr_from_projection(&select.projection[4])
+ );
+ assert_eq!(
+ &Expr::Value(
+ (Value::QuoteDelimitedStringLiteral(quote_delimited_string('<',
"a'bc", '>')))
+ .with_empty_span()
+ ),
+ expr_from_projection(&select.projection[5])
+ );
+ assert_eq!(
+ &Expr::Value(
+ (Value::QuoteDelimitedStringLiteral(quote_delimited_string('<',
"<<a'bc", '>')))
+ .with_empty_span()
+ ),
+ expr_from_projection(&select.projection[6])
+ );
+ assert_eq!(
+ &Expr::Value(
+ (Value::QuoteDelimitedStringLiteral(quote_delimited_string('(',
"'abc'('abc", ')')))
+ .with_empty_span()
+ ),
+ expr_from_projection(&select.projection[7])
+ );
+ assert_eq!(
+ &Expr::Value(
+ (Value::QuoteDelimitedStringLiteral(quote_delimited_string('(',
"abc'def)", ')')))
+ .with_empty_span()
+ ),
+ expr_from_projection(&select.projection[8])
+ );
+ assert_eq!(
+ &Expr::Value(
+ (Value::QuoteDelimitedStringLiteral(quote_delimited_string('(',
"abc'def))", ')')))
+ .with_empty_span()
+ ),
+ expr_from_projection(&select.projection[9])
+ );
+}
+
+#[test]
+fn parse_invalid_quote_delimited_strings() {
+ let dialect = all_dialects_where(|d| d.supports_quote_delimited_string());
+ // ~ invalid quote delimiter
+ for q in [' ', '\t', '\r', '\n'] {
+ assert_eq!(
+ dialect.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:?}"
+ );
+ }
+ // ~ invalid eof after quote
+ assert_eq!(
+ dialect.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"
+ );
+ // ~ unterminated string
+ assert_eq!(
+ dialect.parse_sql_statements("SELECT Q'|asdfa...."),
+ Err(ParserError::TokenizerError(
+ "Unterminated string literal at Line: 1, Column: 9".into()
+ )),
+ "with EOF quote char"
+ );
+}
+
+#[test]
+fn parse_quote_delimited_string_lowercase() {
+ let dialect = all_dialects_where(|d| d.supports_quote_delimited_string());
+ let sql = "select q'!a'b'c!d!' from dual";
+ let select = dialect.verified_only_select_with_canonical(sql, "SELECT
Q'!a'b'c!d!' FROM dual");
+ assert_eq!(1, select.projection.len());
+ assert_eq!(
+ &Expr::Value(
+ Value::QuoteDelimitedStringLiteral(quote_delimited_string('!',
"a'b'c!d", '!'))
+ .with_empty_span()
+ ),
+ expr_from_projection(&select.projection[0])
+ );
+}
+
+#[test]
+fn parse_quote_delimited_string_but_is_a_word() {
+ let dialect = all_dialects_where(|d| d.supports_quote_delimited_string());
+ let sql = "SELECT q, quux, q.abc FROM dual q";
+ let select = dialect.verified_only_select(sql);
+ assert_eq!(3, select.projection.len());
+ assert_eq!(
+ &Expr::Identifier(Ident::with_span(Span::empty(), "q")),
+ expr_from_projection(&select.projection[0])
+ );
+ assert_eq!(
+ &Expr::Identifier(Ident::with_span(Span::empty(), "quux")),
+ expr_from_projection(&select.projection[1])
+ );
+ assert_eq!(
+ &Expr::CompoundIdentifier(vec![
+ Ident::with_span(Span::empty(), "q"),
+ Ident::with_span(Span::empty(), "abc")
+ ]),
+ expr_from_projection(&select.projection[2])
+ );
+}
+
+#[test]
+fn parse_national_quote_delimited_string() {
+ let dialect = all_dialects_where(|d| d.supports_quote_delimited_string());
+ let sql = "SELECT NQ'.abc.' FROM dual";
+ let select = dialect.verified_only_select(sql);
+ assert_eq!(1, select.projection.len());
+ assert_eq!(
+ &Expr::Value(
+
Value::NationalQuoteDelimitedStringLiteral(quote_delimited_string('.', "abc",
'.'))
+ .with_empty_span()
+ ),
+ expr_from_projection(&select.projection[0])
+ );
+}
+
+#[test]
+fn parse_national_quote_delimited_string_lowercase() {
+ let dialect = all_dialects_where(|d| d.supports_quote_delimited_string());
+ for prefix in ["nq", "Nq", "nQ", "NQ"] {
+ let select = dialect.verified_only_select_with_canonical(
+ &format!("select {prefix}'!a'b'c!d!' from dual"),
+ "SELECT NQ'!a'b'c!d!' FROM dual",
+ );
+ assert_eq!(1, select.projection.len());
+ assert_eq!(
+ &Expr::Value(
+
Value::NationalQuoteDelimitedStringLiteral(quote_delimited_string(
+ '!', "a'b'c!d", '!'
+ ))
+ .with_empty_span()
+ ),
+ expr_from_projection(&select.projection[0])
+ );
+ }
+}
+
+#[test]
+fn parse_national_quote_delimited_string_but_is_a_word() {
+ let dialect = all_dialects_where(|d| d.supports_quote_delimited_string());
+ let sql = "SELECT nq, nqoo, nq.abc FROM dual q";
+ let select = dialect.verified_only_select(sql);
+ assert_eq!(3, select.projection.len());
+ assert_eq!(
+ &Expr::Identifier(Ident::with_span(Span::empty(), "nq")),
+ expr_from_projection(&select.projection[0])
+ );
+ assert_eq!(
+ &Expr::Identifier(Ident::with_span(Span::empty(), "nqoo")),
+ expr_from_projection(&select.projection[1])
+ );
+ assert_eq!(
+ &Expr::CompoundIdentifier(vec![
+ Ident::with_span(Span::empty(), "nq"),
+ Ident::with_span(Span::empty(), "abc")
+ ]),
+ expr_from_projection(&select.projection[2])
+ );
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]