Skip to content

Commit

Permalink
Support IN () syntax of SQLite, alternate proposal (apache#1028)
Browse files Browse the repository at this point in the history
  • Loading branch information
alamb authored Oct 29, 2023
1 parent 2f0c99c commit 4cdaa40
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 1 deletion.
4 changes: 4 additions & 0 deletions src/dialect/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<Result<Expr, ParserError>> {
// return None to fall back to the default behavior
Expand Down
4 changes: 4 additions & 0 deletions src/dialect/sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -52,4 +52,8 @@ impl Dialect for SQLiteDialect {
None
}
}

fn supports_in_empty_list(&self) -> bool {
true
}
}
27 changes: 26 additions & 1 deletion src/parser/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
}
};
Expand Down Expand Up @@ -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<T, F>(&mut self, f: F) -> Result<Vec<T>, ParserError>
where
F: FnMut(&mut Parser<'a>) -> Result<T, ParserError>,
{
// ()
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]
Expand Down
34 changes: 34 additions & 0 deletions tests/sqlparser_sqlite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down Expand Up @@ -392,13 +393,46 @@ 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 {})],
options: None,
}
}

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
Expand Down

0 comments on commit 4cdaa40

Please sign in to comment.