Skip to content

Commit d8b43a9

Browse files
authored
Redshift escape string literal logic. (#28)
1 parent 7c0410b commit d8b43a9

File tree

2 files changed

+28
-10
lines changed

2 files changed

+28
-10
lines changed

src/tokenizer.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,8 @@ use sqlparser_derive::{Visit, VisitMut};
3838

3939
use crate::ast::DollarQuotedString;
4040
use crate::dialect::{
41-
BigQueryDialect, DuckDbDialect, GenericDialect, HiveDialect, SnowflakeDialect,
41+
BigQueryDialect, DuckDbDialect, GenericDialect, HiveDialect, RedshiftSqlDialect,
42+
SnowflakeDialect,
4243
};
4344
use crate::dialect::{Dialect, MySqlDialect};
4445
use crate::keywords::{Keyword, ALL_KEYWORDS, ALL_KEYWORDS_INDEX};
@@ -1365,7 +1366,7 @@ impl<'a> Tokenizer<'a> {
13651366
// consume
13661367
chars.next();
13671368
// slash escaping is specific to MySQL / BigQuery dialect.
1368-
if dialect_of!(self is MySqlDialect | BigQueryDialect) {
1369+
if dialect_of!(self is MySqlDialect | BigQueryDialect | RedshiftSqlDialect) {
13691370
if let Some(next) = chars.peek() {
13701371
if !self.unescape {
13711372
// In no-escape mode, the given query has to be saved completely including backslashes.

tests/sqlparser_redshift.rs

Lines changed: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ use test_utils::*;
1717

1818
use sqlparser::ast::*;
1919
use sqlparser::dialect::RedshiftSqlDialect;
20+
use sqlparser::parser::ParserOptions;
2021

2122
#[test]
2223
fn test_square_brackets_over_db_schema_table_name() {
@@ -202,10 +203,10 @@ fn parse_like() {
202203

203204
// Test with escape char
204205
let sql = &format!(
205-
"SELECT * FROM customers WHERE name {}LIKE '%a' ESCAPE '\\'",
206+
r#"SELECT * FROM customers WHERE name {}LIKE '%a' ESCAPE '\\'"#,
206207
if negated { "NOT " } else { "" }
207208
);
208-
let select = redshift().verified_only_select(sql);
209+
let select = redshift().verified_only_select_with_canonical(sql, "");
209210
assert_eq!(
210211
Expr::Like {
211212
expr: Box::new(Expr::Identifier(Ident::new("name").empty_span())),
@@ -220,10 +221,10 @@ fn parse_like() {
220221
// This statement tests that LIKE and NOT LIKE have the same precedence.
221222
// This was previously mishandled (#81).
222223
let sql = &format!(
223-
"SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL",
224+
r#"SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL"#,
224225
if negated { "NOT " } else { "" }
225226
);
226-
let select = redshift().verified_only_select(sql);
227+
let select = redshift().verified_only_select_with_canonical(sql, "");
227228
assert_eq!(
228229
Expr::IsNull(Box::new(Expr::Like {
229230
expr: Box::new(Expr::Identifier(Ident::new("name").empty_span())),
@@ -260,10 +261,10 @@ fn parse_similar_to() {
260261

261262
// Test with escape char
262263
let sql = &format!(
263-
"SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\'",
264+
r#"SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\'"#,
264265
if negated { "NOT " } else { "" }
265266
);
266-
let select = redshift().verified_only_select(sql);
267+
let select = redshift().verified_only_select_with_canonical(sql, "");
267268
assert_eq!(
268269
Expr::SimilarTo {
269270
expr: Box::new(Expr::Identifier(Ident::new("name").empty_span())),
@@ -277,10 +278,10 @@ fn parse_similar_to() {
277278

278279
// This statement tests that SIMILAR TO and NOT SIMILAR TO have the same precedence.
279280
let sql = &format!(
280-
"SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\' IS NULL",
281+
r#"SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\' IS NULL"#,
281282
if negated { "NOT " } else { "" }
282283
);
283-
let select = redshift().verified_only_select(sql);
284+
let select = redshift().verified_only_select_with_canonical(sql, "");
284285
assert_eq!(
285286
Expr::IsNull(Box::new(Expr::SimilarTo {
286287
expr: Box::new(Expr::Identifier(Ident::new("name").empty_span())),
@@ -303,6 +304,13 @@ fn redshift() -> TestedDialects {
303304
}
304305
}
305306

307+
fn redshift_unescaped() -> TestedDialects {
308+
TestedDialects {
309+
dialects: vec![Box::new(RedshiftSqlDialect {})],
310+
options: Some(ParserOptions::new().with_unescape(false)),
311+
}
312+
}
313+
306314
#[test]
307315
fn test_sharp() {
308316
let sql = "SELECT #_of_values";
@@ -346,3 +354,12 @@ fn parse_listagg() {
346354
fn parse_quoted_identifier() {
347355
redshift().verified_only_select(r#"SELECT 'foo' AS "123_col""#);
348356
}
357+
358+
#[test]
359+
fn test_escape_string() {
360+
redshift_unescaped().verified_stmt(r"SELECT 'I\'m fine'");
361+
redshift_unescaped().verified_stmt(r"SELECT 'I\\\'m fine'");
362+
redshift_unescaped().verified_stmt(r#"SELECT 'I''m fine'"#);
363+
redshift_unescaped().verified_stmt(r#"SELECT 'I\\\'m fine'"#);
364+
redshift_unescaped().verified_stmt(r#"SELECT '[\'\\[\\]]'"#);
365+
}

0 commit comments

Comments
 (0)