mirror of
https://github.com/milvus-io/milvus.git
synced 2026-07-21 10:15:43 +00:00
fix: preserve filter bitset when MVCC is disabled (#49736)
issue: #49735 - Preserve upstream scalar filter bitsets when `common.visibilityFilterEnabled=false`. - Only mark all rows visible in the disabled visibility-filter path when `MvccNode` has no upstream filter input. - Add C++ and Go regression coverage for preserving predicates/filter bitsets in this path. Signed-off-by: Shawn Wang <shawn.wang@zilliz.com>
This commit is contained in:
@@ -82,7 +82,9 @@ PhyMvccNode::GetOutput() {
|
||||
TargetBitmap(active_count_),
|
||||
TargetBitmap(active_count_))
|
||||
: GetColumnVector(input_);
|
||||
query_context->set_all_rows_visible(true);
|
||||
if (is_source_node_) {
|
||||
query_context->set_all_rows_visible(true);
|
||||
}
|
||||
is_finished_ = true;
|
||||
return std::make_shared<RowVector>(std::vector<VectorPtr>{col_input});
|
||||
}
|
||||
@@ -133,4 +135,4 @@ PhyMvccNode::IsFinished() {
|
||||
}
|
||||
|
||||
} // namespace exec
|
||||
} // namespace milvus
|
||||
} // namespace milvus
|
||||
|
||||
@@ -17,6 +17,8 @@
|
||||
#include "common/Schema.h"
|
||||
#include "exec/QueryContext.h"
|
||||
#include "exec/Task.h"
|
||||
#include "expr/ITypeExpr.h"
|
||||
#include "pb/plan.pb.h"
|
||||
#include "plan/PlanNode.h"
|
||||
#include "segcore/SegcoreConfig.h"
|
||||
#include "segcore/SegmentSealed.h"
|
||||
@@ -368,6 +370,59 @@ TEST_F(MvccFastPathTest, VisibilityFilterDisabled_DeletesIgnored) {
|
||||
<< "visibilityFilterEnabled=false should not mask deleted rows";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// visibilityFilterEnabled=false must not make VectorSearchNode skip an upstream
|
||||
// scalar filter bitset.
|
||||
// ---------------------------------------------------------------------------
|
||||
TEST_F(MvccFastPathTest, VisibilityFilterDisabled_PreservesUpstreamFilter) {
|
||||
VisibilityFilterGuard guard(false);
|
||||
auto segment = CreateSealedSegment();
|
||||
|
||||
proto::plan::GenericValue value;
|
||||
value.set_int64_val(N_ / 2);
|
||||
auto expr = std::make_shared<expr::UnaryRangeFilterExpr>(
|
||||
expr::ColumnInfo(int64_fid_, DataType::INT64),
|
||||
proto::plan::OpType::LessThan,
|
||||
value,
|
||||
std::vector<proto::plan::GenericValue>{});
|
||||
|
||||
auto filter_node = std::make_shared<plan::FilterBitsNode>("filter_1", expr);
|
||||
auto mvcc_node = std::make_shared<plan::MvccNode>(
|
||||
"mvcc_1", std::vector<plan::PlanNodePtr>{filter_node});
|
||||
auto plan = plan::PlanFragment(mvcc_node);
|
||||
|
||||
auto query_context = std::make_shared<QueryContext>(
|
||||
"test_vis_disabled_with_filter",
|
||||
segment.get(),
|
||||
N_,
|
||||
MAX_TIMESTAMP,
|
||||
0,
|
||||
0,
|
||||
query::PlanOptions{false},
|
||||
std::make_shared<QueryConfig>(
|
||||
std::unordered_map<std::string, std::string>{}));
|
||||
|
||||
auto task =
|
||||
Task::Create("task_vis_disabled_with_filter", plan, 0, query_context);
|
||||
RowVectorPtr output;
|
||||
for (;;) {
|
||||
auto current = task->Next();
|
||||
if (!current) {
|
||||
break;
|
||||
}
|
||||
output = current;
|
||||
}
|
||||
|
||||
EXPECT_FALSE(query_context->get_all_rows_visible())
|
||||
<< "upstream scalar filter bitset must still be passed downstream";
|
||||
ASSERT_NE(output, nullptr);
|
||||
auto col = std::dynamic_pointer_cast<ColumnVector>(output->child(0));
|
||||
ASSERT_NE(col, nullptr);
|
||||
TargetBitmapView view(col->GetRawData(), col->size());
|
||||
EXPECT_GT(view.count(), 0)
|
||||
<< "upstream scalar filter should still filter out some rows";
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// visibilityFilterEnabled=false on growing segment: still skips all filtering
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
@@ -623,6 +623,13 @@ func (s *TwoStageSearchSuite) TestTwoStageSearch() {
|
||||
worker1.EXPECT().SearchSegments(mock.Anything, mock.AnythingOfType("*querypb.SearchRequest")).
|
||||
Run(func(_ context.Context, req *querypb.SearchRequest) {
|
||||
callCount++
|
||||
if callCount == 2 {
|
||||
optimizedPlan := &planpb.PlanNode{}
|
||||
err := proto.Unmarshal(req.GetReq().GetSerializedExprPlan(), optimizedPlan)
|
||||
s.Require().NoError(err)
|
||||
s.NotNil(optimizedPlan.GetVectorAnns().GetPredicates(),
|
||||
"second-stage search must keep predicates in the serialized plan")
|
||||
}
|
||||
}).Return(&internalpb.SearchResults{
|
||||
FilterValidCounts: []int64{5000},
|
||||
}, nil)
|
||||
|
||||
Reference in New Issue
Block a user