From 6f1b1704c82d6711ede140865cc61fddab4f7c35 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Mon, 5 Oct 2026 16:35:24 +0200 Subject: [PATCH 1/3] SQLite: Reject colon-based semi-structured data traversal --- src/dialect/sqlite.rs | 2 ++ tests/sqlparser_sqlite.rs | 19 +++++++++++++++++++ 2 files changed, 21 insertions(+) diff --git a/src/dialect/sqlite.rs b/src/dialect/sqlite.rs index 3deffafb7d..fa841f0ec6 100644 --- a/src/dialect/sqlite.rs +++ b/src/dialect/sqlite.rs @@ -103,6 +103,8 @@ impl Dialect for SQLiteDialect { fn get_next_precedence(&self, parser: &Parser) -> Option> { match parser.peek_token_ref().token { Token::Ampersand | Token::Pipe => Some(Ok(self.prec_value(Precedence::Ampersand))), + // lowest prec to prevent it from turning into a binary op + Token::Colon => Some(Ok(self.prec_unknown())), _ => None, } } diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index eaadea263e..531c9f04be 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -1225,6 +1225,25 @@ fn parse_over_as_column_alias_after_function() { ); } +#[test] +fn parse_colon_does_not_start_json_access() { + // SQLite rejects `SELECT c:se` (`near ":se": syntax error`); no `:` traversal. + assert!(sqlite().parse_sql_statements("SELECT c:se").is_err()); + assert!(sqlite().parse_sql_statements("SELECT a:b FROM t").is_err()); + + let ast = sqlite().verified_only_select("SELECT :xxx"); + assert_eq!( + ast.projection[0], + UnnamedExpr(Expr::Value( + (Value::Placeholder(":xxx".into())).with_empty_span() + )), + ); + + assert!(TestedDialects::new(vec![Box::new(GenericDialect {})]) + .parse_sql_statements("SELECT a:b FROM t") + .is_ok()); +} + fn sqlite() -> TestedDialects { TestedDialects::new(vec![Box::new(SQLiteDialect {})]) } From deb755afaca240b71f9a7bf916e9deeb7798ba04 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Fri, 16 Oct 2026 23:29:28 +0200 Subject: [PATCH 2/3] Assert exact errors for the new deny lints --- tests/sqlparser_sqlite.rs | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index 531c9f04be..d8e2c4db33 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -1228,8 +1228,14 @@ fn parse_over_as_column_alias_after_function() { #[test] fn parse_colon_does_not_start_json_access() { // SQLite rejects `SELECT c:se` (`near ":se": syntax error`); no `:` traversal. - assert!(sqlite().parse_sql_statements("SELECT c:se").is_err()); - assert!(sqlite().parse_sql_statements("SELECT a:b FROM t").is_err()); + for sql in ["SELECT c:se", "SELECT a:b FROM t"] { + assert_eq!( + sqlite().parse_sql_statements(sql), + Err(ParserError::ParserError( + "Expected: end of statement, found: :".to_string() + )) + ); + } let ast = sqlite().verified_only_select("SELECT :xxx"); assert_eq!( @@ -1239,9 +1245,9 @@ fn parse_colon_does_not_start_json_access() { )), ); - assert!(TestedDialects::new(vec![Box::new(GenericDialect {})]) + TestedDialects::new(vec![Box::new(GenericDialect {})]) .parse_sql_statements("SELECT a:b FROM t") - .is_ok()); + .unwrap(); } fn sqlite() -> TestedDialects { From 9813bd0f026bd53c64398f052e828f377ad1c8d2 Mon Sep 17 00:00:00 2001 From: LucaCappelletti94 Date: Sat, 24 Oct 2026 00:26:26 +0200 Subject: [PATCH 3/3] Place regression tests beside related syntax cases --- tests/sqlparser_sqlite.rs | 50 +++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/tests/sqlparser_sqlite.rs b/tests/sqlparser_sqlite.rs index d8e2c4db33..1bc51ec89e 100644 --- a/tests/sqlparser_sqlite.rs +++ b/tests/sqlparser_sqlite.rs @@ -390,6 +390,31 @@ fn test_placeholder() { ); } +#[test] +fn parse_colon_does_not_start_json_access() { + // SQLite rejects `SELECT c:se` (`near ":se": syntax error`); no `:` traversal. + for sql in ["SELECT c:se", "SELECT a:b FROM t"] { + assert_eq!( + sqlite().parse_sql_statements(sql), + Err(ParserError::ParserError( + "Expected: end of statement, found: :".to_string() + )) + ); + } + + let ast = sqlite().verified_only_select("SELECT :xxx"); + assert_eq!( + ast.projection[0], + UnnamedExpr(Expr::Value( + (Value::Placeholder(":xxx".into())).with_empty_span() + )), + ); + + TestedDialects::new(vec![Box::new(GenericDialect {})]) + .parse_sql_statements("SELECT a:b FROM t") + .unwrap(); +} + #[test] fn parse_create_table_with_strict() { let sql = "CREATE TABLE Fruits (id TEXT NOT NULL PRIMARY KEY) STRICT"; @@ -1225,31 +1250,6 @@ fn parse_over_as_column_alias_after_function() { ); } -#[test] -fn parse_colon_does_not_start_json_access() { - // SQLite rejects `SELECT c:se` (`near ":se": syntax error`); no `:` traversal. - for sql in ["SELECT c:se", "SELECT a:b FROM t"] { - assert_eq!( - sqlite().parse_sql_statements(sql), - Err(ParserError::ParserError( - "Expected: end of statement, found: :".to_string() - )) - ); - } - - let ast = sqlite().verified_only_select("SELECT :xxx"); - assert_eq!( - ast.projection[0], - UnnamedExpr(Expr::Value( - (Value::Placeholder(":xxx".into())).with_empty_span() - )), - ); - - TestedDialects::new(vec![Box::new(GenericDialect {})]) - .parse_sql_statements("SELECT a:b FROM t") - .unwrap(); -} - fn sqlite() -> TestedDialects { TestedDialects::new(vec![Box::new(SQLiteDialect {})]) }