mirror of
https://github.com/apache/datafusion-sqlparser-rs.git
synced 2025-09-20 12:49:47 +00:00
feat: Support double quoted string (#530)
This commit is contained in:
parent
f29ce10a1a
commit
17c238bda7
4 changed files with 52 additions and 7 deletions
|
@ -504,6 +504,7 @@ impl<'a> Parser<'a> {
|
||||||
}
|
}
|
||||||
Token::Number(_, _)
|
Token::Number(_, _)
|
||||||
| Token::SingleQuotedString(_)
|
| Token::SingleQuotedString(_)
|
||||||
|
| Token::DoubleQuotedString(_)
|
||||||
| Token::NationalStringLiteral(_)
|
| Token::NationalStringLiteral(_)
|
||||||
| Token::HexStringLiteral(_) => {
|
| Token::HexStringLiteral(_) => {
|
||||||
self.prev_token();
|
self.prev_token();
|
||||||
|
@ -2773,6 +2774,7 @@ impl<'a> Parser<'a> {
|
||||||
Err(e) => parser_err!(format!("Could not parse '{}' as number: {}", n, e)),
|
Err(e) => parser_err!(format!("Could not parse '{}' as number: {}", n, e)),
|
||||||
},
|
},
|
||||||
Token::SingleQuotedString(ref s) => Ok(Value::SingleQuotedString(s.to_string())),
|
Token::SingleQuotedString(ref s) => Ok(Value::SingleQuotedString(s.to_string())),
|
||||||
|
Token::DoubleQuotedString(ref s) => Ok(Value::DoubleQuotedString(s.to_string())),
|
||||||
Token::NationalStringLiteral(ref s) => Ok(Value::NationalStringLiteral(s.to_string())),
|
Token::NationalStringLiteral(ref s) => Ok(Value::NationalStringLiteral(s.to_string())),
|
||||||
Token::EscapedStringLiteral(ref s) => Ok(Value::EscapedStringLiteral(s.to_string())),
|
Token::EscapedStringLiteral(ref s) => Ok(Value::EscapedStringLiteral(s.to_string())),
|
||||||
Token::HexStringLiteral(ref s) => Ok(Value::HexStringLiteral(s.to_string())),
|
Token::HexStringLiteral(ref s) => Ok(Value::HexStringLiteral(s.to_string())),
|
||||||
|
@ -2807,6 +2809,7 @@ impl<'a> Parser<'a> {
|
||||||
match self.next_token() {
|
match self.next_token() {
|
||||||
Token::Word(Word { value, keyword, .. }) if keyword == Keyword::NoKeyword => Ok(value),
|
Token::Word(Word { value, keyword, .. }) if keyword == Keyword::NoKeyword => Ok(value),
|
||||||
Token::SingleQuotedString(s) => Ok(s),
|
Token::SingleQuotedString(s) => Ok(s),
|
||||||
|
Token::DoubleQuotedString(s) => Ok(s),
|
||||||
Token::EscapedStringLiteral(s) if dialect_of!(self is PostgreSqlDialect | GenericDialect) => {
|
Token::EscapedStringLiteral(s) if dialect_of!(self is PostgreSqlDialect | GenericDialect) => {
|
||||||
Ok(s)
|
Ok(s)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,8 @@ pub enum Token {
|
||||||
Char(char),
|
Char(char),
|
||||||
/// Single quoted string: i.e: 'string'
|
/// Single quoted string: i.e: 'string'
|
||||||
SingleQuotedString(String),
|
SingleQuotedString(String),
|
||||||
|
/// Double quoted string: i.e: "string"
|
||||||
|
DoubleQuotedString(String),
|
||||||
/// "National" string literal: i.e: N'string'
|
/// "National" string literal: i.e: N'string'
|
||||||
NationalStringLiteral(String),
|
NationalStringLiteral(String),
|
||||||
/// "escaped" string literal, which are an extension to the SQL standard: i.e: e'first \n second' or E 'first \n second'
|
/// "escaped" string literal, which are an extension to the SQL standard: i.e: e'first \n second' or E 'first \n second'
|
||||||
|
@ -161,6 +163,7 @@ impl fmt::Display for Token {
|
||||||
Token::Number(ref n, l) => write!(f, "{}{long}", n, long = if *l { "L" } else { "" }),
|
Token::Number(ref n, l) => write!(f, "{}{long}", n, long = if *l { "L" } else { "" }),
|
||||||
Token::Char(ref c) => write!(f, "{}", c),
|
Token::Char(ref c) => write!(f, "{}", c),
|
||||||
Token::SingleQuotedString(ref s) => write!(f, "'{}'", s),
|
Token::SingleQuotedString(ref s) => write!(f, "'{}'", s),
|
||||||
|
Token::DoubleQuotedString(ref s) => write!(f, "\"{}\"", s),
|
||||||
Token::NationalStringLiteral(ref s) => write!(f, "N'{}'", s),
|
Token::NationalStringLiteral(ref s) => write!(f, "N'{}'", s),
|
||||||
Token::EscapedStringLiteral(ref s) => write!(f, "E'{}'", s),
|
Token::EscapedStringLiteral(ref s) => write!(f, "E'{}'", s),
|
||||||
Token::HexStringLiteral(ref s) => write!(f, "X'{}'", s),
|
Token::HexStringLiteral(ref s) => write!(f, "X'{}'", s),
|
||||||
|
@ -385,7 +388,7 @@ impl<'a> Tokenizer<'a> {
|
||||||
match chars.peek() {
|
match chars.peek() {
|
||||||
Some('\'') => {
|
Some('\'') => {
|
||||||
// N'...' - a <national character string literal>
|
// N'...' - a <national character string literal>
|
||||||
let s = self.tokenize_single_quoted_string(chars)?;
|
let s = self.tokenize_quoted_string(chars, '\'')?;
|
||||||
Ok(Some(Token::NationalStringLiteral(s)))
|
Ok(Some(Token::NationalStringLiteral(s)))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -417,7 +420,7 @@ impl<'a> Tokenizer<'a> {
|
||||||
match chars.peek() {
|
match chars.peek() {
|
||||||
Some('\'') => {
|
Some('\'') => {
|
||||||
// X'...' - a <binary string literal>
|
// X'...' - a <binary string literal>
|
||||||
let s = self.tokenize_single_quoted_string(chars)?;
|
let s = self.tokenize_quoted_string(chars, '\'')?;
|
||||||
Ok(Some(Token::HexStringLiteral(s)))
|
Ok(Some(Token::HexStringLiteral(s)))
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
|
@ -442,12 +445,20 @@ impl<'a> Tokenizer<'a> {
|
||||||
}
|
}
|
||||||
Ok(Some(Token::make_word(&s, None)))
|
Ok(Some(Token::make_word(&s, None)))
|
||||||
}
|
}
|
||||||
// string
|
// single quoted string
|
||||||
'\'' => {
|
'\'' => {
|
||||||
let s = self.tokenize_single_quoted_string(chars)?;
|
let s = self.tokenize_quoted_string(chars, '\'')?;
|
||||||
|
|
||||||
Ok(Some(Token::SingleQuotedString(s)))
|
Ok(Some(Token::SingleQuotedString(s)))
|
||||||
}
|
}
|
||||||
|
// double quoted string
|
||||||
|
'\"' if !self.dialect.is_delimited_identifier_start(ch)
|
||||||
|
&& !self.dialect.is_identifier_start(ch) =>
|
||||||
|
{
|
||||||
|
let s = self.tokenize_quoted_string(chars, '"')?;
|
||||||
|
|
||||||
|
Ok(Some(Token::DoubleQuotedString(s)))
|
||||||
|
}
|
||||||
// delimited (quoted) identifier
|
// delimited (quoted) identifier
|
||||||
quote_start
|
quote_start
|
||||||
if self.dialect.is_delimited_identifier_start(ch)
|
if self.dialect.is_delimited_identifier_start(ch)
|
||||||
|
@ -769,9 +780,10 @@ impl<'a> Tokenizer<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read a single quoted string, starting with the opening quote.
|
/// Read a single quoted string, starting with the opening quote.
|
||||||
fn tokenize_single_quoted_string(
|
fn tokenize_quoted_string(
|
||||||
&self,
|
&self,
|
||||||
chars: &mut Peekable<Chars<'_>>,
|
chars: &mut Peekable<Chars<'_>>,
|
||||||
|
quote_style: char,
|
||||||
) -> Result<String, TokenizerError> {
|
) -> Result<String, TokenizerError> {
|
||||||
let mut s = String::new();
|
let mut s = String::new();
|
||||||
chars.next(); // consume the opening quote
|
chars.next(); // consume the opening quote
|
||||||
|
@ -780,12 +792,12 @@ impl<'a> Tokenizer<'a> {
|
||||||
let mut is_escaped = false;
|
let mut is_escaped = false;
|
||||||
while let Some(&ch) = chars.peek() {
|
while let Some(&ch) = chars.peek() {
|
||||||
match ch {
|
match ch {
|
||||||
'\'' => {
|
char if char == quote_style => {
|
||||||
chars.next(); // consume
|
chars.next(); // consume
|
||||||
if is_escaped {
|
if is_escaped {
|
||||||
s.push(ch);
|
s.push(ch);
|
||||||
is_escaped = false;
|
is_escaped = false;
|
||||||
} else if chars.peek().map(|c| *c == '\'').unwrap_or(false) {
|
} else if chars.peek().map(|c| *c == quote_style).unwrap_or(false) {
|
||||||
s.push(ch);
|
s.push(ch);
|
||||||
chars.next();
|
chars.next();
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -18,6 +18,21 @@ use test_utils::*;
|
||||||
use sqlparser::ast::*;
|
use sqlparser::ast::*;
|
||||||
use sqlparser::dialect::BigQueryDialect;
|
use sqlparser::dialect::BigQueryDialect;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_literal_string() {
|
||||||
|
let sql = r#"SELECT 'single', "double""#;
|
||||||
|
let select = bigquery().verified_only_select(sql);
|
||||||
|
assert_eq!(2, select.projection.len());
|
||||||
|
assert_eq!(
|
||||||
|
&Expr::Value(Value::SingleQuotedString("single".to_string())),
|
||||||
|
expr_from_projection(&select.projection[0])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&Expr::Value(Value::DoubleQuotedString("double".to_string())),
|
||||||
|
expr_from_projection(&select.projection[1])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_table_identifiers() {
|
fn parse_table_identifiers() {
|
||||||
fn test_table_ident(ident: &str, expected: Vec<Ident>) {
|
fn test_table_ident(ident: &str, expected: Vec<Ident>) {
|
||||||
|
|
|
@ -30,6 +30,21 @@ fn parse_identifiers() {
|
||||||
mysql().verified_stmt("SELECT $a$, àà");
|
mysql().verified_stmt("SELECT $a$, àà");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_literal_string() {
|
||||||
|
let sql = r#"SELECT 'single', "double""#;
|
||||||
|
let select = mysql().verified_only_select(sql);
|
||||||
|
assert_eq!(2, select.projection.len());
|
||||||
|
assert_eq!(
|
||||||
|
&Expr::Value(Value::SingleQuotedString("single".to_string())),
|
||||||
|
expr_from_projection(&select.projection[0])
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
&Expr::Value(Value::DoubleQuotedString("double".to_string())),
|
||||||
|
expr_from_projection(&select.projection[1])
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_show_columns() {
|
fn parse_show_columns() {
|
||||||
let table_name = ObjectName(vec![Ident::new("mytable")]);
|
let table_name = ObjectName(vec![Ident::new("mytable")]);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue