From bc95b2ded65b31cd72e792211e472b81902c19ae Mon Sep 17 00:00:00 2001 From: Spade A <71589810+SpadeA-Tang@users.noreply.github.com> Date: Tue, 9 Jun 2026 14:46:20 +0800 Subject: [PATCH] fix: advance MatchExpr child cursor on conjunct short-circuit (#50298) issue: https://github.com/milvus-io/milvus/issues/49755 ref: https://github.com/milvus-io/milvus/issues/42148 Signed-off-by: SpadeA --- internal/core/src/exec/expression/MatchExpr.h | 3 ++ .../src/exec/expression/MatchExprTest.cpp | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+) diff --git a/internal/core/src/exec/expression/MatchExpr.h b/internal/core/src/exec/expression/MatchExpr.h index cf0f46bbbb..54653eed80 100644 --- a/internal/core/src/exec/expression/MatchExpr.h +++ b/internal/core/src/exec/expression/MatchExpr.h @@ -57,6 +57,9 @@ class PhyMatchFilterExpr : public Expr { ? active_count_ - current_pos_ : batch_size_; current_pos_ += real_batch_size; + for (auto& input : inputs_) { + input->MoveCursor(); + } } } diff --git a/internal/core/src/exec/expression/MatchExprTest.cpp b/internal/core/src/exec/expression/MatchExprTest.cpp index 1a8a85aca5..53e21236b0 100644 --- a/internal/core/src/exec/expression/MatchExprTest.cpp +++ b/internal/core/src/exec/expression/MatchExprTest.cpp @@ -1626,6 +1626,34 @@ TEST_F(SealedMatchExprTestNoIndex, MatchWithOtherExpr) { std::cout << "==============================" << std::endl; } +TEST_F(SealedMatchExprTestNoIndex, ConjunctSkipMovesMatchChildCursor) { + std::string target_str = "aaa"; + int32_t target_int = 100; + + // Batch size is 100 in this fixture. The id predicate rejects the first two + // batches completely, so ConjunctExpr short-circuits and skips MatchExpr. + std::string predicate = "$[sub_str] == \"" + target_str + + "\" && $[sub_int] > " + std::to_string(target_int); + std::string filter_expr = + "id > 199 && match_any(struct_array, " + predicate + ")"; + + auto result = ExecuteRetrieve(filter_expr); + + std::set expected_rows; + for (size_t i = 0; i < N_; ++i) { + if (i > 199 && CountMatchingElements(i, target_str, target_int) > 0) { + expected_rows.insert(static_cast(i)); + } + } + + std::set actual_rows; + for (const auto offset : result->offset()) { + actual_rows.insert(offset); + } + + EXPECT_EQ(expected_rows, actual_rows); +} + // ==================== Parameterized Test for Different Int Types ==================== // This tests that int8_t/int16_t/int32_t are correctly handled in ProcessDataChunksForElementLevel