diff --git a/src/ast/mod.rs b/src/ast/mod.rs index 4ee4bab0..2b81f0b3 100644 --- a/src/ast/mod.rs +++ b/src/ast/mod.rs @@ -35,7 +35,7 @@ pub use self::query::{ OrderByExpr, Query, Select, SelectInto, SelectItem, SetExpr, SetOperator, SetQuantifier, TableAlias, TableFactor, TableWithJoins, Top, Values, With, }; -pub use self::value::{DateTimeField, TrimWhereField, Value}; +pub use self::value::{escape_quoted_string, DateTimeField, TrimWhereField, Value}; mod data_type; mod ddl; diff --git a/src/parser.rs b/src/parser.rs index 73af099d..0753d263 100644 --- a/src/parser.rs +++ b/src/parser.rs @@ -3753,6 +3753,8 @@ impl<'a> Parser<'a> { // ignore the and treat the multiple strings as // a single ." Token::SingleQuotedString(s) => Ok(Some(Ident::with_quote('\'', s))), + // Support for MySql dialect double qouted string, `AS "HOUR"` for example + Token::DoubleQuotedString(s) => Ok(Some(Ident::with_quote('\"', s))), not_an_ident => { if after_as { return self.expected("an identifier after AS", not_an_ident); @@ -3836,6 +3838,7 @@ impl<'a> Parser<'a> { match self.next_token() { Token::Word(w) => Ok(w.to_ident()), Token::SingleQuotedString(s) => Ok(Ident::with_quote('\'', s)), + Token::DoubleQuotedString(s) => Ok(Ident::with_quote('\"', s)), unexpected => self.expected("identifier", unexpected), } } diff --git a/src/test_utils.rs b/src/test_utils.rs index d51aec1a..cbb92928 100644 --- a/src/test_utils.rs +++ b/src/test_utils.rs @@ -142,6 +142,8 @@ pub fn all_dialects() -> TestedDialects { Box::new(SnowflakeDialect {}), Box::new(HiveDialect {}), Box::new(RedshiftSqlDialect {}), + Box::new(MySqlDialect {}), + Box::new(BigQueryDialect {}), ], } } diff --git a/tests/sqlparser_bigquery.rs b/tests/sqlparser_bigquery.rs index 0a606c3e..86b47dda 100644 --- a/tests/sqlparser_bigquery.rs +++ b/tests/sqlparser_bigquery.rs @@ -115,6 +115,115 @@ fn parse_cast_type() { bigquery().verified_only_select(sql); } +#[test] +fn parse_like() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a'", + if negated { "NOT " } else { "" } + ); + let select = bigquery().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = bigquery().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that LIKE and NOT LIKE have the same precedence. + // This was previously mishandled (#81). + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = bigquery().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + +#[test] +fn parse_similar_to() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a'", + if negated { "NOT " } else { "" } + ); + let select = bigquery().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = bigquery().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that SIMILAR TO and NOT SIMILAR TO have the same precedence. + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = bigquery().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + fn bigquery() -> TestedDialects { TestedDialects { dialects: vec![Box::new(BigQueryDialect {})], diff --git a/tests/sqlparser_clickhouse.rs b/tests/sqlparser_clickhouse.rs index a61df73c..3e974d56 100644 --- a/tests/sqlparser_clickhouse.rs +++ b/tests/sqlparser_clickhouse.rs @@ -152,6 +152,168 @@ fn parse_kill() { ); } +#[test] +fn parse_delimited_identifiers() { + // check that quoted identifiers in any position remain quoted after serialization + let select = clickhouse().verified_only_select( + r#"SELECT "alias"."bar baz", "myfun"(), "simple id" AS "column alias" FROM "a table" AS "alias""#, + ); + // check FROM + match only(select.from).relation { + TableFactor::Table { + name, + alias, + args, + with_hints, + } => { + assert_eq!(vec![Ident::with_quote('"', "a table")], name.0); + assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name); + assert!(args.is_none()); + assert!(with_hints.is_empty()); + } + _ => panic!("Expecting TableFactor::Table"), + } + // check SELECT + assert_eq!(3, select.projection.len()); + assert_eq!( + &Expr::CompoundIdentifier(vec![ + Ident::with_quote('"', "alias"), + Ident::with_quote('"', "bar baz"), + ]), + expr_from_projection(&select.projection[0]), + ); + assert_eq!( + &Expr::Function(Function { + name: ObjectName(vec![Ident::with_quote('"', "myfun")]), + args: vec![], + over: None, + distinct: false, + special: false, + }), + expr_from_projection(&select.projection[1]), + ); + match &select.projection[2] { + SelectItem::ExprWithAlias { expr, alias } => { + assert_eq!(&Expr::Identifier(Ident::with_quote('"', "simple id")), expr); + assert_eq!(&Ident::with_quote('"', "column alias"), alias); + } + _ => panic!("Expected ExprWithAlias"), + } + + clickhouse().verified_stmt(r#"CREATE TABLE "foo" ("bar" "int")"#); + clickhouse().verified_stmt(r#"ALTER TABLE foo ADD CONSTRAINT "bar" PRIMARY KEY (baz)"#); + //TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#); +} + +#[test] +fn parse_like() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a'", + if negated { "NOT " } else { "" } + ); + let select = clickhouse().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = clickhouse().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that LIKE and NOT LIKE have the same precedence. + // This was previously mishandled (#81). + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = clickhouse().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + +#[test] +fn parse_similar_to() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a'", + if negated { "NOT " } else { "" } + ); + let select = clickhouse().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = clickhouse().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that SIMILAR TO and NOT SIMILAR TO have the same precedence. + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = clickhouse().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + fn clickhouse() -> TestedDialects { TestedDialects { dialects: vec![Box::new(ClickHouseDialect {})], diff --git a/tests/sqlparser_common.rs b/tests/sqlparser_common.rs index 52c6a56a..4efb8cc7 100644 --- a/tests/sqlparser_common.rs +++ b/tests/sqlparser_common.rs @@ -886,61 +886,6 @@ fn parse_not_precedence() { ); } -#[test] -fn parse_like() { - fn chk(negated: bool) { - let sql = &format!( - "SELECT * FROM customers WHERE name {}LIKE '%a'", - if negated { "NOT " } else { "" } - ); - let select = verified_only_select(sql); - assert_eq!( - Expr::Like { - expr: Box::new(Expr::Identifier(Ident::new("name"))), - negated, - pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: None, - }, - select.selection.unwrap() - ); - - // Test with escape char - let sql = &format!( - "SELECT * FROM customers WHERE name {}LIKE '%a' ESCAPE '\\'", - if negated { "NOT " } else { "" } - ); - let select = verified_only_select(sql); - assert_eq!( - Expr::Like { - expr: Box::new(Expr::Identifier(Ident::new("name"))), - negated, - pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: Some('\\'), - }, - select.selection.unwrap() - ); - - // This statement tests that LIKE and NOT LIKE have the same precedence. - // This was previously mishandled (#81). - let sql = &format!( - "SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL", - if negated { "NOT " } else { "" } - ); - let select = verified_only_select(sql); - assert_eq!( - Expr::IsNull(Box::new(Expr::Like { - expr: Box::new(Expr::Identifier(Ident::new("name"))), - negated, - pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: None, - })), - select.selection.unwrap() - ); - } - chk(false); - chk(true); -} - #[test] fn parse_null_like() { let sql = "SELECT \ @@ -1035,60 +980,6 @@ fn parse_ilike() { chk(true); } -#[test] -fn parse_similar_to() { - fn chk(negated: bool) { - let sql = &format!( - "SELECT * FROM customers WHERE name {}SIMILAR TO '%a'", - if negated { "NOT " } else { "" } - ); - let select = verified_only_select(sql); - assert_eq!( - Expr::SimilarTo { - expr: Box::new(Expr::Identifier(Ident::new("name"))), - negated, - pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: None, - }, - select.selection.unwrap() - ); - - // Test with escape char - let sql = &format!( - "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\'", - if negated { "NOT " } else { "" } - ); - let select = verified_only_select(sql); - assert_eq!( - Expr::SimilarTo { - expr: Box::new(Expr::Identifier(Ident::new("name"))), - negated, - pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: Some('\\'), - }, - select.selection.unwrap() - ); - - // This statement tests that SIMILAR TO and NOT SIMILAR TO have the same precedence. - let sql = &format!( - "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\' IS NULL", - if negated { "NOT " } else { "" } - ); - let select = verified_only_select(sql); - assert_eq!( - Expr::IsNull(Box::new(Expr::SimilarTo { - expr: Box::new(Expr::Identifier(Ident::new("name"))), - negated, - pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), - escape_char: Some('\\'), - })), - select.selection.unwrap() - ); - } - chk(false); - chk(true); -} - #[test] fn parse_in_list() { fn chk(negated: bool) { @@ -3453,59 +3344,6 @@ fn parse_unnest() { ); } -#[test] -fn parse_delimited_identifiers() { - // check that quoted identifiers in any position remain quoted after serialization - let select = verified_only_select( - r#"SELECT "alias"."bar baz", "myfun"(), "simple id" AS "column alias" FROM "a table" AS "alias""#, - ); - // check FROM - match only(select.from).relation { - TableFactor::Table { - name, - alias, - args, - with_hints, - } => { - assert_eq!(vec![Ident::with_quote('"', "a table")], name.0); - assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name); - assert!(args.is_none()); - assert!(with_hints.is_empty()); - } - _ => panic!("Expecting TableFactor::Table"), - } - // check SELECT - assert_eq!(3, select.projection.len()); - assert_eq!( - &Expr::CompoundIdentifier(vec![ - Ident::with_quote('"', "alias"), - Ident::with_quote('"', "bar baz"), - ]), - expr_from_projection(&select.projection[0]), - ); - assert_eq!( - &Expr::Function(Function { - name: ObjectName(vec![Ident::with_quote('"', "myfun")]), - args: vec![], - over: None, - distinct: false, - special: false, - }), - expr_from_projection(&select.projection[1]), - ); - match &select.projection[2] { - SelectItem::ExprWithAlias { expr, alias } => { - assert_eq!(&Expr::Identifier(Ident::with_quote('"', "simple id")), expr); - assert_eq!(&Ident::with_quote('"', "column alias"), alias); - } - _ => panic!("Expected ExprWithAlias"), - } - - verified_stmt(r#"CREATE TABLE "foo" ("bar" "int")"#); - verified_stmt(r#"ALTER TABLE foo ADD CONSTRAINT "bar" PRIMARY KEY (baz)"#); - //TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#); -} - #[test] fn parse_parens() { use self::BinaryOperator::*; diff --git a/tests/sqlparser_hive.rs b/tests/sqlparser_hive.rs index c4df6b5d..695f63b5 100644 --- a/tests/sqlparser_hive.rs +++ b/tests/sqlparser_hive.rs @@ -15,7 +15,10 @@ //! Test SQL syntax specific to Hive. The parser based on the generic dialect //! is also tested (on the inputs it can handle). -use sqlparser::ast::{CreateFunctionUsing, Expr, Ident, ObjectName, Statement, UnaryOperator}; +use sqlparser::ast::{ + CreateFunctionUsing, Expr, Function, Ident, ObjectName, SelectItem, Statement, TableFactor, + UnaryOperator, Value, +}; use sqlparser::dialect::{GenericDialect, HiveDialect}; use sqlparser::parser::ParserError; use sqlparser::test_utils::*; @@ -300,6 +303,168 @@ fn filter_as_alias() { println!("{}", hive().one_statement_parses_to(sql, expected)); } +#[test] +fn parse_delimited_identifiers() { + // check that quoted identifiers in any position remain quoted after serialization + let select = hive().verified_only_select( + r#"SELECT "alias"."bar baz", "myfun"(), "simple id" AS "column alias" FROM "a table" AS "alias""#, + ); + // check FROM + match only(select.from).relation { + TableFactor::Table { + name, + alias, + args, + with_hints, + } => { + assert_eq!(vec![Ident::with_quote('"', "a table")], name.0); + assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name); + assert!(args.is_none()); + assert!(with_hints.is_empty()); + } + _ => panic!("Expecting TableFactor::Table"), + } + // check SELECT + assert_eq!(3, select.projection.len()); + assert_eq!( + &Expr::CompoundIdentifier(vec![ + Ident::with_quote('"', "alias"), + Ident::with_quote('"', "bar baz"), + ]), + expr_from_projection(&select.projection[0]), + ); + assert_eq!( + &Expr::Function(Function { + name: ObjectName(vec![Ident::with_quote('"', "myfun")]), + args: vec![], + over: None, + distinct: false, + special: false, + }), + expr_from_projection(&select.projection[1]), + ); + match &select.projection[2] { + SelectItem::ExprWithAlias { expr, alias } => { + assert_eq!(&Expr::Identifier(Ident::with_quote('"', "simple id")), expr); + assert_eq!(&Ident::with_quote('"', "column alias"), alias); + } + _ => panic!("Expected ExprWithAlias"), + } + + hive().verified_stmt(r#"CREATE TABLE "foo" ("bar" "int")"#); + hive().verified_stmt(r#"ALTER TABLE foo ADD CONSTRAINT "bar" PRIMARY KEY (baz)"#); + //TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#); +} + +#[test] +fn parse_like() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a'", + if negated { "NOT " } else { "" } + ); + let select = hive().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = hive().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that LIKE and NOT LIKE have the same precedence. + // This was previously mishandled (#81). + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = hive().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + +#[test] +fn parse_similar_to() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a'", + if negated { "NOT " } else { "" } + ); + let select = hive().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = hive().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that SIMILAR TO and NOT SIMILAR TO have the same precedence. + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = hive().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + fn hive() -> TestedDialects { TestedDialects { dialects: vec![Box::new(HiveDialect {})], diff --git a/tests/sqlparser_mssql.rs b/tests/sqlparser_mssql.rs index de2376f9..41b0803e 100644 --- a/tests/sqlparser_mssql.rs +++ b/tests/sqlparser_mssql.rs @@ -140,6 +140,168 @@ fn parse_mssql_create_role() { } } +#[test] +fn parse_delimited_identifiers() { + // check that quoted identifiers in any position remain quoted after serialization + let select = ms_and_generic().verified_only_select( + r#"SELECT "alias"."bar baz", "myfun"(), "simple id" AS "column alias" FROM "a table" AS "alias""#, + ); + // check FROM + match only(select.from).relation { + TableFactor::Table { + name, + alias, + args, + with_hints, + } => { + assert_eq!(vec![Ident::with_quote('"', "a table")], name.0); + assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name); + assert!(args.is_none()); + assert!(with_hints.is_empty()); + } + _ => panic!("Expecting TableFactor::Table"), + } + // check SELECT + assert_eq!(3, select.projection.len()); + assert_eq!( + &Expr::CompoundIdentifier(vec![ + Ident::with_quote('"', "alias"), + Ident::with_quote('"', "bar baz"), + ]), + expr_from_projection(&select.projection[0]), + ); + assert_eq!( + &Expr::Function(Function { + name: ObjectName(vec![Ident::with_quote('"', "myfun")]), + args: vec![], + over: None, + distinct: false, + special: false, + }), + expr_from_projection(&select.projection[1]), + ); + match &select.projection[2] { + SelectItem::ExprWithAlias { expr, alias } => { + assert_eq!(&Expr::Identifier(Ident::with_quote('"', "simple id")), expr); + assert_eq!(&Ident::with_quote('"', "column alias"), alias); + } + _ => panic!("Expected ExprWithAlias"), + } + + ms_and_generic().verified_stmt(r#"CREATE TABLE "foo" ("bar" "int")"#); + ms_and_generic().verified_stmt(r#"ALTER TABLE foo ADD CONSTRAINT "bar" PRIMARY KEY (baz)"#); + //TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#); +} + +#[test] +fn parse_like() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a'", + if negated { "NOT " } else { "" } + ); + let select = ms_and_generic().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = ms_and_generic().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that LIKE and NOT LIKE have the same precedence. + // This was previously mishandled (#81). + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = ms_and_generic().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + +#[test] +fn parse_similar_to() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a'", + if negated { "NOT " } else { "" } + ); + let select = ms_and_generic().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = ms_and_generic().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that SIMILAR TO and NOT SIMILAR TO have the same precedence. + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = ms_and_generic().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + fn ms() -> TestedDialects { TestedDialects { dialects: vec![Box::new(MsSqlDialect {})], diff --git a/tests/sqlparser_postgres.rs b/tests/sqlparser_postgres.rs index c19b264a..d53e1f57 100644 --- a/tests/sqlparser_postgres.rs +++ b/tests/sqlparser_postgres.rs @@ -1966,3 +1966,165 @@ fn parse_create_role() { } } } + +#[test] +fn parse_delimited_identifiers() { + // check that quoted identifiers in any position remain quoted after serialization + let select = pg().verified_only_select( + r#"SELECT "alias"."bar baz", "myfun"(), "simple id" AS "column alias" FROM "a table" AS "alias""#, + ); + // check FROM + match only(select.from).relation { + TableFactor::Table { + name, + alias, + args, + with_hints, + } => { + assert_eq!(vec![Ident::with_quote('"', "a table")], name.0); + assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name); + assert!(args.is_none()); + assert!(with_hints.is_empty()); + } + _ => panic!("Expecting TableFactor::Table"), + } + // check SELECT + assert_eq!(3, select.projection.len()); + assert_eq!( + &Expr::CompoundIdentifier(vec![ + Ident::with_quote('"', "alias"), + Ident::with_quote('"', "bar baz"), + ]), + expr_from_projection(&select.projection[0]), + ); + assert_eq!( + &Expr::Function(Function { + name: ObjectName(vec![Ident::with_quote('"', "myfun")]), + args: vec![], + over: None, + distinct: false, + special: false, + }), + expr_from_projection(&select.projection[1]), + ); + match &select.projection[2] { + SelectItem::ExprWithAlias { expr, alias } => { + assert_eq!(&Expr::Identifier(Ident::with_quote('"', "simple id")), expr); + assert_eq!(&Ident::with_quote('"', "column alias"), alias); + } + _ => panic!("Expected ExprWithAlias"), + } + + pg().verified_stmt(r#"CREATE TABLE "foo" ("bar" "int")"#); + pg().verified_stmt(r#"ALTER TABLE foo ADD CONSTRAINT "bar" PRIMARY KEY (baz)"#); + //TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#); +} + +#[test] +fn parse_like() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a'", + if negated { "NOT " } else { "" } + ); + let select = pg().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = pg().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that LIKE and NOT LIKE have the same precedence. + // This was previously mishandled (#81). + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = pg().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + +#[test] +fn parse_similar_to() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a'", + if negated { "NOT " } else { "" } + ); + let select = pg().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = pg().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that SIMILAR TO and NOT SIMILAR TO have the same precedence. + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = pg().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} diff --git a/tests/sqlparser_redshift.rs b/tests/sqlparser_redshift.rs index 6f77cf33..7597ee98 100644 --- a/tests/sqlparser_redshift.rs +++ b/tests/sqlparser_redshift.rs @@ -95,6 +95,168 @@ fn test_double_quotes_over_db_schema_table_name() { ); } +#[test] +fn parse_delimited_identifiers() { + // check that quoted identifiers in any position remain quoted after serialization + let select = redshift().verified_only_select( + r#"SELECT "alias"."bar baz", "myfun"(), "simple id" AS "column alias" FROM "a table" AS "alias""#, + ); + // check FROM + match only(select.from).relation { + TableFactor::Table { + name, + alias, + args, + with_hints, + } => { + assert_eq!(vec![Ident::with_quote('"', "a table")], name.0); + assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name); + assert!(args.is_none()); + assert!(with_hints.is_empty()); + } + _ => panic!("Expecting TableFactor::Table"), + } + // check SELECT + assert_eq!(3, select.projection.len()); + assert_eq!( + &Expr::CompoundIdentifier(vec![ + Ident::with_quote('"', "alias"), + Ident::with_quote('"', "bar baz"), + ]), + expr_from_projection(&select.projection[0]), + ); + assert_eq!( + &Expr::Function(Function { + name: ObjectName(vec![Ident::with_quote('"', "myfun")]), + args: vec![], + over: None, + distinct: false, + special: false, + }), + expr_from_projection(&select.projection[1]), + ); + match &select.projection[2] { + SelectItem::ExprWithAlias { expr, alias } => { + assert_eq!(&Expr::Identifier(Ident::with_quote('"', "simple id")), expr); + assert_eq!(&Ident::with_quote('"', "column alias"), alias); + } + _ => panic!("Expected ExprWithAlias"), + } + + redshift().verified_stmt(r#"CREATE TABLE "foo" ("bar" "int")"#); + redshift().verified_stmt(r#"ALTER TABLE foo ADD CONSTRAINT "bar" PRIMARY KEY (baz)"#); + //TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#); +} + +#[test] +fn parse_like() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a'", + if negated { "NOT " } else { "" } + ); + let select = redshift().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = redshift().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that LIKE and NOT LIKE have the same precedence. + // This was previously mishandled (#81). + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = redshift().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + +#[test] +fn parse_similar_to() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a'", + if negated { "NOT " } else { "" } + ); + let select = redshift().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = redshift().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that SIMILAR TO and NOT SIMILAR TO have the same precedence. + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = redshift().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + fn redshift() -> TestedDialects { TestedDialects { dialects: vec![Box::new(RedshiftSqlDialect {})], diff --git a/tests/sqlparser_snowflake.rs b/tests/sqlparser_snowflake.rs index b182becb..2a53b084 100644 --- a/tests/sqlparser_snowflake.rs +++ b/tests/sqlparser_snowflake.rs @@ -172,6 +172,168 @@ fn parse_json_using_colon() { snowflake().one_statement_parses_to("SELECT a:b::int FROM t", "SELECT CAST(a:b AS INT) FROM t"); } +#[test] +fn parse_delimited_identifiers() { + // check that quoted identifiers in any position remain quoted after serialization + let select = snowflake().verified_only_select( + r#"SELECT "alias"."bar baz", "myfun"(), "simple id" AS "column alias" FROM "a table" AS "alias""#, + ); + // check FROM + match only(select.from).relation { + TableFactor::Table { + name, + alias, + args, + with_hints, + } => { + assert_eq!(vec![Ident::with_quote('"', "a table")], name.0); + assert_eq!(Ident::with_quote('"', "alias"), alias.unwrap().name); + assert!(args.is_none()); + assert!(with_hints.is_empty()); + } + _ => panic!("Expecting TableFactor::Table"), + } + // check SELECT + assert_eq!(3, select.projection.len()); + assert_eq!( + &Expr::CompoundIdentifier(vec![ + Ident::with_quote('"', "alias"), + Ident::with_quote('"', "bar baz"), + ]), + expr_from_projection(&select.projection[0]), + ); + assert_eq!( + &Expr::Function(Function { + name: ObjectName(vec![Ident::with_quote('"', "myfun")]), + args: vec![], + over: None, + distinct: false, + special: false, + }), + expr_from_projection(&select.projection[1]), + ); + match &select.projection[2] { + SelectItem::ExprWithAlias { expr, alias } => { + assert_eq!(&Expr::Identifier(Ident::with_quote('"', "simple id")), expr); + assert_eq!(&Ident::with_quote('"', "column alias"), alias); + } + _ => panic!("Expected ExprWithAlias"), + } + + snowflake().verified_stmt(r#"CREATE TABLE "foo" ("bar" "int")"#); + snowflake().verified_stmt(r#"ALTER TABLE foo ADD CONSTRAINT "bar" PRIMARY KEY (baz)"#); + //TODO verified_stmt(r#"UPDATE foo SET "bar" = 5"#); +} + +#[test] +fn parse_like() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a'", + if negated { "NOT " } else { "" } + ); + let select = snowflake().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = snowflake().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that LIKE and NOT LIKE have the same precedence. + // This was previously mishandled (#81). + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = snowflake().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + +#[test] +fn parse_similar_to() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a'", + if negated { "NOT " } else { "" } + ); + let select = snowflake().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = snowflake().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that SIMILAR TO and NOT SIMILAR TO have the same precedence. + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = snowflake().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + fn snowflake() -> TestedDialects { TestedDialects { dialects: vec![Box::new(SnowflakeDialect {})], diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index 3f60e16d..8fc3a8d6 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -133,6 +133,115 @@ fn test_placeholder() { ); } +#[test] +fn parse_like() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a'", + if negated { "NOT " } else { "" } + ); + let select = sqlite().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = sqlite().verified_only_select(sql); + assert_eq!( + Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that LIKE and NOT LIKE have the same precedence. + // This was previously mishandled (#81). + let sql = &format!( + "SELECT * FROM customers WHERE name {}LIKE '%a' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = sqlite().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::Like { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + +#[test] +fn parse_similar_to() { + fn chk(negated: bool) { + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a'", + if negated { "NOT " } else { "" } + ); + let select = sqlite().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: None, + }, + select.selection.unwrap() + ); + + // Test with escape char + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\'", + if negated { "NOT " } else { "" } + ); + let select = sqlite().verified_only_select(sql); + assert_eq!( + Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + }, + select.selection.unwrap() + ); + + // This statement tests that SIMILAR TO and NOT SIMILAR TO have the same precedence. + let sql = &format!( + "SELECT * FROM customers WHERE name {}SIMILAR TO '%a' ESCAPE '\\' IS NULL", + if negated { "NOT " } else { "" } + ); + let select = sqlite().verified_only_select(sql); + assert_eq!( + Expr::IsNull(Box::new(Expr::SimilarTo { + expr: Box::new(Expr::Identifier(Ident::new("name"))), + negated, + pattern: Box::new(Expr::Value(Value::SingleQuotedString("%a".to_string()))), + escape_char: Some('\\'), + })), + select.selection.unwrap() + ); + } + chk(false); + chk(true); +} + fn sqlite() -> TestedDialects { TestedDialects { dialects: vec![Box::new(SQLiteDialect {})],