Skip to content

Commit 7ceb086

Browse files
Beauuu-0826claude
andcommitted
[BugFix] Keep non-null rows for negated empty-literal MATCH in inverted-index seek
seek_inverted_index cleared the row bitmap whenever the match literal was empty (read_inverted_index returned nullopt), regardless of negation. That dropped every row for NOT (col MATCH ''), even though MATCH '' matches nothing and its negation is TRUE for all non-null rows. This surfaced once the OR-MATCH fallback started reusing seek_inverted_index. Substitute an empty posting set for the nullopt case and reuse the shared with_not / intersect path, so col MATCH '' keeps no rows while NOT (col MATCH '') keeps every non-null row (only NULLs stay out). Fixes the same latent bug on the AND filtering path. Add SQL regression for both the AND and OR-MATCH fallback paths. Signed-off-by: eason <1569163626@qq.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 28067c6 commit 7ceb086

3 files changed

Lines changed: 23 additions & 3 deletions

File tree

be/src/storage_primitive/column_expr_predicate.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -390,10 +390,11 @@ Status ColumnExprPredicate::seek_inverted_index(const std::string& column_name,
390390

391391
ASSIGN_OR_RETURN(auto roaring_opt, read_inverted_index(column_name, iterator));
392392

393-
// nullopt means empty match string - clear bitmap
393+
// nullopt means an empty match string, which matches no rows. Fall through
394+
// with an empty posting set so the shared logic below produces the right
395+
// result
394396
if (!roaring_opt.has_value()) {
395-
*row_bitmap -= *row_bitmap;
396-
return Status::OK();
397+
roaring_opt.emplace();
397398
}
398399

399400
if (with_not) {

test/sql/test_inverted_index/R/test_inverted_index

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,20 @@ SELECT * FROM t_gin_index_multiple_predicate_none WHERE text_column NOT match_an
778778
4 This is Gin Index
779779
5 None
780780
-- !result
781+
SELECT * FROM t_gin_index_multiple_predicate_none WHERE text_column NOT match_any "";
782+
-- result:
783+
1 ABC
784+
2 abc
785+
3 ABD
786+
4 This is Gin Index
787+
-- !result
788+
SELECT * FROM t_gin_index_multiple_predicate_none WHERE text_column NOT match_any "" OR id1 = 100;
789+
-- result:
790+
1 ABC
791+
2 abc
792+
3 ABD
793+
4 This is Gin Index
794+
-- !result
781795
DROP TABLE t_gin_index_multiple_predicate_none;
782796
-- result:
783797
-- !result

test/sql/test_inverted_index/T/test_inverted_index

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -320,6 +320,11 @@ SELECT * FROM t_gin_index_multiple_predicate_none WHERE text_column NOT match_an
320320
SELECT * FROM t_gin_index_multiple_predicate_none WHERE id1 = 100 OR text_column NOT match_any "ABC";
321321
-- A sibling OR arm may still legitimately select the NULL row.
322322
SELECT * FROM t_gin_index_multiple_predicate_none WHERE text_column NOT match_any "ABC" OR id1 = 5;
323+
-- Empty match literal: match_any '' matches nothing, so NOT is TRUE for every
324+
-- non-null row. Both the AND path and the OR-MATCH fallback must keep those
325+
-- rows (only the NULL row stays out), not drop them.
326+
SELECT * FROM t_gin_index_multiple_predicate_none WHERE text_column NOT match_any "";
327+
SELECT * FROM t_gin_index_multiple_predicate_none WHERE text_column NOT match_any "" OR id1 = 100;
323328

324329
DROP TABLE t_gin_index_multiple_predicate_none;
325330

0 commit comments

Comments
 (0)