diff --git a/src/ast/value.rs b/src/ast/value.rs index a906c4b3f..23f87ec0b 100644 --- a/src/ast/value.rs +++ b/src/ast/value.rs @@ -156,9 +156,10 @@ pub enum Value { /// Triple double quoted strings: Example """abc""" /// [BigQuery](https://cloud.google.com/bigquery/docs/reference/standard-sql/lexical#quoted_literals) TripleDoubleQuotedString(String), - /// e'string value' (postgres extension) - /// See [Postgres docs](https://www.postgresql.org/docs/8.3/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS) - /// for more details. + /// `E'...'` escape string literal. + /// + /// [PostgreSQL](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE) + /// [DuckDB](https://duckdb.org/docs/current/sql/data_types/literal_types#escape-string-literals) EscapedStringLiteral(String), /// u&'string value' (postgres extension) /// See [Postgres docs](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-UESCAPE) diff --git a/src/dialect/duckdb.rs b/src/dialect/duckdb.rs index 2e3673bc4..3cf98ce34 100644 --- a/src/dialect/duckdb.rs +++ b/src/dialect/duckdb.rs @@ -142,4 +142,8 @@ impl Dialect for DuckDbDialect { fn supports_numeric_literal_underscores(&self) -> bool { true } + + fn supports_string_escape_constant(&self) -> bool { + true + } } diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 7c4744c5a..36125a51d 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -1420,7 +1420,8 @@ pub trait Dialect: Debug + Any { /// Returns true if this dialect supports the E'...' syntax for string literals /// - /// Postgres: + /// [PostgreSQL](https://www.postgresql.org/docs/current/sql-syntax-lexical.html#SQL-SYNTAX-STRINGS-ESCAPE) + /// [DuckDB](https://duckdb.org/docs/current/sql/data_types/literal_types#escape-string-literals) fn supports_string_escape_constant(&self) -> bool { false } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index 15f135fff..f3dbd4786 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -1977,8 +1977,7 @@ impl<'a> Parser<'a> { ), }) } - Token::EscapedStringLiteral(_) if dialect_is!(dialect is PostgreSqlDialect | GenericDialect) => - { + Token::EscapedStringLiteral(_) if self.dialect.supports_string_escape_constant() => { self.prev_token(); Ok(Expr::Value(self.parse_value()?)) } @@ -12688,7 +12687,7 @@ impl<'a> Parser<'a> { }) => Ok(value), Token::SingleQuotedString(s) => Ok(s), Token::DoubleQuotedString(s) => Ok(s), - Token::EscapedStringLiteral(s) if dialect_of!(self is PostgreSqlDialect | GenericDialect) => { + Token::EscapedStringLiteral(s) if self.dialect.supports_string_escape_constant() => { Ok(s) } Token::UnicodeStringLiteral(s) => Ok(s), diff --git a/tests/sqlparser_duckdb.rs b/tests/sqlparser_duckdb.rs index a338ef7a8..25cebe01d 100644 --- a/tests/sqlparser_duckdb.rs +++ b/tests/sqlparser_duckdb.rs @@ -23,7 +23,7 @@ use sqlparser::tokenizer::Span; use test_utils::*; use sqlparser::ast::*; -use sqlparser::dialect::{DuckDbDialect, GenericDialect}; +use sqlparser::dialect::{AnsiDialect, DuckDbDialect, GenericDialect}; use sqlparser::parser::ParserError; fn duckdb() -> TestedDialects { @@ -910,3 +910,39 @@ fn test_duckdb_lambda_function() { let sql_transform = "SELECT list_transform([1, 2, 3], lambda x : x * 2)"; duckdb().verified_stmt(sql_transform); } + +#[test] +fn test_escape_string_literal() { + duckdb().verified_stmt(r#"SELECT E'a\nb' AS value"#); + + assert_eq!( + duckdb().verified_expr(r#"E'a\nb'"#), + Expr::Value((Value::EscapedStringLiteral("a\nb".to_string())).with_empty_span()) + ); + assert_eq!( + duckdb().verified_expr(r#"'a\nb'"#), + Expr::Value((Value::SingleQuotedString(r#"a\nb"#.to_string())).with_empty_span()) + ); + duckdb().one_statement_parses_to( + r#"COPY data TO E'out\n.csv'"#, + r#"COPY data TO 'out +.csv'"#, + ); + + let err = duckdb() + .parse_sql_statements("SELECT E'unterminated") + .unwrap_err(); + assert_eq!( + err, + ParserError::TokenizerError( + "Unterminated encoded string literal at Line: 1, Column: 8".to_string() + ) + ); + + let ansi = TestedDialects::new(vec![Box::new(AnsiDialect {})]); + assert_eq!( + ansi.parse_sql_statements(r#"SELECT E'a\nb' AS value"#) + .unwrap_err(), + ParserError::ParserError("Expected: end of statement, found: AS".to_string()) + ); +}