Skip to content

Commit 70fd795

Browse files
committed
fix: normalize HybridSmall payload in SmallReader and pass Roaring slice to bitmap_len_above
SmallReader previously used decode_small_payload which returns raw that may contain duplicates or be unsorted. Switch to decode_small_values to deduplicate and sort, matching the deserialize path. Pass the roaring slice correctly to reader::bitmap_len_above. The previous code passed the HybridLarge buffer with its HB header, causing a BadBytes error.
1 parent 2cf4a7e commit 70fd795

1 file changed

Lines changed: 29 additions & 51 deletions

File tree

src/common/io/src/bitmap.rs

Lines changed: 29 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -753,66 +753,43 @@ fn as_roaring(buf: &[u8]) -> Option<&[u8]> {
753753
}
754754
}
755755

756-
struct SmallReader<'a> {
757-
len: usize,
758-
values: &'a [u8],
756+
struct SmallReader {
757+
values: SmallBitmap,
759758
}
760759

761-
impl<'a> SmallReader<'a> {
762-
fn new(buf: &'a [u8]) -> Result<Self> {
760+
impl SmallReader {
761+
// decode_small_payload returns raw bytes which may contain duplicates or be
762+
// unsorted; decode_small_values deduplicates and sorts via small_insert, so
763+
// that binary-search and two-pointer merge agree with deserialize_bitmap.
764+
fn new(buf: &[u8]) -> Result<Self> {
763765
let payload = &buf[HYBRID_HEADER_LEN..];
764-
let (len, bytes) = decode_small_payload(payload)?;
765-
Ok(Self { len, values: bytes })
766+
Ok(Self {
767+
values: decode_small_values(payload)?,
768+
})
766769
}
767770

768771
fn len(&self) -> usize {
769-
self.len
770-
}
771-
772-
fn is_empty(&self) -> bool {
773-
self.len == 0
774-
}
775-
776-
fn get(&self, i: usize) -> u64 {
777-
read_u64_le(&self.values[i * 8..i * 8 + 8])
772+
self.values.len()
778773
}
779774

780775
fn first(&self) -> Option<u64> {
781-
if self.is_empty() {
782-
None
783-
} else {
784-
Some(self.get(0))
785-
}
776+
self.values.first().copied()
786777
}
787778

788779
fn last(&self) -> Option<u64> {
789-
if self.is_empty() {
790-
None
791-
} else {
792-
Some(self.get(self.len - 1))
793-
}
780+
self.values.last().copied()
794781
}
795782

796783
fn contains(&self, value: u64) -> bool {
797-
let mut lo = 0;
798-
let mut hi = self.len;
799-
while lo < hi {
800-
let mid = lo + (hi - lo) / 2;
801-
match self.get(mid).cmp(&value) {
802-
std::cmp::Ordering::Less => lo = mid + 1,
803-
std::cmp::Ordering::Greater => hi = mid,
804-
std::cmp::Ordering::Equal => return true,
805-
}
806-
}
807-
false
784+
self.values.binary_search(&value).is_ok()
808785
}
809786

810787
fn has_any_with(&self, other: &SmallReader) -> bool {
811788
let mut i = 0;
812789
let mut j = 0;
813-
while i < self.len && j < other.len {
814-
let lv = self.get(i);
815-
let rv = other.get(j);
790+
while i < self.len() && j < other.len() {
791+
let lv = self.values[i];
792+
let rv = other.values[j];
816793
if lv < rv {
817794
i += 1;
818795
} else if rv < lv {
@@ -825,17 +802,17 @@ impl<'a> SmallReader<'a> {
825802
}
826803

827804
fn has_all_with(&self, other: &SmallReader) -> bool {
828-
if self.len < other.len {
805+
if self.len() < other.len() {
829806
return false;
830807
}
831808
let mut i = 0;
832809
let mut j = 0;
833-
while j < other.len {
834-
if i >= self.len {
810+
while j < other.len() {
811+
if i >= self.len() {
835812
return false;
836813
}
837-
let lv = self.get(i);
838-
let rv = other.get(j);
814+
let lv = self.values[i];
815+
let rv = other.values[j];
839816
if lv < rv {
840817
i += 1;
841818
} else if lv == rv {
@@ -947,13 +924,14 @@ pub fn bitmap_has_all(lhs: &[u8], rhs: &[u8]) -> Result<bool> {
947924
return Ok(lhs.has_all_with(&rhs));
948925
}
949926

950-
// Fast path: HybridSmall lhs, Legacy rhs cardinality check
951-
// We need to check cardinality here because: (1) it might be a legacy tree and (2) sometimes
952-
// we don't try demote HybridBitmap after reduce its cardinality immediately.
927+
// Fast path: HybridSmall lhs, HybridLarge or Legacy rhs cardinality check
928+
// We need to check cardinality here because: (1) it might be a legacy tree,
929+
// and (2) we don't always demote HybridBitmap after reducing its cardinality immediately.
953930
if as_roaring(lhs).is_none() && as_roaring(rhs).is_some() {
954931
let lhs_small = SmallReader::new(lhs)?;
932+
let rhs_roaring = as_roaring(rhs).unwrap();
955933
// Fast path: rhs has more values than lhs, impossible to contain
956-
if reader::bitmap_len_above(rhs, lhs_small.len())? {
934+
if reader::bitmap_len_above(rhs_roaring, lhs_small.len())? {
957935
return Ok(false);
958936
}
959937
// rhs has <= lhs_small.len() values, but still need to check containment
@@ -966,7 +944,7 @@ pub fn bitmap_has_all(lhs: &[u8], rhs: &[u8]) -> Result<bool> {
966944
let roaring_buf = as_roaring(lhs).unwrap();
967945
let rhs_small = SmallReader::new(rhs)?;
968946
for i in 0..rhs_small.len() {
969-
if !reader::bitmap_contains(roaring_buf, rhs_small.get(i))? {
947+
if !reader::bitmap_contains(roaring_buf, rhs_small.values[i])? {
970948
return Ok(false);
971949
}
972950
}
@@ -1000,7 +978,7 @@ pub fn bitmap_has_any(lhs: &[u8], rhs: &[u8]) -> Result<bool> {
1000978
(as_roaring(rhs).unwrap(), SmallReader::new(lhs)?)
1001979
};
1002980
for i in 0..small.len() {
1003-
if reader::bitmap_contains(roaring_buf, small.get(i))? {
981+
if reader::bitmap_contains(roaring_buf, small.values[i])? {
1004982
return Ok(true);
1005983
}
1006984
}

0 commit comments

Comments
 (0)