diff --git a/src/parser/parser_expressions.cpp b/src/parser/parser_expressions.cpp index 0150e6e..1ef154c 100644 --- a/src/parser/parser_expressions.cpp +++ b/src/parser/parser_expressions.cpp @@ -694,7 +694,16 @@ ast::ASTNode* Parser::parse_expression(int min_precedence) { if (op_keyword_id == db25::Keyword::IN) { // Check if next is a subquery or a list ast::ASTNode* in_operand = nullptr; - + + // IN must be followed by a parenthesised list or subquery. Anything + // else (e.g. `a IN 5`) is malformed; error rather than silently + // dropping the IN and degrading the predicate to its bare left + // operand (a silent wrong result - the filter would vanish). + if (!current_token_ || current_token_->value != "(") { + error("expected '(' with a value list or subquery after IN"); + return nullptr; + } + // Look for opening paren if (current_token_ && current_token_->value == "(") { // Peek ahead to see if it's a SELECT diff --git a/tests/test_parser_expr_hardening.cpp b/tests/test_parser_expr_hardening.cpp index dcd1356..0301ae5 100644 --- a/tests/test_parser_expr_hardening.cpp +++ b/tests/test_parser_expr_hardening.cpp @@ -673,3 +673,39 @@ TEST_F(ExprHardeningTest, SingleParenIsGroupingNotRow) { EXPECT_EQ(lhs->primary_text, "+"); EXPECT_EQ(find(ast, NodeType::RowConstructor), nullptr) << "no comma -> no row"; } + +// ---- IN requires a parenthesised list or subquery ------------------------- + +TEST_F(ExprHardeningTest, InWithoutParensIsRejected) { + // `a IN 5` (no parens) is malformed; it must error rather than silently + // dropping the IN and leaving the predicate as the bare column `a`. + EXPECT_EQ(parse("SELECT a FROM t WHERE a IN 5"), nullptr); + EXPECT_EQ(parse("SELECT a FROM t WHERE a NOT IN 5"), nullptr); +} + +TEST_F(ExprHardeningTest, InListAndSubqueryStillParse) { + // Regression guard: the valid parenthesised forms are unaffected. + { + auto* ast = parse("SELECT a FROM t WHERE a IN (1, 2, 3)"); + ASSERT_NE(ast, nullptr); + auto* pred = where_predicate(ast); + ASSERT_NE(pred, nullptr); + EXPECT_EQ(pred->node_type, NodeType::InExpr); + EXPECT_EQ(pred->primary_text, "IN"); + } + { + auto* ast = parse("SELECT a FROM t WHERE a IN (SELECT id FROM u)"); + ASSERT_NE(ast, nullptr); + auto* pred = where_predicate(ast); + ASSERT_NE(pred, nullptr); + EXPECT_EQ(pred->node_type, NodeType::InExpr); + } + { + auto* ast = parse("SELECT a FROM t WHERE a NOT IN (1, 2)"); + ASSERT_NE(ast, nullptr); + auto* pred = where_predicate(ast); + ASSERT_NE(pred, nullptr); + EXPECT_EQ(pred->node_type, NodeType::InExpr); + EXPECT_EQ(pred->primary_text, "NOT IN"); + } +}