From 154d819f06a1fa91d947e9bbd16d9329ab385883 Mon Sep 17 00:00:00 2001 From: fenfeng9 Date: Thu, 12 Feb 2026 04:09:57 +0800 Subject: [PATCH 1/3] fix: correct OR null handling for BlockList|BlockList --- rust/lance-core/src/utils/mask/nullable.rs | 23 +++++++++++++++++----- rust/lance/tests/query/primitives.rs | 1 + 2 files changed, 19 insertions(+), 5 deletions(-) diff --git a/rust/lance-core/src/utils/mask/nullable.rs b/rust/lance-core/src/utils/mask/nullable.rs index 3575731e017..81615ba64b0 100644 --- a/rust/lance-core/src/utils/mask/nullable.rs +++ b/rust/lance-core/src/utils/mask/nullable.rs @@ -251,15 +251,17 @@ impl std::ops::BitOr for NullableRowAddrMask { Self::BlockList(NullableRowAddrSet { selected, nulls }) } (Self::BlockList(a), Self::BlockList(b)) => { + let a_false = a.selected.clone() - &a.nulls; + let b_false = b.selected.clone() - &b.nulls; let nulls = if a.nulls.is_empty() && b.nulls.is_empty() { RowAddrTreeMap::new() // Fast path } else { - // null or null -> null (excluding rows that are true in either) - let false_rows = - (a.selected.clone() - &a.nulls) & (b.selected.clone() - &b.nulls); - (a.nulls | &b.nulls) - false_rows + // NULL if: (A NULL & B FALSE) or (A FALSE & B NULL) or (A NULL & B NULL). + (a.nulls.clone() & &b_false) + | (b.nulls.clone() & &a_false) + | (a.nulls & &b.nulls) }; - let selected = (a.selected & b.selected) | &nulls; + let selected = (a_false & b_false) | &nulls; Self::BlockList(NullableRowAddrSet { selected, nulls }) } } @@ -390,6 +392,17 @@ mod tests { assert_mask_selects(&result, &[2], &[0, 1]); } + #[test] + fn test_or_block_block_true_overrides_null() { + // TRUE OR NULL should be TRUE, even when both sides are BlockList. + let true_mask = block(&[], &[]); + let null_mask = block(&[], &[0]); + let result = true_mask | null_mask; + + // Row 0 should be TRUE. + assert_mask_selects(&result, &[0], &[]); + } + #[test] fn test_row_selection_bit_or() { // [T, N, T, N, F, F, F] diff --git a/rust/lance/tests/query/primitives.rs b/rust/lance/tests/query/primitives.rs index a6173bf83b7..a3789623d9f 100644 --- a/rust/lance/tests/query/primitives.rs +++ b/rust/lance/tests/query/primitives.rs @@ -81,6 +81,7 @@ async fn test_query_integer(#[case] data_type: DataType) { test_filter(&original, &ds, "value is not null").await; test_filter(&original, &ds, "(value != 0) OR (value < 20)").await; test_filter(&original, &ds, "NOT ((value != 0) OR (value < 20))").await; + test_filter(&original, &ds, "(c1 != 71) OR ((c1 != 52) OR (c1 IS NULL))").await; }) .await } From 867214f7ae6d3ad1879a8d202a4e686fe1a2dd2a Mon Sep 17 00:00:00 2001 From: fenfeng9 Date: Thu, 12 Feb 2026 04:19:04 +0800 Subject: [PATCH 2/3] update test --- rust/lance/tests/query/primitives.rs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/rust/lance/tests/query/primitives.rs b/rust/lance/tests/query/primitives.rs index a3789623d9f..7d4ce8a889f 100644 --- a/rust/lance/tests/query/primitives.rs +++ b/rust/lance/tests/query/primitives.rs @@ -82,6 +82,12 @@ async fn test_query_integer(#[case] data_type: DataType) { test_filter(&original, &ds, "(value != 0) OR (value < 20)").await; test_filter(&original, &ds, "NOT ((value != 0) OR (value < 20))").await; test_filter(&original, &ds, "(c1 != 71) OR ((c1 != 52) OR (c1 IS NULL))").await; + test_filter( + &original, + &ds, + "NOT ((c1 != 71) OR ((c1 != 52) OR (c1 IS NULL)))", + ) + .await; }) .await } From 64813989899307a44805c96a103aa26c6b86fac6 Mon Sep 17 00:00:00 2001 From: fenfeng9 Date: Thu, 12 Feb 2026 15:49:12 +0800 Subject: [PATCH 3/3] update test --- rust/lance/tests/query/primitives.rs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/rust/lance/tests/query/primitives.rs b/rust/lance/tests/query/primitives.rs index 7d4ce8a889f..c1c70e6a3fe 100644 --- a/rust/lance/tests/query/primitives.rs +++ b/rust/lance/tests/query/primitives.rs @@ -81,11 +81,16 @@ async fn test_query_integer(#[case] data_type: DataType) { test_filter(&original, &ds, "value is not null").await; test_filter(&original, &ds, "(value != 0) OR (value < 20)").await; test_filter(&original, &ds, "NOT ((value != 0) OR (value < 20))").await; - test_filter(&original, &ds, "(c1 != 71) OR ((c1 != 52) OR (c1 IS NULL))").await; test_filter( &original, &ds, - "NOT ((c1 != 71) OR ((c1 != 52) OR (c1 IS NULL)))", + "(value != 5) OR ((value != 52) OR (value IS NULL))", + ) + .await; + test_filter( + &original, + &ds, + "NOT ((value != 5) OR ((value != 52) OR (value IS NULL)))", ) .await; })