From 4cdaa40abe2caa17375245838b63f1295ed1fca0 Mon Sep 17 00:00:00 2001 From: Andrew Lamb Date: Sun, 29 Oct 2023 07:31:08 -0400 Subject: [PATCH] Support `IN ()` syntax of SQLite, alternate proposal (#1028) --- src/dialect/mod.rs | 4 ++++ src/dialect/sqlite.rs | 4 ++++ src/parser/mod.rs | 27 ++++++++++++++++++++++++++- tests/sqlparser_sqlite.rs | 34 ++++++++++++++++++++++++++++++++++ 4 files changed, 68 insertions(+), 1 deletion(-) diff --git a/src/dialect/mod.rs b/src/dialect/mod.rs index 856cfe1c9..accf61efc 100644 --- a/src/dialect/mod.rs +++ b/src/dialect/mod.rs @@ -124,6 +124,10 @@ pub trait Dialect: Debug + Any { fn supports_substring_from_for_expr(&self) -> bool { true } + /// Returns true if the dialect supports `(NOT) IN ()` expressions + fn supports_in_empty_list(&self) -> bool { + false + } /// Dialect-specific prefix parser override fn parse_prefix(&self, _parser: &mut Parser) -> Option> { // return None to fall back to the default behavior diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs index 68515d24f..c9e9ab185 100644 --- a/src/dialect/sqlite.rs +++ b/src/dialect/sqlite.rs @@ -52,4 +52,8 @@ impl Dialect for SQLiteDialect { None } } + + fn supports_in_empty_list(&self) -> bool { + true + } } diff --git a/src/parser/mod.rs b/src/parser/mod.rs index c3976c742..1964437eb 100644 --- a/src/parser/mod.rs +++ b/src/parser/mod.rs @@ -2344,7 +2344,11 @@ impl<'a> Parser<'a> { } else { Expr::InList { expr: Box::new(expr), - list: self.parse_comma_separated(Parser::parse_expr)?, + list: if self.dialect.supports_in_empty_list() { + self.parse_comma_separated0(Parser::parse_expr)? + } else { + self.parse_comma_separated(Parser::parse_expr)? + }, negated, } }; @@ -2710,6 +2714,27 @@ impl<'a> Parser<'a> { Ok(values) } + /// Parse a comma-separated list of 0+ items accepted by `F` + pub fn parse_comma_separated0(&mut self, f: F) -> Result, ParserError> + where + F: FnMut(&mut Parser<'a>) -> Result, + { + // () + if matches!(self.peek_token().token, Token::RParen) { + return Ok(vec![]); + } + // (,) + if self.options.trailing_commas + && matches!(self.peek_nth_token(0).token, Token::Comma) + && matches!(self.peek_nth_token(1).token, Token::RParen) + { + let _ = self.consume_token(&Token::Comma); + return Ok(vec![]); + } + + self.parse_comma_separated(f) + } + /// Run a parser method `f`, reverting back to the current position /// if unsuccessful. #[must_use] diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index 4935f1f50..7cfe9422a 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -22,6 +22,7 @@ use test_utils::*; use sqlparser::ast::SelectItem::UnnamedExpr; use sqlparser::ast::*; use sqlparser::dialect::{GenericDialect, SQLiteDialect}; +use sqlparser::parser::ParserOptions; use sqlparser::tokenizer::Token; #[test] @@ -392,6 +393,32 @@ fn parse_attach_database() { } } +#[test] +fn parse_where_in_empty_list() { + let sql = "SELECT * FROM t1 WHERE a IN ()"; + let select = sqlite().verified_only_select(sql); + if let Expr::InList { list, .. } = select.selection.as_ref().unwrap() { + assert_eq!(list.len(), 0); + } else { + unreachable!() + } + + sqlite_with_options(ParserOptions::new().with_trailing_commas(true)).one_statement_parses_to( + "SELECT * FROM t1 WHERE a IN (,)", + "SELECT * FROM t1 WHERE a IN ()", + ); +} + +#[test] +fn invalid_empty_list() { + let sql = "SELECT * FROM t1 WHERE a IN (,,)"; + let sqlite = sqlite_with_options(ParserOptions::new().with_trailing_commas(true)); + assert_eq!( + "sql parser error: Expected an expression:, found: ,", + sqlite.parse_sql_statements(sql).unwrap_err().to_string() + ); +} + fn sqlite() -> TestedDialects { TestedDialects { dialects: vec![Box::new(SQLiteDialect {})], @@ -399,6 +426,13 @@ fn sqlite() -> TestedDialects { } } +fn sqlite_with_options(options: ParserOptions) -> TestedDialects { + TestedDialects { + dialects: vec![Box::new(SQLiteDialect {})], + options: Some(options), + } +} + fn sqlite_and_generic() -> TestedDialects { TestedDialects { // we don't have a separate SQLite dialect, so test only the generic dialect for now