@@ -28,6 +28,8 @@ use roaring::RoaringTreemap;
2828use roaring:: treemap:: Iter ;
2929use smallvec:: SmallVec ;
3030
31+ pub use crate :: bitmap:: reader:: BitmapStats ;
32+
3133mod reader;
3234
3335// https://github.com/ClickHouse/ClickHouse/blob/516a6ed6f8bd8c5f6eed3a10e9037580b2fb6152/src/AggregateFunctions/AggregateFunctionGroupBitmapData.h#L914
@@ -816,6 +818,102 @@ pub fn bitmap_max(buf: &[u8]) -> Result<Option<u64>> {
816818 }
817819}
818820
821+ pub fn bitmap_stats ( buf : & [ u8 ] ) -> Result < BitmapStats > {
822+ if buf. is_empty ( ) {
823+ return Ok ( BitmapStats {
824+ len : 0 ,
825+ min : None ,
826+ max : None ,
827+ } ) ;
828+ }
829+ if is_hybrid_large ( buf) {
830+ Ok ( reader:: bitmap_stats ( & buf[ HYBRID_HEADER_LEN ..] ) ?)
831+ } else {
832+ let bm = deserialize_bitmap ( buf) ?;
833+ Ok ( BitmapStats {
834+ len : bm. len ( ) as u64 ,
835+ min : bm. min ( ) ,
836+ max : bm. max ( ) ,
837+ } )
838+ }
839+ }
840+
841+ pub fn bitmap_has_any ( lhs : & [ u8 ] , rhs : & [ u8 ] ) -> Result < bool > {
842+ if lhs. is_empty ( ) || rhs. is_empty ( ) {
843+ return Ok ( false ) ;
844+ }
845+
846+ validate_serialized_bitmap_header_and_length ( lhs) ?;
847+ validate_serialized_bitmap_header_and_length ( rhs) ?;
848+
849+ // Stats fast path: range rejection
850+ // If lhs.max < rhs.min OR rhs.max < lhs.min, no overlap possible
851+ if is_hybrid_large ( lhs) && is_hybrid_large ( rhs) {
852+ let lhs_stats = reader:: bitmap_stats ( & lhs[ HYBRID_HEADER_LEN ..] ) ?;
853+ let rhs_stats = reader:: bitmap_stats ( & rhs[ HYBRID_HEADER_LEN ..] ) ?;
854+ if let ( Some ( lhs_min) , Some ( rhs_max) ) = ( lhs_stats. min , rhs_stats. max )
855+ && lhs_min > rhs_max
856+ {
857+ return Ok ( false ) ;
858+ }
859+ if let ( Some ( rhs_min) , Some ( lhs_max) ) = ( rhs_stats. min , lhs_stats. max )
860+ && rhs_min > lhs_max
861+ {
862+ return Ok ( false ) ;
863+ }
864+ Ok ( reader:: bitmap_has_any (
865+ & lhs[ HYBRID_HEADER_LEN ..] ,
866+ & rhs[ HYBRID_HEADER_LEN ..] ,
867+ ) ?)
868+ } else if is_hybrid_large ( lhs) || is_hybrid_large ( rhs) {
869+ // Normalize: large is the HybridLarge side, other is the non-HybridLarge side
870+ let ( large, other) = if is_hybrid_large ( lhs) {
871+ ( lhs, rhs)
872+ } else {
873+ ( rhs, lhs)
874+ } ;
875+
876+ // Stats fast path: range rejection
877+ let large_stats = reader:: bitmap_stats ( & large[ HYBRID_HEADER_LEN ..] ) ?;
878+ let other_stats = bitmap_stats ( other) ?;
879+ if let ( Some ( large_min) , Some ( other_max) ) = ( large_stats. min , other_stats. max )
880+ && large_min > other_max
881+ {
882+ return Ok ( false ) ;
883+ }
884+ if let ( Some ( other_min) , Some ( large_max) ) = ( other_stats. min , large_stats. max )
885+ && other_min > large_max
886+ {
887+ return Ok ( false ) ;
888+ }
889+
890+ // other is HybridSmall: iterate its (≤31) values through bitmap_contains on large.
891+ // Each bitmap_contains call scans prefix buckets from the start, causing redundant
892+ // work across iterations. This could be improved with a hint/cursor mechanism that
893+ // tracks the last scanned position, but since HybridSmall holds at most 31 values
894+ // the overhead is bounded (~μs-level). Revisit if this path becomes a bottleneck.
895+ if is_hybrid ( other) && !is_hybrid_large ( other) {
896+ let other_bm = deserialize_bitmap ( other) ?;
897+ for value in other_bm. iter ( ) {
898+ if bitmap_contains ( large, value) ? {
899+ return Ok ( true ) ;
900+ }
901+ }
902+ return Ok ( false ) ;
903+ }
904+
905+ // other is Legacy (potentially large): fallback to full deserialization
906+ let lhs_bm = deserialize_bitmap ( lhs) ?;
907+ let rhs_bm = deserialize_bitmap ( rhs) ?;
908+ Ok ( lhs_bm. intersection_len ( & rhs_bm) != 0 )
909+ } else {
910+ // Both else: deserialize both
911+ let lhs_bm = deserialize_bitmap ( lhs) ?;
912+ let rhs_bm = deserialize_bitmap ( rhs) ?;
913+ Ok ( lhs_bm. intersection_len ( & rhs_bm) != 0 )
914+ }
915+ }
916+
819917fn parse_bitmap_rhs ( buf : & [ u8 ] ) -> Result < BitmapRhsView < ' _ > > {
820918 if buf. is_empty ( ) {
821919 return Ok ( BitmapRhsView :: Empty ) ;
@@ -1647,4 +1745,195 @@ mod tests {
16471745 // Empty buffer
16481746 assert_eq ! ( bitmap_max( & [ ] ) . unwrap( ) , None ) ;
16491747 }
1748+
1749+ fn ground_truth_has_any ( lhs : & [ u8 ] , rhs : & [ u8 ] ) -> bool {
1750+ let lhs_bm = deserialize_bitmap ( lhs) . unwrap ( ) ;
1751+ let rhs_bm = deserialize_bitmap ( rhs) . unwrap ( ) ;
1752+ lhs_bm. intersection_len ( & rhs_bm) != 0
1753+ }
1754+
1755+ #[ test]
1756+ fn test_bitmap_has_any ( ) {
1757+ // Both HybridLarge with overlap: 0..50000 and 40000..90000 -> overlap at 40000-49999
1758+ let lhs = HybridBitmap :: from_iter ( 0u64 ..50000 ) ;
1759+ let rhs = HybridBitmap :: from_iter ( 40000u64 ..90000 ) ;
1760+ let mut lhs_buf = Vec :: new ( ) ;
1761+ let mut rhs_buf = Vec :: new ( ) ;
1762+ lhs. serialize_into ( & mut lhs_buf) . unwrap ( ) ;
1763+ rhs. serialize_into ( & mut rhs_buf) . unwrap ( ) ;
1764+ assert_eq ! (
1765+ bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ,
1766+ ground_truth_has_any( & lhs_buf, & rhs_buf)
1767+ ) ;
1768+ assert ! ( bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ) ;
1769+
1770+ // Both HybridLarge disjoint: 0..50000 and 100000..150000
1771+ let lhs = HybridBitmap :: from_iter ( 0u64 ..50000 ) ;
1772+ let rhs = HybridBitmap :: from_iter ( 100000u64 ..150000 ) ;
1773+ let mut lhs_buf = Vec :: new ( ) ;
1774+ let mut rhs_buf = Vec :: new ( ) ;
1775+ lhs. serialize_into ( & mut lhs_buf) . unwrap ( ) ;
1776+ rhs. serialize_into ( & mut rhs_buf) . unwrap ( ) ;
1777+ assert_eq ! (
1778+ bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ,
1779+ ground_truth_has_any( & lhs_buf, & rhs_buf)
1780+ ) ;
1781+ assert ! ( !bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ) ;
1782+
1783+ // Both HybridSmall with overlap: 0..31 and 20..51 -> overlap at 20-30
1784+ let lhs = HybridBitmap :: from_iter ( 0u64 ..31 ) ;
1785+ let rhs = HybridBitmap :: from_iter ( 20u64 ..51 ) ;
1786+ let mut lhs_buf = Vec :: new ( ) ;
1787+ let mut rhs_buf = Vec :: new ( ) ;
1788+ lhs. serialize_into ( & mut lhs_buf) . unwrap ( ) ;
1789+ rhs. serialize_into ( & mut rhs_buf) . unwrap ( ) ;
1790+ assert_eq ! (
1791+ bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ,
1792+ ground_truth_has_any( & lhs_buf, & rhs_buf)
1793+ ) ;
1794+ assert ! ( bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ) ;
1795+
1796+ // Both HybridSmall disjoint: 0..15 and 20..35
1797+ let lhs = HybridBitmap :: from_iter ( 0u64 ..15 ) ;
1798+ let rhs = HybridBitmap :: from_iter ( 20u64 ..35 ) ;
1799+ let mut lhs_buf = Vec :: new ( ) ;
1800+ let mut rhs_buf = Vec :: new ( ) ;
1801+ lhs. serialize_into ( & mut lhs_buf) . unwrap ( ) ;
1802+ rhs. serialize_into ( & mut rhs_buf) . unwrap ( ) ;
1803+ assert_eq ! (
1804+ bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ,
1805+ ground_truth_has_any( & lhs_buf, & rhs_buf)
1806+ ) ;
1807+ assert ! ( !bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ) ;
1808+
1809+ // Mixed (HybridLarge lhs, HybridSmall rhs) with overlap: 0..50000 and 10..41
1810+ let lhs = HybridBitmap :: from_iter ( 0u64 ..50000 ) ;
1811+ let rhs = HybridBitmap :: from_iter ( 10u64 ..41 ) ;
1812+ let mut lhs_buf = Vec :: new ( ) ;
1813+ let mut rhs_buf = Vec :: new ( ) ;
1814+ lhs. serialize_into ( & mut lhs_buf) . unwrap ( ) ;
1815+ rhs. serialize_into ( & mut rhs_buf) . unwrap ( ) ;
1816+ assert_eq ! (
1817+ bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ,
1818+ ground_truth_has_any( & lhs_buf, & rhs_buf)
1819+ ) ;
1820+ assert ! ( bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ) ;
1821+
1822+ // Mixed (HybridSmall lhs, HybridLarge rhs) with overlap: 10..41 and 0..50000
1823+ let lhs = HybridBitmap :: from_iter ( 10u64 ..41 ) ;
1824+ let rhs = HybridBitmap :: from_iter ( 0u64 ..50000 ) ;
1825+ let mut lhs_buf = Vec :: new ( ) ;
1826+ let mut rhs_buf = Vec :: new ( ) ;
1827+ lhs. serialize_into ( & mut lhs_buf) . unwrap ( ) ;
1828+ rhs. serialize_into ( & mut rhs_buf) . unwrap ( ) ;
1829+ assert_eq ! (
1830+ bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ,
1831+ ground_truth_has_any( & lhs_buf, & rhs_buf)
1832+ ) ;
1833+ assert ! ( bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ) ;
1834+
1835+ // Empty lhs
1836+ let rhs = HybridBitmap :: from_iter ( 0u64 ..50000 ) ;
1837+ let mut rhs_buf = Vec :: new ( ) ;
1838+ rhs. serialize_into ( & mut rhs_buf) . unwrap ( ) ;
1839+ assert ! ( !bitmap_has_any( & [ ] , & rhs_buf) . unwrap( ) ) ;
1840+
1841+ // Empty rhs
1842+ let lhs = HybridBitmap :: from_iter ( 0u64 ..50000 ) ;
1843+ let mut lhs_buf = Vec :: new ( ) ;
1844+ lhs. serialize_into ( & mut lhs_buf) . unwrap ( ) ;
1845+ assert ! ( !bitmap_has_any( & lhs_buf, & [ ] ) . unwrap( ) ) ;
1846+
1847+ // Both empty
1848+ assert ! ( !bitmap_has_any( & [ ] , & [ ] ) . unwrap( ) ) ;
1849+
1850+ // Mixed (Legacy lhs, HybridLarge rhs) with overlap
1851+ let mut lhs_tree = RoaringTreemap :: new ( ) ;
1852+ for v in [ 0u64 , 2500 , 40000 ] {
1853+ lhs_tree. insert ( v) ;
1854+ }
1855+ let rhs = HybridBitmap :: from_iter ( 0u64 ..50000 ) ;
1856+ let mut lhs_buf = Vec :: new ( ) ;
1857+ let mut rhs_buf = Vec :: new ( ) ;
1858+ lhs_tree. serialize_into ( & mut lhs_buf) . unwrap ( ) ;
1859+ rhs. serialize_into ( & mut rhs_buf) . unwrap ( ) ;
1860+ assert_eq ! (
1861+ bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ,
1862+ ground_truth_has_any( & lhs_buf, & rhs_buf)
1863+ ) ;
1864+ assert ! ( bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ) ;
1865+
1866+ // Mixed (HybridLarge lhs, Legacy rhs) with overlap
1867+ let lhs = HybridBitmap :: from_iter ( 0u64 ..50000 ) ;
1868+ let mut rhs_tree = RoaringTreemap :: new ( ) ;
1869+ for v in [ 0u64 , 2500 , 40000 ] {
1870+ rhs_tree. insert ( v) ;
1871+ }
1872+ let mut lhs_buf = Vec :: new ( ) ;
1873+ let mut rhs_buf = Vec :: new ( ) ;
1874+ lhs. serialize_into ( & mut lhs_buf) . unwrap ( ) ;
1875+ rhs_tree. serialize_into ( & mut rhs_buf) . unwrap ( ) ;
1876+ assert_eq ! (
1877+ bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ,
1878+ ground_truth_has_any( & lhs_buf, & rhs_buf)
1879+ ) ;
1880+ assert ! ( bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ) ;
1881+
1882+ // Both Legacy with overlap
1883+ let mut lhs_tree = RoaringTreemap :: new ( ) ;
1884+ for v in 0u64 ..100 {
1885+ lhs_tree. insert ( v) ;
1886+ }
1887+ let mut rhs_tree = RoaringTreemap :: new ( ) ;
1888+ for v in 50u64 ..150 {
1889+ rhs_tree. insert ( v) ;
1890+ }
1891+ let mut lhs_buf = Vec :: new ( ) ;
1892+ let mut rhs_buf = Vec :: new ( ) ;
1893+ lhs_tree. serialize_into ( & mut lhs_buf) . unwrap ( ) ;
1894+ rhs_tree. serialize_into ( & mut rhs_buf) . unwrap ( ) ;
1895+ assert_eq ! (
1896+ bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ,
1897+ ground_truth_has_any( & lhs_buf, & rhs_buf)
1898+ ) ;
1899+ assert ! ( bitmap_has_any( & lhs_buf, & rhs_buf) . unwrap( ) ) ;
1900+ }
1901+
1902+ #[ test]
1903+ fn test_bitmap_stats ( ) {
1904+ // HybridLarge stats
1905+ let large = HybridBitmap :: from_iter ( 0u64 ..50000 ) ;
1906+ let mut buf = Vec :: new ( ) ;
1907+ large. serialize_into ( & mut buf) . unwrap ( ) ;
1908+ let stats = bitmap_stats ( & buf) . unwrap ( ) ;
1909+ assert_eq ! ( stats. len, 50000 ) ;
1910+ assert_eq ! ( stats. min, Some ( 0 ) ) ;
1911+ assert_eq ! ( stats. max, Some ( 49999 ) ) ;
1912+
1913+ // HybridSmall stats
1914+ let small = HybridBitmap :: from_iter ( 100u64 ..131 ) ;
1915+ let mut buf = Vec :: new ( ) ;
1916+ small. serialize_into ( & mut buf) . unwrap ( ) ;
1917+ let stats = bitmap_stats ( & buf) . unwrap ( ) ;
1918+ assert_eq ! ( stats. len, 31 ) ;
1919+ assert_eq ! ( stats. min, Some ( 100 ) ) ;
1920+ assert_eq ! ( stats. max, Some ( 130 ) ) ;
1921+
1922+ // Empty stats
1923+ let stats = bitmap_stats ( & [ ] ) . unwrap ( ) ;
1924+ assert_eq ! ( stats. len, 0 ) ;
1925+ assert_eq ! ( stats. min, None ) ;
1926+ assert_eq ! ( stats. max, None ) ;
1927+
1928+ // Legacy stats
1929+ let mut tree = RoaringTreemap :: new ( ) ;
1930+ tree. insert ( 42 ) ;
1931+ tree. insert ( 100 ) ;
1932+ let mut buf = Vec :: new ( ) ;
1933+ tree. serialize_into ( & mut buf) . unwrap ( ) ;
1934+ let stats = bitmap_stats ( & buf) . unwrap ( ) ;
1935+ assert_eq ! ( stats. len, 2 ) ;
1936+ assert_eq ! ( stats. min, Some ( 42 ) ) ;
1937+ assert_eq ! ( stats. max, Some ( 100 ) ) ;
1938+ }
16501939}
0 commit comments