Skip to content

Commit e33634f

Browse files
committed
[fix](be) Preserve nested incomplete scoring failures
### What problem does this PR solve? Issue Number: None Related PR: #69160 Problem Summary: A nested scored compound discards its temporary scores before reporting unsupported evaluation. An outer AND or OR could swallow that error when its other child contributes no scores, allowing zero-score residual results. Preserve the error through scored parents independently of map contents. ### Release note Nested incomplete scored predicates report an error instead of silently returning zero scores. ### Check List (For Author) - Test: 31 targeted ASAN unit tests, ASAN BE build, and Boolean SQL regression including 16 new expected-error combinations passed. The new SQL case failed on the prior binary. - Behavior changed: Yes. Preserve incomplete-scoring errors through nested compounds. - Does this need documentation: No.
1 parent b13bdbc commit e33634f

3 files changed

Lines changed: 90 additions & 0 deletions

File tree

‎be/src/exprs/vcompound_pred.h‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -291,6 +291,12 @@ class VCompoundPred : public VectorizedFnCall {
291291
!st.ok()) {
292292
LOG(ERROR) << "expr:" << child->expr_name()
293293
<< " evaluate_inverted_index error:" << st.to_string();
294+
// A scored child may have discarded its map before reporting this error.
295+
// Preserve the failure even when no sibling contributes scores.
296+
if (branch_similarity != nullptr &&
297+
st.code() == ErrorCode::INVERTED_INDEX_NOT_SUPPORTED) {
298+
return st;
299+
}
294300
all_pass = false;
295301
continue;
296302
}
@@ -320,6 +326,12 @@ class VCompoundPred : public VectorizedFnCall {
320326
!st.ok()) {
321327
LOG(ERROR) << "expr:" << child->expr_name()
322328
<< " evaluate_inverted_index error:" << st.to_string();
329+
// A scored child may have discarded its map before reporting this error.
330+
// Preserve the failure even when no sibling contributes scores.
331+
if (branch_similarity != nullptr &&
332+
st.code() == ErrorCode::INVERTED_INDEX_NOT_SUPPORTED) {
333+
return st;
334+
}
323335
all_pass = false;
324336
continue;
325337
}

‎be/test/exprs/vcompound_pred_score_test.cpp‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,5 +218,59 @@ TEST(VCompoundPredScoreTest, IncompleteScoredBranchReturnsErrorWithoutPublishing
218218
EXPECT_TRUE(parent_similarity->release_scores().empty());
219219
}
220220

221+
TEST(VCompoundPredScoreTest, NestedIncompleteScoringErrorSurvivesEmptyOrUnindexedSiblings) {
222+
for (auto op : {TExprOpcode::COMPOUND_OR, TExprOpcode::COMPOUND_AND}) {
223+
for (bool unindexed_sibling : {false, true}) {
224+
for (bool reverse_children : {false, true}) {
225+
SCOPED_TRACE(::testing::Message()
226+
<< "op=" << op << " unindexed=" << unindexed_sibling
227+
<< " reversed=" << reverse_children);
228+
auto incomplete = make_compound(TExprOpcode::COMPOUND_AND,
229+
std::make_shared<ScoredBitmapExpr>(
230+
std::initializer_list<uint32_t> {1}, 1.0F),
231+
std::make_shared<UnindexedExpr>());
232+
VExprSPtr sibling;
233+
if (unindexed_sibling) {
234+
sibling = std::make_shared<UnindexedExpr>();
235+
} else if (op == TExprOpcode::COMPOUND_AND) {
236+
// An empty AND may short-circuit before the incomplete child is reached.
237+
sibling = std::make_shared<ScoredBitmapExpr>(
238+
std::initializer_list<uint32_t> {1}, 0.0F);
239+
} else {
240+
sibling = std::make_shared<ScoredBitmapExpr>(std::initializer_list<uint32_t> {},
241+
0.0F);
242+
}
243+
auto nested = reverse_children ? make_compound(op, sibling, incomplete)
244+
: make_compound(op, incomplete, sibling);
245+
// Exercise propagation across multiple compound levels, including NOT.
246+
auto root = make_not(make_not(nested));
247+
248+
std::vector<ColumnId> col_ids;
249+
std::vector<std::unique_ptr<segment_v2::IndexIterator>> iterators;
250+
std::vector<IndexFieldNameAndTypePair> storage_types;
251+
std::unordered_map<ColumnId, std::unordered_map<const VExpr*, bool>> status_map;
252+
auto score_runtime = std::make_shared<ScoreRuntime>(nullptr, false, 0);
253+
auto index_context = std::make_shared<IndexExecContext>(
254+
col_ids, iterators, storage_types, status_map, score_runtime);
255+
auto query_context = std::make_shared<segment_v2::IndexQueryContext>();
256+
auto parent_similarity = std::make_shared<CollectionSimilarity>();
257+
parent_similarity->collect(0, 7.0F);
258+
query_context->collection_similarity = parent_similarity;
259+
index_context->set_index_query_context(query_context);
260+
auto expr_context = VExprContext::create_shared(root);
261+
expr_context->set_index_context(index_context);
262+
263+
auto status = root->evaluate_inverted_index(expr_context.get(), 2);
264+
EXPECT_EQ(status.code(), ErrorCode::INVERTED_INDEX_NOT_SUPPORTED);
265+
EXPECT_EQ(query_context->collection_similarity, parent_similarity);
266+
EXPECT_FALSE(index_context->has_index_result_for_expr(root.get()));
267+
auto scores = parent_similarity->release_scores();
268+
ASSERT_EQ(scores.size(), 1);
269+
EXPECT_FLOAT_EQ(scores.at(0), 7.0F);
270+
}
271+
}
272+
}
273+
}
274+
221275
} // namespace
222276
} // namespace doris

‎regression-test/suites/search/test_search_score_boolean_branches.groovy‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -106,4 +106,28 @@ suite("test_search_score_boolean_branches", "p0") {
106106
"""
107107
exception "Cannot score a compound predicate with incomplete index evaluation"
108108
}
109+
110+
// A failed scored child must remain an error even when its sibling adds no scores.
111+
for (boolean wand : [false, true]) {
112+
sql "set enable_inverted_index_wand_query = ${wand}"
113+
for (String sibling : ["tags MATCH_ANY 'missing_token'", "id < 0"]) {
114+
for (boolean reverseChildren : [false, true]) {
115+
String incomplete = "(title MATCH_ANY 'apple' AND id > 0)"
116+
String predicate = reverseChildren ? "${sibling} OR ${incomplete}"
117+
: "${incomplete} OR ${sibling}"
118+
// Repeat the query to cover bitmap-cache reuse after the error.
119+
for (int attempt = 0; attempt < 2; ++attempt) {
120+
test {
121+
sql """
122+
SELECT id, score() AS bm25_score
123+
FROM test_search_score_multi_column_mixed
124+
WHERE ${predicate}
125+
ORDER BY bm25_score DESC LIMIT 100
126+
"""
127+
exception "Cannot score a compound predicate with incomplete index evaluation"
128+
}
129+
}
130+
}
131+
}
132+
}
109133
}

0 commit comments

Comments
 (0)