This is an automated email from the ASF dual-hosted git repository.
hello-stephen pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/doris.git
The following commit(s) were added to refs/heads/master by this push:
new 307bf9e333f [Refactor](bitmap) Refactor BitmapValue small_set and
roaring_bitmap (#65421)
307bf9e333f is described below
commit 307bf9e333f349726c0d00cfe82c446138809bf2
Author: linrrarity <[email protected]>
AuthorDate: Thu Jul 16 09:39:54 2026 +0800
[Refactor](bitmap) Refactor BitmapValue small_set and roaring_bitmap
(#65421)
Problem Summary:
This PR optimizes bitmap execution performance by updating the forked
Roaring64Map implementation and refactoring the small-cardinality SET
representation in BitmapValue.
1. Updates Doris's forked Roaring64Map implementation based on CRoaring
v4.7.2:
https://github.com/RoaringBitmap/CRoaring/blob/v4.7.2/cpp/roaring/roaring64map.hh
Doris-specific serialization/deserialization behavior is preserved.
2. Refactors BitmapValue's small-cardinality SET representation from
`phmap::flat_hash_set` to an inline fixed-capacity container backed by
`std::array<uint64_t, 32>`.
Main SET optimizations include:
- Store small bitmap values inline to avoid heap allocation in common
SET operations.
- Add batch insert paths so add_many and SET deserialization can
copy/process values in bulk.
- Optimize subset operations for SET:
- sub_range filters values directly without sorting.
- sub_limit uses filtering plus nth_element to select the smallest
limited subset without fully sorting all values.
- offset_limit uses nth_element-based rank selection to avoid fully
sorting the SET.
- Add direct intersects and contains_all helpers to avoid materializing
temporary bitmaps in `bitmap_has_any` and `bitmap_has_all`.
3. Compatibility:
- No new bitmap type code is introduced.
- Existing EMPTY, SINGLE32, SINGLE64, SET, SET_V2, BITMAP32/64, and
BITMAP32/64_V2 formats remain readable.
- SET still serializes using the historical SET format.
4. Performance
BITMAP_FROM_ARRAY
before:
```text
Doris> SELECT COUNT(*) FROM bitmap_test WHERE
array_size(bitmap_to_array(large_bitmap)) > 0;
+----------+
| COUNT(*) |
+----------+
| 5000000 |
+----------+
1 row in set (28.962 sec)
Doris> SELECT COUNT(*) FROM bitmap_test WHERE
array_size(bitmap_to_array(small_bitmap)) > 0;
+----------+
| COUNT(*) |
+----------+
| 5000000 |
+----------+
1 row in set (1.267 sec)
```
now:
```text
Doris> SELECT COUNT(*) FROM bitmap_test WHERE
array_size(bitmap_to_array(large_bitmap)) > 0;
+----------+
| COUNT(*) |
+----------+
| 5000000 |
+----------+
1 row in set (21.722 sec)
Doris> SELECT COUNT(*) FROM bitmap_test WHERE
array_size(bitmap_to_array(small_bitmap)) > 0;
+----------+
| COUNT(*) |
+----------+
| 5000000 |
+----------+
1 row in set (0.506 sec)
```
---
BITMAP_CONTAINS
before:
```text
Doris> SELECT COUNT(*) FROM bitmap_test WHERE
bitmap_contains(small_bitmap, id % 100);
+----------+
| COUNT(*) |
+----------+
| 5000000 |
+----------+
1 row in set (0.995 sec)
```
now:
```text
Doris> SELECT COUNT(*) FROM bitmap_test WHERE
bitmap_contains(small_bitmap, id % 100);
+----------+
| COUNT(*) |
+----------+
| 5000000 |
+----------+
1 row in set (0.290 sec)
```
---
SUB_BITMAP
before:
```text
Doris> SELECT COUNT(*) FROM bitmap_test WHERE
bitmap_count(sub_bitmap(large_bitmap, 100, 500)) > 0;
+----------+
| COUNT(*) |
+----------+
| 5000000 |
+----------+
1 row in set (1 min 3.175 sec)
```
now:
```text
Doris> SELECT COUNT(*) FROM bitmap_test WHERE
bitmap_count(sub_bitmap(large_bitmap, 100, 500)) > 0;
+----------+
| COUNT(*) |
+----------+
| 5000000 |
+----------+
1 row in set (51.151 sec)
```
---
BITMAP_SUBSET_LIMIT
before:
```text
Doris> SELECT COUNT(*)
-> FROM bitmap_test
-> WHERE bitmap_count(bitmap_subset_limit(large_bitmap, id % 10000,
500)) > 0;
+----------+
| COUNT(*) |
+----------+
| 5000000 |
+----------+
1 row in set (1 min 11.807 sec)
```
now:
```text
Doris> SELECT COUNT(*)
-> FROM bitmap_test
-> WHERE bitmap_count(bitmap_subset_limit(large_bitmap, id % 10000,
500)) > 0;
+----------+
| COUNT(*) |
+----------+
| 5000000 |
+----------+
1 row in set (48.117 sec)
```
---
be/src/core/value/bitmap_value.h | 1287 +++++++++++++-------
be/src/exprs/function/function_bitmap.cpp | 34 +-
be/src/exprs/function/function_bitmap_min_or_max.h | 12 +-
be/src/util/coding.h | 15 +
be/test/core/block/column_complex_test.cpp | 4 +-
be/test/core/value/bitmap_value_test.cpp | 449 ++++++-
.../bitmap_functions/test_bitmap_function.out | 21 +
.../bitmap_functions/test_bitmap_function.groovy | 13 +
8 files changed, 1366 insertions(+), 469 deletions(-)
diff --git a/be/src/core/value/bitmap_value.h b/be/src/core/value/bitmap_value.h
index 3b7b05d04a1..f18a7eb8fd3 100644
--- a/be/src/core/value/bitmap_value.h
+++ b/be/src/core/value/bitmap_value.h
@@ -21,9 +21,13 @@
#include <parallel_hashmap/phmap.h>
#include <algorithm>
+#include <array>
+#include <bit>
#include <cstdarg>
#include <cstdint>
#include <cstdio>
+#include <initializer_list>
+#include <iterator>
#include <limits>
#include <map>
#include <memory>
@@ -34,7 +38,9 @@
#include <set>
#include <stdexcept>
#include <string>
+#include <type_traits>
#include <utility>
+#include <vector>
#include "common/config.h"
#include "common/exception.h"
@@ -99,12 +105,14 @@ namespace detail {
class Roaring64MapSetBitForwardIterator;
-// Forked from
https://github.com/RoaringBitmap/CRoaring/blob/v0.2.60/cpp/roaring64map.hh
+// Forked from
https://github.com/RoaringBitmap/CRoaring/blob/v4.7.2/cpp/roaring/roaring64map.hh
// What we change includes
// - a custom serialization format is used inside
read()/write()/getSizeInBytes()
// - added clear() and is32BitsEnough()
class Roaring64Map {
public:
+ using RoaringMap = std::map<uint32_t, roaring::Roaring>;
+
/**
* Create an empty bitmap
*/
@@ -141,41 +149,46 @@ public:
* Add value x
*
*/
- void add(uint32_t x) {
- roarings[0].add(x);
- roarings[0].setCopyOnWrite(copyOnWrite);
- }
- void add(uint64_t x) {
- roarings[highBytes(x)].add(lowBytes(x));
- roarings[highBytes(x)].setCopyOnWrite(copyOnWrite);
- }
+ void add(uint32_t x) { lookupOrCreateInner(0).add(x); }
- template <typename T>
+ void add(uint64_t x) { lookupOrCreateInner(highBytes(x)).add(lowBytes(x));
}
+
+ template <typename T,
+ typename = std::enable_if_t<std::is_integral_v<T> &&
+ !std::is_same_v<std::remove_cv_t<T>,
uint64_t>>>
void addMany(size_t n_args, const T* vals) {
if constexpr (sizeof(T) == sizeof(uint32_t)) {
- auto& roaring = roarings[0];
- roaring.addMany(n_args, reinterpret_cast<const uint32_t*>(vals));
- roaring.setCopyOnWrite(copyOnWrite);
+ lookupOrCreateInner(0).addMany(n_args, reinterpret_cast<const
uint32_t*>(vals));
} else if constexpr (sizeof(T) < sizeof(uint32_t)) {
- auto& roaring = roarings[0];
std::vector<uint32_t> values(n_args);
for (size_t i = 0; i != n_args; ++i) {
- values[i] = uint32_t(vals[i]);
+ values[i] = static_cast<uint32_t>(vals[i]);
}
- roaring.addMany(n_args, values.data());
- roaring.setCopyOnWrite(copyOnWrite);
+ lookupOrCreateInner(0).addMany(n_args, values.data());
} else {
- for (size_t lcv = 0; lcv < n_args; lcv++) {
- roarings[highBytes(vals[lcv])].add(lowBytes(vals[lcv]));
- roarings[highBytes(vals[lcv])].setCopyOnWrite(copyOnWrite);
- }
+ addMany64(n_args, vals);
}
}
- void addMany(size_t n_args, const uint64_t* vals) {
+ void addMany(size_t n_args, const uint64_t* vals) { addMany64(n_args,
vals); }
+
+ template <typename T>
+ void addMany64(size_t n_args, const T* vals) {
+ // Potentially reduce outer map lookups by optimistically
+ // assuming that adjacent values will belong to the same inner bitmap.
+ roaring::Roaring* last_inner_bitmap = nullptr;
+ uint32_t last_value_high = 0;
+ roaring::BulkContext last_bulk_context;
for (size_t lcv = 0; lcv < n_args; lcv++) {
- roarings[highBytes(vals[lcv])].add(lowBytes(vals[lcv]));
- roarings[highBytes(vals[lcv])].setCopyOnWrite(copyOnWrite);
+ auto value = static_cast<uint64_t>(vals[lcv]);
+ auto value_high = highBytes(value);
+ auto value_low = lowBytes(value);
+ if (last_inner_bitmap == nullptr || value_high != last_value_high)
{
+ last_inner_bitmap = &lookupOrCreateInner(value_high);
+ last_value_high = value_high;
+ last_bulk_context = roaring::BulkContext {};
+ }
+ last_inner_bitmap->addBulk(last_bulk_context, value_low);
}
}
@@ -192,7 +205,6 @@ public:
}
/**
* Return the largest value (if not empty)
- *
*/
uint64_t maximum() const {
for (auto roaring_iter = roarings.crbegin(); roaring_iter !=
roarings.crend();
@@ -201,14 +213,13 @@ public:
return uniteBytes(roaring_iter->first,
roaring_iter->second.maximum());
}
}
- // we put std::numeric_limits<>::max/min in parenthesis
+ // we put std::numeric_limits<>::max/min in parentheses
// to avoid a clash with the Windows.h header under Windows
return (std::numeric_limits<uint64_t>::min)();
}
/**
* Return the smallest value (if not empty)
- *
*/
uint64_t minimum() const {
for (auto roaring_iter = roarings.cbegin(); roaring_iter !=
roarings.cend();
@@ -217,7 +228,7 @@ public:
return uniteBytes(roaring_iter->first,
roaring_iter->second.minimum());
}
}
- // we put std::numeric_limits<>::max/min in parenthesis
+ // we put std::numeric_limits<>::max/min in parentheses
// to avoid a clash with the Windows.h header under Windows
return (std::numeric_limits<uint64_t>::max)();
}
@@ -226,25 +237,75 @@ public:
* Check if value x is present
*/
bool contains(uint32_t x) const {
- return roarings.count(0) == 0 ? false : roarings.at(0).contains(x);
+ auto iter = roarings.find(0);
+ if (iter == roarings.end()) {
+ return false;
+ }
+ return iter->second.contains(x);
}
bool contains(uint64_t x) const {
- return roarings.count(highBytes(x)) == 0 ? false
- :
roarings.at(highBytes(x)).contains(lowBytes(x));
+ auto iter = roarings.find(highBytes(x));
+ if (iter == roarings.end()) {
+ return false;
+ }
+ return iter->second.contains(lowBytes(x));
}
/**
- * Compute the intersection between the current bitmap and the provided
- * bitmap,
+ * Compute the intersection of the current bitmap and the provided bitmap,
* writing the result in the current bitmap. The provided bitmap is not
* modified.
+ *
+ * Performance hint: if you are computing the intersection between several
+ * bitmaps, two-by-two, it is best to start with the smallest bitmap.
*/
- Roaring64Map& operator&=(const Roaring64Map& r) {
- for (auto& map_entry : roarings) {
- if (r.roarings.count(map_entry.first) == 1) {
- map_entry.second &= r.roarings.at(map_entry.first);
- } else {
- map_entry.second = roaring::Roaring();
+ Roaring64Map& operator&=(const Roaring64Map& other) {
+ if (this == &other) {
+ // ANDing *this with itself is a no-op.
+ return *this;
+ }
+
+ // Logic table summarizing what to do when a given outer key is
+ // present vs. absent from self and other.
+ //
+ // self other (self & other) work to do
+ // --------------------------------------------
+ // absent absent empty None
+ // absent present empty None
+ // present absent empty Erase self
+ // present present empty or not Intersect self with other, but
+ // erase self if result is empty.
+ //
+ // Because there is only work to do when a key is present in 'self',
the
+ // main for loop iterates over entries in 'self'.
+
+ decltype(roarings.begin()) self_next;
+ for (auto self_iter = roarings.begin(); self_iter != roarings.end();
+ self_iter = self_next) {
+ // Do the 'next' operation now, so we don't have to worry about
+ // invalidation of self_iter down below with the 'erase' operation.
+ self_next = std::next(self_iter);
+
+ auto self_key = self_iter->first;
+ auto& self_bitmap = self_iter->second;
+
+ auto other_iter = other.roarings.find(self_key);
+ if (other_iter == other.roarings.end()) {
+ // 'other' doesn't have self_key. In the logic table above,
+ // this reflects the case (self.present & other.absent).
+ // So, erase self.
+ roarings.erase(self_iter);
+ continue;
+ }
+
+ // Both sides have self_key. In the logic table above, this
reflects
+ // the case (self.present & other.present). So, intersect self with
+ // other.
+ const auto& other_bitmap = other_iter->second;
+ self_bitmap &= other_bitmap;
+ if (self_bitmap.isEmpty()) {
+ // ...but if intersection is empty, remove it altogether.
+ roarings.erase(self_iter);
}
}
return *this;
@@ -252,51 +313,178 @@ public:
/**
* Compute the difference between the current bitmap and the provided
- * bitmap,
- * writing the result in the current bitmap. The provided bitmap is not
- * modified.
+ * bitmap, writing the result in the current bitmap. The provided bitmap
+ * is not modified.
*/
- Roaring64Map& operator-=(const Roaring64Map& r) {
- for (auto& map_entry : roarings) {
- if (r.roarings.count(map_entry.first) == 1) {
- map_entry.second -= r.roarings.at(map_entry.first);
+ Roaring64Map& operator-=(const Roaring64Map& other) {
+ if (this == &other) {
+ // Subtracting *this from itself results in the empty map.
+ roarings.clear();
+ return *this;
+ }
+
+ // Logic table summarizing what to do when a given outer key is
+ // present vs. absent from self and other.
+ //
+ // self other (self - other) work to do
+ // --------------------------------------------
+ // absent absent empty None
+ // absent present empty None
+ // present absent unchanged None
+ // present present empty or not Subtract other from self, but
+ // erase self if result is empty
+ //
+ // Because there is only work to do when a key is present in both
'self'
+ // and 'other', the main while loop ping-pongs back and forth until it
+ // finds the next key that is the same on both sides.
+
+ auto self_iter = roarings.begin();
+ auto other_iter = other.roarings.cbegin();
+
+ while (self_iter != roarings.end() && other_iter !=
other.roarings.cend()) {
+ auto self_key = self_iter->first;
+ auto other_key = other_iter->first;
+ if (self_key < other_key) {
+ // Because self_key is < other_key, advance self_iter to the
+ // first point where self_key >= other_key (or end).
+ self_iter = roarings.lower_bound(other_key);
+ continue;
+ }
+
+ if (self_key > other_key) {
+ // Because self_key is > other_key, advance other_iter to the
+ // first point where other_key >= self_key (or end).
+ other_iter = other.roarings.lower_bound(self_key);
+ continue;
+ }
+
+ // Both sides have self_key. In the logic table above, this
reflects
+ // the case (self.present & other.present). So subtract other from
+ // self.
+ auto& self_bitmap = self_iter->second;
+ const auto& other_bitmap = other_iter->second;
+ self_bitmap -= other_bitmap;
+
+ if (self_bitmap.isEmpty()) {
+ // ...but if subtraction is empty, remove it altogether.
+ self_iter = roarings.erase(self_iter);
+ } else {
+ ++self_iter;
}
+ ++other_iter;
}
return *this;
}
/**
- * Compute the union between the current bitmap and the provided bitmap,
+ * Compute the union of the current bitmap and the provided bitmap,
* writing the result in the current bitmap. The provided bitmap is not
* modified.
*
* See also the fastunion function to aggregate many bitmaps more quickly.
*/
- Roaring64Map& operator|=(const Roaring64Map& r) {
- for (const auto& map_entry : r.roarings) {
- if (roarings.count(map_entry.first) == 0) {
- roarings[map_entry.first] = map_entry.second;
- roarings[map_entry.first].setCopyOnWrite(copyOnWrite);
- } else {
- roarings[map_entry.first] |= map_entry.second;
+ Roaring64Map& operator|=(const Roaring64Map& other) {
+ if (this == &other) {
+ // ORing *this with itself is a no-op.
+ return *this;
+ }
+
+ // Logic table summarizing what to do when a given outer key is
+ // present vs. absent from self and other.
+ //
+ // self other (self | other) work to do
+ // --------------------------------------------
+ // absent absent empty None
+ // absent present not empty Copy other to self and set flags
+ // present absent unchanged None
+ // present present not empty self |= other
+ //
+ // Because there is only work to do when a key is present in 'other',
+ // the main for loop iterates over entries in 'other'.
+
+ for (const auto& other_entry : other.roarings) {
+ const auto& other_bitmap = other_entry.second;
+
+ // Try to insert other_bitmap into self at other_key. We take
+ // advantage of the fact that std::map::insert will not overwrite
an
+ // existing entry.
+ auto insert_result = roarings.insert(other_entry);
+ auto self_iter = insert_result.first;
+ auto insert_happened = insert_result.second;
+ auto& self_bitmap = self_iter->second;
+
+ if (insert_happened) {
+ // Key was not present in self, so insert was performed above.
+ // In the logic table above, this reflects the case
+ // (self.absent | other.present). Because the copy has already
+ // happened, thanks to the 'insert' operation above, we just
+ // need to set the copyOnWrite flag.
+ self_bitmap.setCopyOnWrite(copyOnWrite);
+ continue;
}
+
+ // Both sides have self_key, and the insert was not performed. In
+ // the logic table above, this reflects the case
+ // (self.present & other.present). So OR other into self.
+ self_bitmap |= other_bitmap;
}
return *this;
}
/**
- * Compute the symmetric union between the current bitmap and the provided
- * bitmap,
- * writing the result in the current bitmap. The provided bitmap is not
- * modified.
+ * Compute the XOR of the current bitmap and the provided bitmap, writing
+ * the result in the current bitmap. The provided bitmap is not modified.
*/
- Roaring64Map& operator^=(const Roaring64Map& r) {
- for (const auto& map_entry : r.roarings) {
- if (roarings.count(map_entry.first) == 0) {
- roarings[map_entry.first] = map_entry.second;
- roarings[map_entry.first].setCopyOnWrite(copyOnWrite);
- } else {
- roarings[map_entry.first] ^= map_entry.second;
+ Roaring64Map& operator^=(const Roaring64Map& other) {
+ if (this == &other) {
+ // XORing *this with itself results in the empty map.
+ roarings.clear();
+ return *this;
+ }
+
+ // Logic table summarizing what to do when a given outer key is
+ // present vs. absent from self and other.
+ //
+ // self other (self ^ other) work to do
+ // --------------------------------------------
+ // absent absent empty None
+ // absent present non-empty Copy other to self and set flags
+ // present absent unchanged None
+ // present present empty or not XOR other into self, but erase
self
+ // if result is empty.
+ //
+ // Because there is only work to do when a key is present in 'other',
+ // the main for loop iterates over entries in 'other'.
+
+ for (const auto& other_entry : other.roarings) {
+ const auto& other_bitmap = other_entry.second;
+
+ // Try to insert other_bitmap into self at other_key. We take
+ // advantage of the fact that std::map::insert will not overwrite
an
+ // existing entry.
+ auto insert_result = roarings.insert(other_entry);
+ auto self_iter = insert_result.first;
+ auto insert_happened = insert_result.second;
+ auto& self_bitmap = self_iter->second;
+
+ if (insert_happened) {
+ // Key was not present in self, so insert was performed above.
+ // In the logic table above, this reflects the case
+ // (self.absent ^ other.present). Because the copy has already
+ // happened, thanks to the 'insert' operation above, we just
+ // need to set the copyOnWrite flag.
+ self_bitmap.setCopyOnWrite(copyOnWrite);
+ continue;
+ }
+
+ // Both sides have self_key, and the insert was not performed. In
+ // the logic table above, this reflects the case
+ // (self.present ^ other.present). So XOR other into self.
+ self_bitmap ^= other_bitmap;
+
+ if (self_bitmap.isEmpty()) {
+ // ...but if intersection is empty, remove it altogether.
+ roarings.erase(self_iter);
}
}
return *this;
@@ -339,6 +527,18 @@ public:
return card;
}
+ bool intersect(const Roaring64Map& r) const {
+ const auto& smaller = roarings.size() <= r.roarings.size() ? roarings
: r.roarings;
+ const auto& larger = roarings.size() <= r.roarings.size() ? r.roarings
: roarings;
+ for (const auto& map_entry : smaller) {
+ auto it = larger.find(map_entry.first);
+ if (it != larger.cend() && map_entry.second.intersect(it->second))
{
+ return true;
+ }
+ }
+ return false;
+ }
+
/**
* Computes the size of the union between two bitmaps.
*
@@ -394,6 +594,20 @@ public:
return card;
}
+ bool isSubset(const Roaring64Map& r) const {
+ for (const auto& map_entry : roarings) {
+ if (map_entry.second.isEmpty()) {
+ continue;
+ }
+ auto roaring_iter = r.roarings.find(map_entry.first);
+ if (roaring_iter == r.roarings.cend())
+ return false;
+ else if (!map_entry.second.isSubset(roaring_iter->second))
+ return false;
+ }
+ return true;
+ }
+
/**
* Returns true if the bitmap is empty (cardinality is zero).
*/
@@ -425,6 +639,22 @@ public:
: false;
}
+ /**
+ * Convert the bitmap to a sorted array. Write the output to "ans", the
+ * caller is responsible to ensure that there is enough memory allocated
+ * (e.g., ans = new uint64_t[mybitmap.cardinality()];)
+ */
+ void toUint64Array(uint64_t* ans) const {
+ // Annoyingly, VS 2017 marks std::accumulate() as [[nodiscard]]
+ (void)std::accumulate(roarings.cbegin(), roarings.cend(), ans,
+ [](uint64_t* previous,
+ const std::pair<const uint32_t,
roaring::Roaring>& map_entry) {
+ for (uint32_t low_bits : map_entry.second)
+ *previous++ =
uniteBytes(map_entry.first, low_bits);
+ return previous;
+ });
+ }
+
/**
* Return true if the two bitmaps contain the same elements.
*/
@@ -436,10 +666,8 @@ public:
auto rhs_iter = r.roarings.cbegin();
auto rhs_cend = r.roarings.cend();
while (lhs_iter != lhs_cend && rhs_iter != rhs_cend) {
- auto lhs_key = lhs_iter->first;
- auto rhs_key = rhs_iter->first;
- const auto& lhs_map = lhs_iter->second;
- const auto& rhs_map = rhs_iter->second;
+ auto lhs_key = lhs_iter->first, rhs_key = rhs_iter->first;
+ const auto &lhs_map = lhs_iter->second, &rhs_map =
rhs_iter->second;
if (lhs_map.isEmpty()) {
++lhs_iter;
continue;
@@ -487,9 +715,9 @@ public:
}
/**
- * If needed, reallocate memory to shrink the memory usage. Returns
- * the number of bytes saved.
- */
+ * If needed, reallocate memory to shrink the memory usage.
+ * Returns the number of bytes saved.
+ */
size_t shrinkToFit() {
size_t savedBytes = 0;
auto iter = roarings.begin();
@@ -497,7 +725,7 @@ public:
if (iter->second.isEmpty()) {
// empty Roarings are 84 bytes
savedBytes += 88;
- iter = roarings.erase(iter);
+ roarings.erase(iter++);
} else {
savedBytes += iter->second.shrinkToFit();
iter++;
@@ -507,13 +735,15 @@ public:
}
/**
- * Iterate over the bitmap elements. The function iterator is called once
- * for all the values with ptr (can be nullptr) as the second parameter of
each
- * call.
+ * Iterate over the bitmap elements in order(start from the smallest one)
+ * and call iterator once for every element until the iterator function
+ * returns false. To iterate over all values, the iterator function should
+ * always return true.
*
- * roaring_iterator is simply a pointer to a function that returns bool
- * (true means that the iteration should continue while false means that it
- * should stop), and takes (uint32_t,void*) as inputs.
+ * The roaring_iterator64 parameter is a pointer to a function that
+ * returns bool (true means that the iteration should continue while false
+ * means that it should stop), and takes (uint64_t element, void* ptr) as
+ * inputs.
*/
void iterate(roaring::api::roaring_iterator64 iterator, void* ptr) const {
for (const auto& map_entry : roarings) {
@@ -665,8 +895,8 @@ public:
*/
static Roaring64Map fastunion(size_t n, const Roaring64Map** inputs) {
struct pq_entry {
- phmap::btree_map<uint32_t, roaring::Roaring>::const_iterator
iterator;
- phmap::btree_map<uint32_t, roaring::Roaring>::const_iterator end;
+ RoaringMap::const_iterator iterator;
+ RoaringMap::const_iterator end;
};
struct pq_comp {
@@ -746,7 +976,7 @@ public:
const_iterator end() const;
private:
- phmap::btree_map<uint32_t, roaring::Roaring> roarings {};
+ RoaringMap roarings {};
bool copyOnWrite {false};
static uint32_t highBytes(const uint64_t in) { return uint32_t(in >> 32); }
static uint32_t lowBytes(const uint64_t in) { return uint32_t(in); }
@@ -758,23 +988,60 @@ private:
}
void emplaceOrInsert(const uint32_t key, roaring::Roaring&& value) {
+ // CRoaring's C++ move constructor bit-copies the underlying C struct
and
+ // is unsafe with phmap::btree_map value relocation after operations
such as shrinkToFit().
roarings.emplace(key, value);
}
+
+ roaring::Roaring& lookupOrCreateInner(uint32_t key) {
+ auto& bitmap = roarings[key];
+ bitmap.setCopyOnWrite(copyOnWrite);
+ return bitmap;
+ }
};
-// Forked from
https://github.com/RoaringBitmap/CRoaring/blob/v0.4.0/cpp/roaring64map.hh
+// Forked from
https://github.com/RoaringBitmap/CRoaring/blob/v4.7.2/cpp/roaring/roaring64map.hh
// Used to go through the set bits. Not optimally fast, but convenient.
class Roaring64MapSetBitForwardIterator {
public:
+ using iterator_category = std::forward_iterator_tag;
+ using pointer = uint64_t*;
+ using reference_type = uint64_t&;
+ using value_type = uint64_t;
+ using difference_type = int64_t;
using type_of_iterator = Roaring64MapSetBitForwardIterator;
/**
* Provides the location of the set bit.
*/
- uint64_t operator*() const {
+ value_type operator*() const {
return Roaring64Map::uniteBytes(map_iter->first, i.current_value);
}
+ bool operator<(const type_of_iterator& o) const {
+ if (map_iter == map_end) return false;
+ if (o.map_iter == o.map_end) return true;
+ return **this < *o;
+ }
+
+ bool operator<=(const type_of_iterator& o) const {
+ if (o.map_iter == o.map_end) return true;
+ if (map_iter == map_end) return false;
+ return **this <= *o;
+ }
+
+ bool operator>(const type_of_iterator& o) const {
+ if (o.map_iter == o.map_end) return false;
+ if (map_iter == map_end) return true;
+ return **this > *o;
+ }
+
+ bool operator>=(const type_of_iterator& o) const {
+ if (map_iter == map_end) return true;
+ if (o.map_iter == o.map_end) return false;
+ return **this >= *o;
+ }
+
type_of_iterator& operator++() { // ++i, must returned inc. value
if (i.has_value) {
roaring_advance_uint32_iterator(&i);
@@ -798,22 +1065,6 @@ public:
return orig;
}
- bool move(const uint64_t& x) {
- map_iter = p.lower_bound(Roaring64Map::highBytes(x));
- if (map_iter != p.cend()) {
- roaring_init_iterator(&map_iter->second.roaring, &i);
- if (map_iter->first == Roaring64Map::highBytes(x)) {
- if (roaring_move_uint32_iterator_equalorlarger(&i,
Roaring64Map::lowBytes(x)))
- return true;
- map_iter++;
- if (map_iter == map_end) return false;
- roaring_init_iterator(&map_iter->second.roaring, &i);
- }
- return true;
- }
- return false;
- }
-
bool operator==(const Roaring64MapSetBitForwardIterator& o) const {
if (map_iter == map_end && o.map_iter == o.map_end) return true;
if (o.map_iter == o.map_end) return false;
@@ -832,7 +1083,8 @@ public:
return *this;
}
- Roaring64MapSetBitForwardIterator(const Roaring64MapSetBitForwardIterator&
r) = default;
+ Roaring64MapSetBitForwardIterator(const Roaring64MapSetBitForwardIterator&
r)
+ : p(r.p), map_iter(r.map_iter), map_end(r.map_end), i(r.i) {}
Roaring64MapSetBitForwardIterator(const Roaring64Map& parent, bool
exhausted = false)
: p(parent.roarings), map_end(parent.roarings.cend()) {
@@ -850,9 +1102,9 @@ public:
}
protected:
- const phmap::btree_map<uint32_t, roaring::Roaring>& p;
- phmap::btree_map<uint32_t, roaring::Roaring>::const_iterator map_iter {};
- phmap::btree_map<uint32_t, roaring::Roaring>::const_iterator map_end {};
+ const Roaring64Map::RoaringMap& p;
+ Roaring64Map::RoaringMap::const_iterator map_iter {};
+ Roaring64Map::RoaringMap::const_iterator map_end {};
roaring::api::roaring_uint32_iterator_t i {};
};
@@ -866,15 +1118,193 @@ inline Roaring64MapSetBitForwardIterator
Roaring64Map::end() const {
} // namespace detail
+class BitmapValue;
+
+class BitmapSmallSet {
+public:
+ using value_type = uint64_t;
+ using iterator = value_type*;
+ using const_iterator = const value_type*;
+
+ static constexpr size_t INLINE_CAPACITY = 32;
+ static_assert(INLINE_CAPACITY <= std::numeric_limits<uint8_t>::max(),
+ "INLINE_CAPACITY must fit into uint8_t");
+
+ BitmapSmallSet() = default;
+ BitmapSmallSet(const BitmapSmallSet&) = default;
+ BitmapSmallSet& operator=(const BitmapSmallSet&) = default;
+ BitmapSmallSet(BitmapSmallSet&& other) noexcept { *this =
std::move(other); }
+
+ BitmapSmallSet& operator=(BitmapSmallSet&& other) noexcept {
+ if (this == &other) {
+ return *this;
+ }
+ _values = other._values;
+ _size = other._size;
+ other.clear();
+ return *this;
+ }
+
+ size_t size() const { return _size; }
+ bool empty() const { return _size == 0; }
+
+ iterator begin() { return _values.data(); }
+ iterator end() { return _values.data() + _size; }
+ const_iterator begin() const { return _values.data(); }
+ const_iterator end() const { return _values.data() + _size; }
+ const_iterator cbegin() const { return begin(); }
+ const_iterator cend() const { return end(); }
+ const value_type* data() const { return _values.data(); }
+
+ void clear() { _size = 0; }
+
+ bool contains(value_type value) const { return find_index(value) != npos; }
+
+ // Use `insert/insert_many` only when the caller has proved the inline
storage will not overflow.
+ // Paths that may increase cardinality should call
`BitmapValue::add/add_many` instead.
+ void insert(value_type value) {
+ size_t pos = find_index(value);
+ if (pos != npos) {
+ return;
+ }
+
+ DORIS_CHECK(_size < INLINE_CAPACITY) << "BitmapSmallSet overflow";
+
+ _values[_size] = value;
+ ++_size;
+ }
+
+ template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
+ void insert_many(const T* values, size_t n) {
+ DORIS_CHECK(_size + n <= INLINE_CAPACITY) << "BitmapSmallSet overflow";
+ if (n == 0) {
+ return;
+ }
+
+ if constexpr (std::is_same_v<std::remove_cv_t<T>, value_type>) {
+ memcpy(_values.data() + _size, values, sizeof(value_type) * n);
+ } else {
+ std::array<value_type, INLINE_CAPACITY> converted_values {};
+ for (size_t i = 0; i < n; ++i) {
+ converted_values[i] = static_cast<value_type>(values[i]);
+ }
+ memcpy(_values.data() + _size, converted_values.data(),
sizeof(value_type) * n);
+ }
+ _size = static_cast<uint8_t>(_size + n);
+ compact_unique();
+ }
+
+ template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
+ bool try_insert_many(const T* values, size_t n) {
+ if (_size + n <= INLINE_CAPACITY) {
+ insert_many(values, n);
+ return true;
+ }
+
+ BitmapSmallSet candidate = *this;
+ for (size_t i = 0; i < n; ++i) {
+ auto value = static_cast<value_type>(values[i]);
+ if (candidate.contains(value)) {
+ continue;
+ }
+ if (candidate.size() >= INLINE_CAPACITY) {
+ return false;
+ }
+ candidate.insert(value);
+ }
+ *this = std::move(candidate);
+ return true;
+ }
+
+ // Read serialized SET payload. Values are stored as fixed little-endian
uint64_t.
+ bool read(const void* src, size_t n, bool deduplicate = false) {
+ DORIS_CHECK(n <= INLINE_CAPACITY) << "BitmapSmallSet overflow";
+ clear();
+ decode_fixed64_le_array(_values.data(), src, n);
+ _size = static_cast<uint8_t>(n);
+ if (deduplicate) {
+ compact_unique();
+ return true;
+ }
+ return has_unique_values();
+ }
+
+ void copy_to_sorted(value_type* dst) const {
+ if (_size > 0) {
+ memcpy(dst, _values.data(), sizeof(value_type) * _size);
+ }
+ std::sort(dst, dst + _size);
+ }
+
+ size_t erase(value_type value) {
+ size_t pos = find_index(value);
+ if (pos == npos) {
+ return 0;
+ }
+ erase_at(pos);
+ return 1;
+ }
+
+ value_type operator[](size_t index) const { return _values[index]; }
+ value_type& operator[](size_t index) { return _values[index]; }
+
+private:
+ friend class BitmapValue;
+
+ size_t find_index(value_type value) const {
+ for (size_t i = 0; i < _size; ++i) {
+ if (_values[i] == value) {
+ return i;
+ }
+ }
+ return npos;
+ }
+
+ void erase_at(size_t pos) {
+ DCHECK_LT(pos, _size);
+ _values[pos] = _values[_size - 1];
+ --_size;
+ }
+
+ bool has_unique_values() const {
+ for (size_t i = 0; i < _size; ++i) {
+ for (size_t j = i + 1; j < _size; ++j) {
+ if (_values[i] == _values[j]) {
+ return false;
+ }
+ }
+ }
+ return true;
+ }
+
+ void compact_unique() {
+ size_t unique_size = 0;
+ for (size_t i = 0; i < _size; ++i) {
+ bool found = false;
+ for (size_t j = 0; j < unique_size; ++j) {
+ if (_values[j] == _values[i]) {
+ found = true;
+ break;
+ }
+ }
+ if (!found) {
+ _values[unique_size++] = _values[i];
+ }
+ }
+ _size = static_cast<uint8_t>(unique_size);
+ }
+
+ std::array<value_type, INLINE_CAPACITY> _values {};
+ uint8_t _size = 0;
+ static constexpr size_t npos = static_cast<size_t>(-1);
+};
+
// Represent the in-memory and on-disk structure of Doris's BITMAP data type.
// Optimize for the case where the bitmap contains 0 or 1 element which is
common
// for streaming load scenario.
class BitmapValueIterator;
class BitmapValue {
public:
- template <typename T>
- using SetContainer = phmap::flat_hash_set<T>;
-
// Construct an empty bitmap.
BitmapValue() : _sv(0), _bitmap(nullptr), _type(EMPTY), _is_shared(false)
{ _set.clear(); }
@@ -989,32 +1419,21 @@ public:
break;
}
_is_shared = other._is_shared;
+ other._type = EMPTY;
+ other._is_shared = false;
return *this;
}
// Construct a bitmap from given elements.
- explicit BitmapValue(const std::vector<uint64_t>& bits) :
_is_shared(false) {
- if (bits.size() == 0) {
- _type = EMPTY;
- return;
- }
-
- if (bits.size() == 1) {
- _type = SINGLE;
- _sv = bits[0];
- return;
- }
+ template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
+ explicit BitmapValue(const std::vector<T>& bits) : _is_shared(false) {
+ _type = EMPTY;
+ this->add_many(bits.data(), bits.size());
+ }
- if (!config::enable_set_in_bitmap_value || bits.size() >
SET_TYPE_THRESHOLD) {
- _type = BITMAP;
- _prepare_bitmap_for_write();
- _bitmap->addMany(bits.size(), &bits[0]);
- } else {
- _type = SET;
- for (auto v : bits) {
- _set.insert(v);
- }
- }
+ explicit BitmapValue(const std::vector<uint64_t>& bits) :
_is_shared(false) {
+ _type = EMPTY;
+ add_many(bits.data(), bits.size());
}
BitmapTypeCode::type get_type_code() const {
@@ -1040,18 +1459,27 @@ public:
__builtin_unreachable();
}
- template <typename T>
+ template <typename T, typename = std::enable_if_t<std::is_integral_v<T>>>
void add_many(const T* values, const size_t count) {
+ if (count == 0) {
+ return;
+ }
+
switch (_type) {
case EMPTY:
if (count == 1) {
- _sv = values[0];
+ _sv = static_cast<uint64_t>(values[0]);
_type = SINGLE;
- } else if (config::enable_set_in_bitmap_value && count <
SET_TYPE_THRESHOLD) {
- for (size_t i = 0; i != count; ++i) {
- _set.insert(values[i]);
+ } else if (config::enable_set_in_bitmap_value) {
+ BitmapSmallSet new_set;
+ if (new_set.try_insert_many(values, count)) {
+ _set = std::move(new_set);
+ _type = SET;
+ } else {
+ _prepare_bitmap_for_write();
+ _bitmap->addMany(count, values);
+ _type = BITMAP;
}
- _type = SET;
} else {
_prepare_bitmap_for_write();
_bitmap->addMany(count, values);
@@ -1059,13 +1487,18 @@ public:
}
break;
case SINGLE:
- if (config::enable_set_in_bitmap_value && count <
SET_TYPE_THRESHOLD) {
- _set.insert(_sv);
- for (size_t i = 0; i != count; ++i) {
- _set.insert(values[i]);
+ if (config::enable_set_in_bitmap_value) {
+ BitmapSmallSet new_set;
+ new_set.insert(_sv);
+ if (new_set.try_insert_many(values, count)) {
+ _set = std::move(new_set);
+ _type = SET;
+ } else {
+ _prepare_bitmap_for_write();
+ _bitmap->add(_sv);
+ _bitmap->addMany(count, values);
+ _type = BITMAP;
}
- _type = SET;
- _convert_to_bitmap_if_need();
} else {
_prepare_bitmap_for_write();
_bitmap->add(_sv);
@@ -1078,10 +1511,10 @@ public:
_bitmap->addMany(count, values);
break;
case SET:
- for (size_t i = 0; i != count; ++i) {
- _set.insert(values[i]);
+ if (!_set.try_insert_many(values, count)) {
+ _convert_set_to_bitmap();
+ _bitmap->addMany(count, values);
}
- _convert_to_bitmap_if_need();
break;
}
}
@@ -1093,8 +1526,9 @@ public:
_sv = value;
_type = SINGLE;
} else {
- _set.insert(value);
_type = SET;
+ _set.clear();
+ _set.insert(value);
}
break;
case SINGLE:
@@ -1103,6 +1537,7 @@ public:
break;
}
if (config::enable_set_in_bitmap_value) {
+ _set.clear();
_set.insert(_sv);
_set.insert(value);
_type = SET;
@@ -1118,8 +1553,15 @@ public:
_bitmap->add(value);
break;
case SET:
- _set.insert(value);
- _convert_to_bitmap_if_need();
+ if (_set.contains(value)) {
+ break;
+ }
+ if (_set.size() < SET_TYPE_THRESHOLD) {
+ _set.insert(value);
+ } else {
+ _convert_set_to_bitmap();
+ _bitmap->add(value);
+ }
break;
}
}
@@ -1169,11 +1611,11 @@ public:
_convert_to_smaller_type();
break;
case SET: {
- for (auto it = _set.begin(); it != _set.end();) {
- if (rhs.contains(*it)) {
- it = _set.erase(it);
+ for (size_t i = 0; i < _set.size();) {
+ if (rhs.contains(_set[i])) {
+ _set.erase_at(i);
} else {
- ++it;
+ ++i;
}
}
_convert_to_smaller_type();
@@ -1193,18 +1635,16 @@ public:
case BITMAP:
_prepare_bitmap_for_write();
for (auto v : rhs._set) {
- if (_bitmap->contains(v)) {
- _bitmap->remove(v);
- }
+ _bitmap->remove(v);
}
_convert_to_smaller_type();
break;
case SET: {
- for (auto it = _set.begin(); it != _set.end();) {
- if (rhs.contains(*it)) {
- it = _set.erase(it);
+ for (size_t i = 0; i < _set.size();) {
+ if (rhs.contains(_set[i])) {
+ _set.erase_at(i);
} else {
- ++it;
+ ++i;
}
}
_convert_to_smaller_type();
@@ -1252,11 +1692,10 @@ public:
case SET: {
_prepare_bitmap_for_write();
*_bitmap = *rhs._bitmap;
-
- for (auto v : _set) {
- _bitmap->add(v);
- }
+ _bitmap->addMany(_set.size(), _set.data());
_type = BITMAP;
+
+ _set.clear();
break;
}
}
@@ -1268,34 +1707,19 @@ public:
_type = SET;
break;
case SINGLE: {
- if ((rhs._set.size() + rhs._set.contains(_sv) >
SET_TYPE_THRESHOLD)) {
- _prepare_bitmap_for_write();
- _bitmap->add(_sv);
- for (auto v : rhs._set) {
- _bitmap->add(v);
- }
- _type = BITMAP;
- } else {
- _set = rhs._set;
- _set.insert(_sv);
- _type = SET;
- }
+ _set = rhs._set;
+ _type = SET;
+ this->add(_sv);
break;
}
case BITMAP:
_prepare_bitmap_for_write();
- for (auto v : rhs._set) {
- _bitmap->add(v);
- }
+ _bitmap->addMany(rhs._set.size(), rhs._set.data());
break;
- case SET: {
- for (auto v : rhs._set) {
- _set.insert(v);
- }
- _convert_to_bitmap_if_need();
+ case SET:
+ this->add_many(rhs._set.data(), rhs._set.size());
break;
}
- }
break;
}
return *this;
@@ -1304,7 +1728,7 @@ public:
BitmapValue& fastunion(const std::vector<const BitmapValue*>& values) {
std::vector<const detail::Roaring64Map*> bitmaps;
std::vector<uint64_t> single_values;
- std::vector<const SetContainer<uint64_t>*> sets;
+ std::vector<const BitmapSmallSet*> sets;
for (const auto* value : values) {
switch (value->_type) {
case EMPTY:
@@ -1338,9 +1762,7 @@ public:
break;
case SET: {
*_bitmap = detail::Roaring64Map::fastunion(bitmaps.size(),
bitmaps.data());
- for (auto v : _set) {
- _bitmap->add(v);
- }
+ _bitmap->addMany(_set.size(), _set.data());
_set.clear();
break;
}
@@ -1350,75 +1772,12 @@ public:
if (!sets.empty()) {
for (auto& set : sets) {
- for (auto v : *set) {
- _set.insert(v);
- }
- }
- switch (_type) {
- case EMPTY:
- _type = SET;
- break;
- case SINGLE: {
- _set.insert(_sv);
- _type = SET;
- break;
- }
- case BITMAP:
- _prepare_bitmap_for_write();
- for (auto v : _set) {
- _bitmap->add(v);
- }
- _type = BITMAP;
- _set.clear();
- break;
- case SET: {
- break;
- }
- }
- if (_type == SET) {
- _convert_to_bitmap_if_need();
+ this->add_many(set->data(), set->size());
}
}
- if (_type == EMPTY && single_values.size() == 1) {
- if (config::enable_set_in_bitmap_value) {
- _type = SET;
- _set.insert(single_values[0]);
- } else {
- _sv = single_values[0];
- _type = SINGLE;
- }
- } else if (!single_values.empty()) {
- switch (_type) {
- case EMPTY:
- case SINGLE:
- if (config::enable_set_in_bitmap_value) {
- _set.insert(single_values.cbegin(), single_values.cend());
- if (_type == SINGLE) {
- _set.insert(_sv);
- }
- _type = SET;
- _convert_to_bitmap_if_need();
- } else {
- _prepare_bitmap_for_write();
- _bitmap->addMany(single_values.size(),
single_values.data());
- if (_type == SINGLE) {
- _bitmap->add(_sv);
- }
- _type = BITMAP;
- _convert_to_smaller_type();
- }
- break;
- case BITMAP: {
- _prepare_bitmap_for_write();
- _bitmap->addMany(single_values.size(), single_values.data());
- break;
- }
- case SET:
- _set.insert(single_values.cbegin(), single_values.cend());
- _convert_to_bitmap_if_need();
- break;
- }
+ if (!single_values.empty()) {
+ this->add_many(single_values.data(), single_values.size());
}
return *this;
@@ -1479,11 +1838,11 @@ public:
_convert_to_smaller_type();
break;
case SET:
- for (auto it = _set.begin(); it != _set.end();) {
- if (!rhs._bitmap->contains(*it)) {
- it = _set.erase(it);
+ for (size_t i = 0; i < _set.size();) {
+ if (!rhs._bitmap->contains(_set[i])) {
+ _set.erase_at(i);
} else {
- ++it;
+ ++i;
}
}
_convert_to_smaller_type();
@@ -1512,11 +1871,11 @@ public:
_convert_to_smaller_type();
break;
case SET:
- for (auto it = _set.begin(); it != _set.end();) {
- if (!rhs._set.contains(*it)) {
- it = _set.erase(it);
+ for (size_t i = 0; i < _set.size();) {
+ if (!rhs._set.contains(_set[i])) {
+ _set.erase_at(i);
} else {
- ++it;
+ ++i;
}
}
_convert_to_smaller_type();
@@ -1546,22 +1905,22 @@ public:
if (_sv == rhs._sv) {
_type = EMPTY;
} else {
- add(rhs._sv);
+ this->add(rhs._sv);
}
break;
case BITMAP:
if (!_bitmap->contains(rhs._sv)) {
- add(rhs._sv);
+ this->add(rhs._sv);
} else {
- _prepare_bitmap_for_write();
- _bitmap->remove(rhs._sv);
+ this->remove(rhs._sv);
}
+ _convert_to_smaller_type();
break;
case SET:
if (!_set.contains(rhs._sv)) {
- _set.insert(rhs._sv);
+ this->add(rhs._sv);
} else {
- _set.erase(rhs._sv);
+ this->remove(rhs._sv);
}
break;
}
@@ -1583,7 +1942,7 @@ public:
if (!rhs._bitmap->contains(_sv)) {
_bitmap->add(_sv);
} else {
- _bitmap->remove(_sv);
+ this->remove(_sv);
}
break;
case BITMAP:
@@ -1601,6 +1960,7 @@ public:
_bitmap->add(v);
}
}
+ _set.clear();
_type = BITMAP;
_convert_to_smaller_type();
break;
@@ -1614,12 +1974,12 @@ public:
break;
case SINGLE:
_set = rhs._set;
- if (!rhs._set.contains(_sv)) {
- _set.insert(_sv);
+ _type = SET;
+ if (_set.contains(_sv)) {
+ this->remove(_sv);
} else {
- _set.erase(_sv);
+ this->add(_sv);
}
- _type = SET;
break;
case BITMAP:
_prepare_bitmap_for_write();
@@ -1633,14 +1993,27 @@ public:
_convert_to_smaller_type();
break;
case SET:
- for (auto v : rhs._set) {
- if (_set.contains(v)) {
- _set.erase(v);
- } else {
- _set.insert(v);
+ if (this == &rhs) {
+ reset();
+ break;
+ }
+
+ std::array<uint64_t, SET_TYPE_THRESHOLD> values_to_add {};
+ size_t add_count = 0;
+ for (size_t i = 0; i < rhs._set.size(); ++i) {
+ auto v = rhs._set[i];
+ if (_set.erase(v) == 0) {
+ values_to_add[add_count++] = v;
}
}
- _convert_to_smaller_type();
+
+ if (_set.size() + add_count <= SET_TYPE_THRESHOLD) {
+ _set.insert_many(values_to_add.data(), add_count);
+ _convert_to_smaller_type();
+ } else {
+ _convert_set_to_bitmap();
+ _bitmap->addMany(add_count, values_to_add.data());
+ }
break;
}
break;
@@ -1663,8 +2036,112 @@ public:
return false;
}
- // true if contains a value that belongs to the range [left, right].
- bool contains_any(uint64_t left, uint64_t right) const;
+ bool intersects(const BitmapValue& rhs) const {
+ switch (rhs._type) {
+ case EMPTY:
+ return false;
+ case SINGLE:
+ return contains(rhs._sv);
+ case BITMAP:
+ switch (_type) {
+ case EMPTY:
+ return false;
+ case SINGLE:
+ return rhs._bitmap->contains(_sv);
+ case BITMAP:
+ return _bitmap->intersect(*rhs._bitmap);
+ case SET:
+ for (auto v : _set) {
+ if (rhs._bitmap->contains(v)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ break;
+ case SET:
+ switch (_type) {
+ case EMPTY:
+ return false;
+ case SINGLE:
+ return rhs._set.contains(_sv);
+ case BITMAP:
+ for (auto v : rhs._set) {
+ if (_bitmap->contains(v)) {
+ return true;
+ }
+ }
+ return false;
+ case SET: {
+ const auto& smaller = _set.size() <= rhs._set.size() ? _set :
rhs._set;
+ const auto& larger = _set.size() <= rhs._set.size() ? rhs._set
: _set;
+ for (auto v : smaller) {
+ if (larger.contains(v)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ }
+ }
+ return false;
+ }
+
+ bool contains_all(const BitmapValue& rhs) const {
+ if (rhs.cardinality() == 0) {
+ return true;
+ }
+
+ switch (rhs._type) {
+ case EMPTY:
+ return true;
+ case SINGLE:
+ return contains(rhs._sv);
+ case BITMAP:
+ switch (_type) {
+ case EMPTY:
+ return false;
+ case SINGLE:
+ return rhs._bitmap->cardinality() == 0 ||
+ (rhs._bitmap->cardinality() == 1 &&
rhs._bitmap->contains(_sv));
+ case SET:
+ if (rhs._bitmap->cardinality() > _set.size()) {
+ return false;
+ }
+ for (auto v : *rhs._bitmap) {
+ if (!_set.contains(v)) {
+ return false;
+ }
+ }
+ return true;
+ case BITMAP:
+ return rhs._bitmap->isSubset(*_bitmap);
+ }
+ break;
+ case SET:
+ switch (_type) {
+ case EMPTY:
+ return false;
+ case SINGLE:
+ return rhs._set.size() == 1 && rhs._set.contains(_sv);
+ case BITMAP:
+ for (auto v : rhs._set) {
+ if (!_bitmap->contains(v)) {
+ return false;
+ }
+ }
+ return true;
+ case SET:
+ for (auto v : rhs._set) {
+ if (!_set.contains(v)) {
+ return false;
+ }
+ }
+ return true;
+ }
+ }
+ return false;
+ }
uint64_t cardinality() const {
switch (_type) {
@@ -1732,8 +2209,10 @@ public:
}
case SET: {
uint64_t cardinality = 0;
- for (auto v : _set) {
- if (rhs._set.contains(v)) {
+ const auto& smaller = _set.size() <= rhs._set.size() ? _set :
rhs._set;
+ const auto& larger = _set.size() <= rhs._set.size() ? rhs._set
: _set;
+ for (auto v : smaller) {
+ if (larger.contains(v)) {
++cardinality;
}
}
@@ -1795,13 +2274,7 @@ public:
return cardinality;
}
case SET: {
- uint64_t cardinality = _set.size();
- for (auto v : _set) {
- if (!rhs._set.contains(v)) {
- ++cardinality;
- }
- }
- return cardinality;
+ return _set.size() + rhs._set.size() - and_cardinality(rhs);
}
}
}
@@ -1859,13 +2332,7 @@ public:
return cardinality;
}
case SET: {
- uint64_t cardinality = _set.size();
- for (auto v : rhs._set) {
- if (_set.contains(v)) {
- cardinality -= 1;
- }
- }
- return cardinality;
+ return _set.size() - and_cardinality(rhs);
}
}
}
@@ -1924,9 +2391,14 @@ public:
++dst;
*dst = static_cast<uint8_t>(_set.size());
++dst;
- for (auto v : _set) {
- encode_fixed64_le(reinterpret_cast<uint8_t*>(dst), v);
- dst += sizeof(uint64_t);
+ {
+ // SET serialization does not guarantee a canonical value
order. The old phmap-backed
+ // SET order also depended on bucket state, so write the
current inline order directly.
+ // TypeCode::SET(1 byte) | count(1 byte) | uint64 values in
little-endian order.
+ for (auto v : _set) {
+ encode_fixed64_le(reinterpret_cast<uint8_t*>(dst), v);
+ dst += sizeof(uint64_t);
+ }
}
break;
case BITMAP:
@@ -1938,6 +2410,7 @@ public:
// Deserialize a bitmap value from `src`.
// Return false if `src` begins with unknown type code, true otherwise.
bool deserialize(const char* src) {
+ reset();
switch (*src) {
case BitmapTypeCode::EMPTY:
_type = EMPTY;
@@ -1947,6 +2420,7 @@ public:
_sv = decode_fixed32_le(reinterpret_cast<const uint8_t*>(src + 1));
if (config::enable_set_in_bitmap_value) {
_type = SET;
+ _set.clear();
_set.insert(_sv);
}
break;
@@ -1955,6 +2429,7 @@ public:
_sv = decode_fixed64_le(reinterpret_cast<const uint8_t*>(src + 1));
if (config::enable_set_in_bitmap_value) {
_type = SET;
+ _set.clear();
_set.insert(_sv);
}
break;
@@ -1980,51 +2455,34 @@ public:
throw Exception(ErrorCode::INTERNAL_ERROR,
"bitmap value with incorrect set count, count:
{}", count);
}
- _set.reserve(count);
- for (uint8_t i = 0; i != count; ++i, src += sizeof(uint64_t)) {
- _set.insert(decode_fixed64_le(reinterpret_cast<const
uint8_t*>(src)));
- }
- if (_set.size() != count) {
+ if (!_set.read(src, count)) {
throw Exception(ErrorCode::INTERNAL_ERROR,
- "bitmap value with incorrect set count, count:
{}, set size: {}",
- count, _set.size());
+ "bitmap value with duplicated set values,
count: {}", count);
}
+ src += sizeof(uint64_t) * count;
if (!config::enable_set_in_bitmap_value) {
- _prepare_bitmap_for_write();
- for (auto v : _set) {
- _bitmap->add(v);
- }
- _type = BITMAP;
- _set.clear();
+ _convert_set_to_bitmap();
}
break;
}
case BitmapTypeCode::SET_V2: {
- uint32_t size = 0;
- memcpy(&size, src + 1, sizeof(uint32_t));
+ uint32_t size = decode_fixed32_le(reinterpret_cast<const
uint8_t*>(src + 1));
src += sizeof(uint32_t) + 1;
if (!config::enable_set_in_bitmap_value || size >
SET_TYPE_THRESHOLD) {
_type = BITMAP;
_prepare_bitmap_for_write();
- for (int i = 0; i < size; ++i) {
- uint64_t key {};
- memcpy(&key, src, sizeof(uint64_t));
- _bitmap->add(key);
- src += sizeof(uint64_t);
+ if (size > 0) {
+ std::vector<uint64_t> values(size);
+ decode_fixed64_le_array(values.data(), src, size);
+ _bitmap->addMany(size, values.data());
+ src += sizeof(uint64_t) * size;
}
} else {
_type = SET;
- _set.reserve(size);
-
- for (int i = 0; i < size; ++i) {
- uint64_t key {};
- memcpy(&key, src, sizeof(uint64_t));
- _set.insert(key);
- src += sizeof(uint64_t);
- }
+ _set.read(src, size, true);
}
break;
}
@@ -2087,10 +2545,11 @@ public:
} iter_ctx;
iter_ctx.ss = &ss;
- std::vector<uint64_t> values(_set.begin(), _set.end());
- std::sort(values.begin(), values.end());
+ std::array<uint64_t, SET_TYPE_THRESHOLD> values {};
+ _set.copy_to_sorted(values.data());
- for (auto v : values) {
+ for (size_t i = 0; i < _set.size(); ++i) {
+ auto v = values[i];
if (iter_ctx.first) {
iter_ctx.first = false;
} else {
@@ -2171,14 +2630,12 @@ public:
}
case SET: {
int64_t count = 0;
- std::vector<uint64_t> values(_set.begin(), _set.end());
- std::sort(values.begin(), values.end());
- for (auto it = values.begin(); it != values.end(); ++it) {
- if (*it < range_start || *it >= range_end) {
- continue;
+ for (size_t i = 0; i < _set.size(); ++i) {
+ uint64_t v = _set[i];
+ if (v >= range_start && v < range_end) {
+ ret_bitmap->add(v);
+ ++count;
}
- ret_bitmap->add(*it);
- ++count;
}
return count;
}
@@ -2194,11 +2651,14 @@ public:
*/
int64_t sub_limit(const int64_t& range_start, const int64_t&
cardinality_limit,
BitmapValue* ret_bitmap) const {
+ if (cardinality_limit <= 0) {
+ return 0;
+ }
switch (_type) {
case EMPTY:
return 0;
case SINGLE: {
- //only single value, so range_start must less than _sv
+ // Only single value, so range_start must be less than or equal to
_sv.
if (range_start > _sv) {
return 0;
} else {
@@ -2222,22 +2682,26 @@ public:
return count;
}
case SET: {
- int64_t count = 0;
-
- std::vector<uint64_t> values(_set.begin(), _set.end());
- std::sort(values.begin(), values.end());
- for (auto it = values.begin(); it != values.end(); ++it) {
- if (*it < range_start) {
+ std::array<uint64_t, SET_TYPE_THRESHOLD> values {};
+ size_t count = 0;
+ for (size_t i = 0; i < _set.size(); ++i) {
+ auto v = _set[i];
+ if (v < range_start) {
continue;
}
- if (count < cardinality_limit) {
- ret_bitmap->add(*it);
- ++count;
- } else {
- break;
- }
+ values[count++] = v;
}
- return count;
+
+ const auto output_count = std::min(count,
static_cast<size_t>(cardinality_limit));
+ if (output_count == 0) {
+ return 0;
+ }
+ if (output_count < count) {
+ std::nth_element(values.begin(), values.begin() + output_count,
+ values.begin() + count);
+ }
+ ret_bitmap->add_many(values.data(), output_count);
+ return output_count;
}
}
return 0;
@@ -2250,6 +2714,10 @@ public:
*/
int64_t offset_limit(const int64_t& offset, const int64_t& limit,
BitmapValue* ret_bitmap) const {
+ if (limit <= 0) {
+ return 0;
+ }
+
switch (_type) {
case EMPTY:
return 0;
@@ -2266,7 +2734,7 @@ public:
break;
}
if (_type == BITMAP) {
- if (std::abs(offset) >= _bitmap->cardinality()) {
+ if (std::abs(offset) > _bitmap->cardinality()) {
return 0;
}
int64_t abs_offset = offset;
@@ -2285,7 +2753,7 @@ public:
}
return count;
} else {
- if (std::abs(offset) > _set.size()) {
+ if (std::abs(offset) > _set.size() || limit <= 0) {
return 0;
}
@@ -2293,25 +2761,28 @@ public:
if (offset < 0) {
abs_offset = _set.size() + offset;
}
+ if (abs_offset >= _set.size()) {
+ return 0;
+ }
- std::vector<uint64_t> values(_set.begin(), _set.end());
- std::sort(values.begin(), values.end());
+ std::array<uint64_t, SET_TYPE_THRESHOLD> values {};
+ memcpy(values.data(), _set.data(), sizeof(uint64_t) * _set.size());
- int64_t count = 0;
- size_t index = 0;
- for (auto v : values) {
- if (index < abs_offset) {
- ++index;
- continue;
- }
- if (count == limit || index == values.size()) {
- break;
- }
- ++count;
- ++index;
- ret_bitmap->add(v);
+ const auto start = static_cast<size_t>(abs_offset);
+ const auto end = std::min(_set.size(), start +
static_cast<size_t>(limit));
+ if (end == start) {
+ return 0;
}
- return count;
+
+ if (end < _set.size()) {
+ std::nth_element(values.begin(), values.begin() + end,
+ values.begin() + _set.size());
+ }
+ if (start > 0) {
+ std::nth_element(values.begin(), values.begin() + start,
values.begin() + end);
+ }
+ ret_bitmap->add_many(values.data() + start, end - start);
+ return end - start;
}
}
@@ -2325,16 +2796,17 @@ public:
break;
}
case BITMAP: {
- for (auto it = _bitmap->begin(); it != _bitmap->end(); ++it) {
- data.emplace_back(*it);
- }
+ const auto old_size = data.size();
+ const auto cardinality = _bitmap->cardinality();
+ data.resize(old_size + cardinality);
+ _bitmap->toUint64Array(reinterpret_cast<uint64_t*>(data.data() +
old_size));
break;
}
case SET: {
- std::vector<uint64_t> values(_set.begin(), _set.end());
- std::sort(values.begin(), values.end());
- for (auto v : values) {
- data.emplace_back(v);
+ std::array<uint64_t, SET_TYPE_THRESHOLD> values {};
+ _set.copy_to_sorted(values.data());
+ for (size_t i = 0; i < _set.size(); ++i) {
+ data.emplace_back(values[i]);
}
break;
}
@@ -2354,7 +2826,6 @@ public:
b_iterator begin() const;
b_iterator end() const;
- b_iterator lower_bound(uint64_t val) const;
private:
void _convert_to_smaller_type() {
@@ -2372,6 +2843,7 @@ private:
_sv = _bitmap->minimum();
} else {
_type = SET;
+ _set.clear();
for (auto v : *_bitmap) {
_set.insert(v);
}
@@ -2382,6 +2854,8 @@ private:
_type = SINGLE;
_sv = *_set.begin();
_set.clear();
+ } else if (_set.empty()) {
+ _type = EMPTY;
}
}
}
@@ -2426,14 +2900,12 @@ private:
_is_shared = false;
}
- void _convert_to_bitmap_if_need() {
- if (_type != SET || _set.size() <= SET_TYPE_THRESHOLD) {
- return;
- }
+ // Promote the current SET payload to roaring. Use this only when the
caller has already
+ // decided SET is no longer the right representation, for example add()
detects a new value
+ // cannot fit in the inline set, or an operation intentionally switches to
roaring first.
+ void _convert_set_to_bitmap() {
_prepare_bitmap_for_write();
- for (auto v : _set) {
- _bitmap->add(v);
- }
+ _bitmap->addMany(_set.size(), _set.data());
_type = BITMAP;
_set.clear();
}
@@ -2447,7 +2919,7 @@ private:
uint64_t _sv = 0; // store the single value when _type == SINGLE
// !FIXME: We should rethink the logic about _bitmap and _is_shared
mutable std::shared_ptr<detail::Roaring64Map> _bitmap; // used when _type
== BITMAP
- SetContainer<uint64_t> _set;
+ BitmapSmallSet _set;
BitmapDataType _type {EMPTY};
// Indicate whether the state is shared among multi BitmapValue object
mutable bool _is_shared = true;
@@ -2573,34 +3045,10 @@ public:
bool operator!=(const BitmapValueIterator& other) const { return !(*this
== other); }
- /**
- * Move the iterator to the first value >= `val`.
- */
- BitmapValueIterator& move(uint64_t val) {
- switch (_bitmap._type) {
- case BitmapValue::BitmapDataType::SINGLE:
- if (_sv < val) {
- _end = true;
- }
- break;
- case BitmapValue::BitmapDataType::BITMAP:
- if (!_iter->move(val)) {
- _end = true;
- }
- break;
- case BitmapValue::BitmapDataType::SET: {
- throw Exception(Status::FatalError("BitmapValue with set do not
support move"));
- }
- default:
- break;
- }
- return *this;
- }
-
private:
const BitmapValue& _bitmap;
detail::Roaring64MapSetBitForwardIterator* _iter = nullptr;
- BitmapValue::SetContainer<uint64_t>::const_iterator _set_iter;
+ BitmapSmallSet::const_iterator _set_iter;
uint64_t _sv = 0;
bool _end = false;
};
@@ -2613,25 +3061,4 @@ inline BitmapValueIterator BitmapValue::end() const {
return {*this, true};
}
-inline BitmapValueIterator BitmapValue::lower_bound(uint64_t val) const {
- return BitmapValueIterator(*this).move(val);
-}
-
-inline bool BitmapValue::contains_any(uint64_t left, uint64_t right) const {
- if (left > right) {
- return false;
- }
-
- if (_type == SET) {
- for (auto v : _set) {
- if (v >= left && v <= right) {
- return true;
- }
- }
- return false;
- }
- auto it = lower_bound(left);
- return it != end() && *it <= right;
-}
-
} // namespace doris
diff --git a/be/src/exprs/function/function_bitmap.cpp
b/be/src/exprs/function/function_bitmap.cpp
index 35341f29764..6213066e0e1 100644
--- a/be/src/exprs/function/function_bitmap.cpp
+++ b/be/src/exprs/function/function_bitmap.cpp
@@ -323,10 +323,15 @@ struct BitmapFromArray {
const auto& nested_column_data = static_cast<const
ColumnType&>(nested_column).get_data();
auto size = offset_column_data.size();
res.reserve(size);
- std::vector<uint64_t> bits;
+ // Preserve the nested column's native integer type here.
+ // For Array<Int32>/Array<UInt32>-like inputs can reach the 32-bit
`add_many` fast path
+ // instead of widening every element to uint64_t first.
+ using ValueType = typename ColumnType::value_type;
+ std::vector<ValueType> bits;
for (size_t i = 0; i < size; ++i) {
auto curr_offset = offset_column_data[i];
auto prev_offset = offset_column_data[i - 1];
+ bits.reserve(curr_offset - prev_offset);
for (auto j = prev_offset; j < curr_offset; ++j) {
auto data = nested_column_data[j];
// invaild value
@@ -904,25 +909,19 @@ struct BitmapHasAny {
static void vector_vector(const TData& lvec, const TData& rvec, ResTData&
res) {
size_t size = lvec.size();
for (size_t i = 0; i < size; ++i) {
- auto bitmap = lvec[i];
- bitmap &= rvec[i];
- res[i] = bitmap.cardinality() != 0;
+ res[i] = lvec[i].intersects(rvec[i]);
}
}
static void vector_scalar(const TData& lvec, const BitmapValue& rval,
ResTData& res) {
size_t size = lvec.size();
for (size_t i = 0; i < size; ++i) {
- auto bitmap = lvec[i];
- bitmap &= rval;
- res[i] = bitmap.cardinality() != 0;
+ res[i] = lvec[i].intersects(rval);
}
}
static void scalar_vector(const BitmapValue& lval, const TData& rvec,
ResTData& res) {
size_t size = rvec.size();
for (size_t i = 0; i < size; ++i) {
- auto bitmap = lval;
- bitmap &= rvec[i];
- res[i] = bitmap.cardinality() != 0;
+ res[i] = lval.intersects(rvec[i]);
}
}
};
@@ -942,28 +941,19 @@ struct BitmapHasAll {
static void vector_vector(const TData& lvec, const TData& rvec, ResTData&
res) {
size_t size = lvec.size();
for (size_t i = 0; i < size; ++i) {
- uint64_t lhs_cardinality = lvec[i].cardinality();
- auto bitmap = lvec[i];
- bitmap |= rvec[i];
- res[i] = bitmap.cardinality() == lhs_cardinality;
+ res[i] = lvec[i].contains_all(rvec[i]);
}
}
static void vector_scalar(const TData& lvec, const BitmapValue& rval,
ResTData& res) {
size_t size = lvec.size();
for (size_t i = 0; i < size; ++i) {
- uint64_t lhs_cardinality = lvec[i].cardinality();
- auto bitmap = lvec[i];
- bitmap |= rval;
- res[i] = bitmap.cardinality() == lhs_cardinality;
+ res[i] = lvec[i].contains_all(rval);
}
}
static void scalar_vector(const BitmapValue& lval, const TData& rvec,
ResTData& res) {
size_t size = rvec.size();
- uint64_t lhs_cardinality = lval.cardinality();
for (size_t i = 0; i < size; ++i) {
- auto bitmap = lval;
- bitmap |= rvec[i];
- res[i] = bitmap.cardinality() == lhs_cardinality;
+ res[i] = lval.contains_all(rvec[i]);
}
}
};
diff --git a/be/src/exprs/function/function_bitmap_min_or_max.h
b/be/src/exprs/function/function_bitmap_min_or_max.h
index 2132e6a44d4..76ebf4616b0 100644
--- a/be/src/exprs/function/function_bitmap_min_or_max.h
+++ b/be/src/exprs/function/function_bitmap_min_or_max.h
@@ -68,20 +68,24 @@ public:
private:
void execute_straight(const ColumnBitmap* date_column, ColumnInt64*
result_column,
NullMap& result_null_map, size_t input_rows_count)
const {
+ const auto& data = date_column->get_data();
+ auto& result_data = result_column->get_data();
+ result_data.resize(input_rows_count);
+
for (size_t i = 0; i < input_rows_count; i++) {
if (result_null_map[i]) {
- result_column->insert_default();
+ result_data[i] = 0;
continue;
}
- BitmapValue value = date_column->get_element(i);
+ const BitmapValue& value = data[i];
if (!value.cardinality()) {
result_null_map[i] = true;
- result_column->insert_default();
+ result_data[i] = 0;
continue;
}
-
result_column->insert(Field::create_field<TYPE_BIGINT>(Impl::calculate(value)));
+ result_data[i] = Impl::calculate(value);
}
}
};
diff --git a/be/src/util/coding.h b/be/src/util/coding.h
index 48a2a8532b0..26d6a2f827c 100644
--- a/be/src/util/coding.h
+++ b/be/src/util/coding.h
@@ -71,6 +71,21 @@ inline uint64_t decode_fixed64_le(const uint8_t* buf) {
return to_endian<std::endian::little>(res);
}
+inline void decode_fixed64_le_array(uint64_t* dst, const void* src, size_t n) {
+ if (n == 0) {
+ return;
+ }
+ if constexpr (std::endian::native == std::endian::little) {
+ memcpy(dst, src, sizeof(uint64_t) * n);
+ } else {
+ const auto* ptr = reinterpret_cast<const uint8_t*>(src);
+ for (size_t i = 0; i < n; ++i) {
+ dst[i] = decode_fixed64_le(ptr);
+ ptr += sizeof(uint64_t);
+ }
+ }
+}
+
inline uint128_t decode_fixed128_le(const uint8_t* buf) {
uint128_t res;
memcpy(&res, buf, sizeof(res));
diff --git a/be/test/core/block/column_complex_test.cpp
b/be/test/core/block/column_complex_test.cpp
index 3a2629474f9..4ced4c4f8e7 100644
--- a/be/test/core/block/column_complex_test.cpp
+++ b/be/test/core/block/column_complex_test.cpp
@@ -60,10 +60,10 @@ TEST(ColumnComplexTest, GetDataAtTest) {
column_bitmap_verify->insert_default();
column_hll_verify->insert_default();
column_quantile_state_verify->insert_many_defaults(1);
- ASSERT_EQ(column_bitmap_verify->byte_size(), 80);
+ ASSERT_EQ(column_bitmap_verify->byte_size(), sizeof(BitmapValue));
ASSERT_EQ(column_hll_verify->byte_size(), 64);
ASSERT_EQ(column_quantile_state_verify->byte_size(), 64);
- ASSERT_EQ(column_bitmap_verify->allocated_bytes(), 80);
+ ASSERT_EQ(column_bitmap_verify->allocated_bytes(), sizeof(BitmapValue));
ASSERT_EQ(column_hll_verify->allocated_bytes(), 64);
ASSERT_EQ(column_quantile_state_verify->allocated_bytes(), 64);
std::cout << "1. test byte_size/allocated_bytes empty success" <<
std::endl;
diff --git a/be/test/core/value/bitmap_value_test.cpp
b/be/test/core/value/bitmap_value_test.cpp
index 1f89539c366..b281d71a443 100644
--- a/be/test/core/value/bitmap_value_test.cpp
+++ b/be/test/core/value/bitmap_value_test.cpp
@@ -78,6 +78,27 @@ TEST(BitmapValueTest, Roaring64Map_ctors) {
EXPECT_FALSE(roaring64_map3.contains(uint32_t(0)));
}
+TEST(BitmapValueTest, Roaring64Map_intersect) {
+ constexpr uint64_t high32 = uint64_t {1} << 32;
+ const std::vector<uint64_t> one_map {1, 2};
+ const std::vector<uint64_t> one_map_overlap {2, 3};
+ const std::vector<uint64_t> one_map_disjoint {3, 4};
+ const std::vector<uint64_t> other_map {high32 + 1, high32 + 2};
+ const std::vector<uint64_t> two_maps {1, 2, high32 + 1, high32 + 2};
+
+ detail::Roaring64Map bitmap_one_map(one_map.size(), one_map.data());
+ detail::Roaring64Map bitmap_overlap(one_map_overlap.size(),
one_map_overlap.data());
+ detail::Roaring64Map bitmap_disjoint(one_map_disjoint.size(),
one_map_disjoint.data());
+ detail::Roaring64Map bitmap_other_map(other_map.size(), other_map.data());
+ detail::Roaring64Map bitmap_two_maps(two_maps.size(), two_maps.data());
+
+ EXPECT_TRUE(bitmap_one_map.intersect(bitmap_overlap));
+ EXPECT_FALSE(bitmap_one_map.intersect(bitmap_disjoint));
+ EXPECT_FALSE(bitmap_one_map.intersect(bitmap_other_map));
+ EXPECT_TRUE(bitmap_two_maps.intersect(bitmap_one_map));
+ EXPECT_TRUE(bitmap_one_map.intersect(bitmap_two_maps));
+}
+
TEST(BitmapValueTest, Roaring64Map_add_remove) {
detail::Roaring64Map roaring64_map;
const std::vector<uint32_t> values({1, 3, 5, 7, 9, 2, 4, 6, 8, 1, 8, 9});
@@ -252,17 +273,6 @@ TEST(BitmapValueTest, Roaring64Map_iterators) {
EXPECT_TRUE(iter != end);
iter++;
}
-
- iter = roaring64_map.begin();
- EXPECT_TRUE(iter.move(2));
- EXPECT_TRUE(iter.move(4294967296));
- EXPECT_FALSE(iter.move(4294967299));
-
- iter = roaring64_map.begin();
- auto iter2 = roaring64_map.begin();
- iter.move(3);
- iter2.move(3);
- EXPECT_TRUE(iter == iter2);
}
TEST(BitmapValueTest, set) {
@@ -359,6 +369,80 @@ TEST(BitmapValueTest, add) {
config::enable_set_in_bitmap_value = false;
}
+TEST(BitmapValueTest, small_set_try_insert_many_over_capacity) {
+ BitmapSmallSet set;
+ const uint64_t initial_values[] = {1, 2, 3};
+ const uint64_t duplicate_values[] = {1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3,
1, 2, 3,
+ 1, 2, 3, 1, 2, 3, 1, 2, 3, 1, 2, 3,
1, 2, 3};
+ const uint64_t new_values[] = {4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
15, 16, 17, 18,
+ 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29,
30, 31, 32, 33};
+
+ EXPECT_TRUE(set.try_insert_many(initial_values,
std::size(initial_values)));
+ EXPECT_TRUE(set.try_insert_many(duplicate_values,
std::size(duplicate_values)));
+ EXPECT_EQ(3, set.size());
+ EXPECT_FALSE(set.try_insert_many(new_values, std::size(new_values)));
+ EXPECT_EQ(3, set.size());
+}
+
+TEST(BitmapValueTest, add_many_all_representations) {
+ const auto old_enable_set = config::enable_set_in_bitmap_value;
+ config::enable_set_in_bitmap_value = true;
+
+ const uint64_t one_value[] = {1};
+ const uint64_t small_values[] = {1, 2, 2, 3};
+ std::vector<uint64_t> bitmap_values(33);
+ std::iota(bitmap_values.begin(), bitmap_values.end(), 1);
+
+ BitmapValue empty;
+ empty.add_many(one_value, 0);
+ EXPECT_TRUE(empty.empty());
+ empty.add_many(one_value, std::size(one_value));
+ EXPECT_EQ(BitmapValue::SINGLE, empty._type);
+
+ BitmapValue empty_to_set;
+ empty_to_set.add_many(small_values, std::size(small_values));
+ EXPECT_EQ(BitmapValue::SET, empty_to_set._type);
+ EXPECT_EQ(3, empty_to_set.cardinality());
+
+ BitmapValue empty_to_bitmap;
+ empty_to_bitmap.add_many(bitmap_values.data(), bitmap_values.size());
+ EXPECT_EQ(BitmapValue::BITMAP, empty_to_bitmap._type);
+
+ BitmapValue single_to_set(1);
+ single_to_set.add_many(small_values, std::size(small_values));
+ EXPECT_EQ(BitmapValue::SET, single_to_set._type);
+ EXPECT_EQ(3, single_to_set.cardinality());
+
+ BitmapValue single_to_bitmap(uint64_t {0});
+ single_to_bitmap.add_many(bitmap_values.data(), bitmap_values.size());
+ EXPECT_EQ(BitmapValue::BITMAP, single_to_bitmap._type);
+ EXPECT_EQ(34, single_to_bitmap.cardinality());
+
+ empty_to_bitmap.add_many(one_value, std::size(one_value));
+ EXPECT_EQ(33, empty_to_bitmap.cardinality());
+
+ std::vector<uint64_t> set_values(31);
+ std::iota(set_values.begin(), set_values.end(), 0);
+ BitmapValue set_to_bitmap(set_values);
+ const uint64_t duplicates[] = {0, 1};
+ const uint64_t overflow_values[] = {31, 33};
+ set_to_bitmap.add_many(duplicates, std::size(duplicates));
+ EXPECT_EQ(BitmapValue::SET, set_to_bitmap._type);
+ set_to_bitmap.add_many(overflow_values, std::size(overflow_values));
+ EXPECT_EQ(BitmapValue::BITMAP, set_to_bitmap._type);
+ EXPECT_EQ(33, set_to_bitmap.cardinality());
+
+ config::enable_set_in_bitmap_value = false;
+ BitmapValue no_set_empty;
+ no_set_empty.add_many(small_values, std::size(small_values));
+ EXPECT_EQ(BitmapValue::BITMAP, no_set_empty._type);
+ BitmapValue no_set_single(uint64_t {0});
+ no_set_single.add_many(one_value, std::size(one_value));
+ EXPECT_EQ(BitmapValue::BITMAP, no_set_single._type);
+
+ config::enable_set_in_bitmap_value = old_enable_set;
+}
+
void check_bitmap_value_operator(const BitmapValue& left, const BitmapValue&
right) {
auto left_cardinality = left.cardinality();
auto right_cardinality = right.cardinality();
@@ -492,6 +576,88 @@ TEST(BitmapValueTest, operators) {
config::enable_set_in_bitmap_value = false;
}
+TEST(BitmapValueTest, bitmap_operator_cow_with_set_rhs) {
+ config::enable_set_in_bitmap_value = true;
+
+ std::vector<uint64_t> values(128);
+ std::iota(values.begin(), values.end(), 0);
+
+ BitmapValue bitmap;
+ bitmap.add_many(values.data(), values.size());
+
+ BitmapValue rhs_set({1, 2, 200});
+
+ BitmapValue copied = bitmap;
+ bitmap -= rhs_set;
+ EXPECT_FALSE(bitmap.contains(1));
+ EXPECT_FALSE(bitmap.contains(2));
+ EXPECT_TRUE(copied.contains(1));
+ EXPECT_TRUE(copied.contains(2));
+
+ bitmap = copied;
+ copied = bitmap;
+ bitmap |= rhs_set;
+ EXPECT_TRUE(copied.contains(1));
+ EXPECT_TRUE(copied.contains(2));
+ EXPECT_FALSE(copied.contains(200));
+
+ bitmap = copied;
+ copied = bitmap;
+ bitmap ^= BitmapValue(1);
+ EXPECT_FALSE(bitmap.contains(1));
+ EXPECT_TRUE(copied.contains(1));
+
+ config::enable_set_in_bitmap_value = false;
+}
+
+TEST(BitmapValueTest, bitmap_xor_single_and_set) {
+ config::enable_set_in_bitmap_value = true;
+
+ BitmapValue lhs_single(1);
+ lhs_single ^= BitmapValue(2);
+ EXPECT_STREQ("1,2", lhs_single.to_string().c_str());
+
+ lhs_single = BitmapValue(1);
+ lhs_single ^= BitmapValue({2, 3});
+ EXPECT_STREQ("1,2,3", lhs_single.to_string().c_str());
+
+ lhs_single = BitmapValue(1);
+ lhs_single ^= BitmapValue({1, 2});
+ EXPECT_STREQ("2", lhs_single.to_string().c_str());
+
+ config::enable_set_in_bitmap_value = false;
+}
+
+TEST(BitmapValueTest, bitmap_xor_same_set_returns_empty) {
+ config::enable_set_in_bitmap_value = true;
+
+ BitmapValue lhs({1, 2});
+ lhs ^= BitmapValue({1, 2});
+
+ EXPECT_EQ(0, lhs.cardinality());
+ EXPECT_EQ(BitmapTypeCode::EMPTY, lhs.get_type_code());
+
+ config::enable_set_in_bitmap_value = false;
+}
+
+TEST(BitmapValueTest, bitmap_xor_set_bitmap_clear_stale_set) {
+ config::enable_set_in_bitmap_value = true;
+
+ BitmapValue lhs({1, 2});
+ std::vector<uint64_t> rhs_values(33);
+ std::iota(rhs_values.begin(), rhs_values.end(), 1);
+ BitmapValue rhs(rhs_values);
+
+ lhs ^= rhs;
+ for (uint64_t v = 3; v <= 32; ++v) {
+ lhs.remove(v);
+ }
+
+ EXPECT_STREQ("33", lhs.to_string().c_str());
+
+ config::enable_set_in_bitmap_value = false;
+}
+
void check_bitmap_equal(const BitmapValue& left, const BitmapValue& right) {
EXPECT_EQ(left.cardinality(), right.cardinality());
EXPECT_EQ(left.minimum(), right.minimum());
@@ -721,6 +887,18 @@ TEST(BitmapValueTest, sub_range_limit) {
config::enable_set_in_bitmap_value = false;
}
+TEST(BitmapValueTest, offset_limit_negative_cardinality_for_set) {
+ config::enable_set_in_bitmap_value = true;
+
+ BitmapValue bitmap({1, 2, 3});
+ BitmapValue out;
+ auto ret = bitmap.offset_limit(-3, 2, &out);
+ EXPECT_EQ(ret, 2);
+ EXPECT_STREQ("1,2", out.to_string().c_str());
+
+ config::enable_set_in_bitmap_value = false;
+}
+
void bitmap_checker_for_all_type(const std::function<void(const
BitmapValue&)>& checker) {
BitmapValue bitmap_empty;
BitmapValue bitmap_single(1);
@@ -913,6 +1091,24 @@ TEST(BitmapValueTest, bitmap_union) {
config::enable_set_in_bitmap_value = old_config;
}
+TEST(BitmapValueTest, fastunion_set_with_bitmap) {
+ const auto old_enable_set = config::enable_set_in_bitmap_value;
+ config::enable_set_in_bitmap_value = true;
+
+ std::vector<uint64_t> bitmap_values(33);
+ std::iota(bitmap_values.begin(), bitmap_values.end(), 3);
+ BitmapValue bitmap(bitmap_values);
+ BitmapValue set({1, 2});
+
+ set.fastunion({&bitmap});
+ EXPECT_EQ(BitmapValue::BITMAP, set._type);
+ EXPECT_EQ(35, set.cardinality());
+ EXPECT_TRUE(set.contains(1));
+ EXPECT_TRUE(set.contains(35));
+
+ config::enable_set_in_bitmap_value = old_enable_set;
+}
+
TEST(BitmapValueTest, bitmap_intersect) {
BitmapValue empty;
BitmapValue single(1024);
@@ -976,6 +1172,51 @@ TEST(BitmapValueTest, bitmap_intersect) {
EXPECT_EQ(2, bitmap6.cardinality());
}
+TEST(BitmapValueTest, bitmap_intersects_all_representations) {
+ const auto old_enable_set = config::enable_set_in_bitmap_value;
+
+ config::enable_set_in_bitmap_value = false;
+ std::vector<uint64_t> bitmap_values(33);
+ std::iota(bitmap_values.begin(), bitmap_values.end(), 0);
+ std::vector<uint64_t> bitmap_overlap_values(33);
+ std::iota(bitmap_overlap_values.begin(), bitmap_overlap_values.end(), 32);
+ std::vector<uint64_t> bitmap_disjoint_values(33);
+ std::iota(bitmap_disjoint_values.begin(), bitmap_disjoint_values.end(),
100);
+ BitmapValue bitmap(bitmap_values);
+ BitmapValue bitmap_overlap(bitmap_overlap_values);
+ BitmapValue bitmap_disjoint(bitmap_disjoint_values);
+ BitmapValue single(1);
+ BitmapValue single_absent(200);
+ BitmapValue empty;
+
+ config::enable_set_in_bitmap_value = true;
+ BitmapValue set({1, 2, 3});
+ BitmapValue set_disjoint({4, 5});
+
+ EXPECT_FALSE(bitmap.intersects(empty));
+ EXPECT_FALSE(empty.intersects(single));
+ EXPECT_TRUE(bitmap.intersects(single));
+ EXPECT_TRUE(set.intersects(single));
+
+ EXPECT_FALSE(empty.intersects(bitmap));
+ EXPECT_TRUE(single.intersects(bitmap));
+ EXPECT_FALSE(single_absent.intersects(bitmap));
+ EXPECT_TRUE(bitmap.intersects(bitmap_overlap));
+ EXPECT_FALSE(bitmap.intersects(bitmap_disjoint));
+ EXPECT_TRUE(set.intersects(bitmap));
+ EXPECT_FALSE(set_disjoint.intersects(bitmap_disjoint));
+
+ EXPECT_FALSE(empty.intersects(set));
+ EXPECT_TRUE(single.intersects(set));
+ EXPECT_FALSE(single_absent.intersects(set));
+ EXPECT_TRUE(bitmap.intersects(set));
+ EXPECT_FALSE(bitmap_disjoint.intersects(set));
+ EXPECT_TRUE(set.intersects(BitmapValue({2, 4})));
+ EXPECT_FALSE(set.intersects(set_disjoint));
+
+ config::enable_set_in_bitmap_value = old_enable_set;
+}
+
std::string convert_bitmap_to_string(BitmapValue& bitmap) {
std::string buf;
buf.resize(bitmap.getSizeInBytes());
@@ -995,6 +1236,36 @@ BitmapValue deserialize_bitmap_from_string(const
std::string& buffer) {
return bitmap;
}
+TEST(BitmapValueTest, deserialize_reused_bitmap_to_set_then_promote) {
+ const auto old_enable_set = config::enable_set_in_bitmap_value;
+ config::enable_set_in_bitmap_value = true;
+
+ std::vector<uint64_t> stale_values(41);
+ std::iota(stale_values.begin(), stale_values.end(), 0);
+ BitmapValue reused(stale_values);
+ EXPECT_EQ(BitmapValue::BITMAP, reused._type);
+
+ std::vector<uint64_t> set_values {100, 101};
+ BitmapValue set_bitmap(set_values);
+ EXPECT_EQ(BitmapValue::SET, set_bitmap._type);
+ std::string set_buffer = convert_bitmap_to_string(set_bitmap);
+
+ EXPECT_TRUE(reused.deserialize(set_buffer.data()));
+ EXPECT_EQ(BitmapValue::SET, reused._type);
+ check_bitmap_values(reused, set_values);
+
+ std::vector<uint64_t> promote_values(31);
+ std::iota(promote_values.begin(), promote_values.end(), 102);
+ reused.add_many(promote_values.data(), promote_values.size());
+
+ std::vector<uint64_t> expected {100, 101};
+ expected.insert(expected.end(), promote_values.begin(),
promote_values.end());
+ EXPECT_EQ(BitmapValue::BITMAP, reused._type);
+ check_bitmap_values(reused, expected);
+
+ config::enable_set_in_bitmap_value = old_enable_set;
+}
+
TEST(BitmapValueTest, bitmap_serde) {
auto old_enable_set = config::enable_set_in_bitmap_value;
auto old_serialize_version = config::bitmap_serialize_version;
@@ -1293,6 +1564,162 @@ TEST(BitmapValueTest, bitmap_value_iterator_test) {
}
}
+TEST(BitmapValueTest, contains_all_ignore_internal_representation) {
+ bool old_enable_set = config::enable_set_in_bitmap_value;
+
+ config::enable_set_in_bitmap_value = false;
+ BitmapValue lhs_bitmap({1, 2, 3});
+ BitmapValue rhs_bitmap({1, 2});
+ EXPECT_EQ(BitmapValue::BITMAP, lhs_bitmap._type);
+ EXPECT_EQ(BitmapValue::BITMAP, rhs_bitmap._type);
+
+ config::enable_set_in_bitmap_value = true;
+ BitmapValue lhs_set({1, 2, 3});
+ BitmapValue rhs_set({1, 2});
+ EXPECT_EQ(BitmapValue::SET, lhs_set._type);
+ EXPECT_EQ(BitmapValue::SET, rhs_set._type);
+
+ EXPECT_TRUE(lhs_set.contains_all(rhs_bitmap));
+ EXPECT_TRUE(lhs_bitmap.contains_all(rhs_set));
+ EXPECT_TRUE(lhs_bitmap.contains_all(rhs_bitmap));
+ EXPECT_FALSE(rhs_set.contains_all(lhs_bitmap));
+
+ BitmapValue empty_set_rhs({1, 3});
+ empty_set_rhs &= BitmapValue({2, 4});
+ EXPECT_EQ(0, empty_set_rhs.cardinality());
+ EXPECT_TRUE(lhs_set.contains_all(empty_set_rhs));
+ EXPECT_TRUE(BitmapValue().contains_all(empty_set_rhs));
+
+ config::enable_set_in_bitmap_value = old_enable_set;
+}
+
+TEST(BitmapValueTest, contains_all_representation_branches) {
+ const auto old_enable_set = config::enable_set_in_bitmap_value;
+
+ config::enable_set_in_bitmap_value = false;
+ BitmapValue bitmap({1, 2, 3});
+ const uint64_t duplicated_one[] = {1, 1};
+ BitmapValue bitmap_one;
+ bitmap_one.add_many(duplicated_one, std::size(duplicated_one));
+ BitmapValue bitmap_two({1, 2});
+ BitmapValue bitmap_missing({1, 4});
+ BitmapValue single(1);
+
+ config::enable_set_in_bitmap_value = true;
+ BitmapValue set({1, 2, 3});
+ BitmapValue set_one;
+ set_one.add(1);
+ BitmapValue set_missing({1, 4});
+
+ EXPECT_FALSE(BitmapValue().contains_all(bitmap_two));
+ EXPECT_TRUE(single.contains_all(BitmapValue(1)));
+ EXPECT_FALSE(single.contains_all(BitmapValue(2)));
+ EXPECT_TRUE(single.contains_all(bitmap_one));
+ EXPECT_FALSE(single.contains_all(bitmap_two));
+ EXPECT_TRUE(set.contains_all(bitmap_two));
+ EXPECT_FALSE(set.contains_all(bitmap_missing));
+ EXPECT_TRUE(bitmap.contains_all(set_one));
+ EXPECT_FALSE(bitmap.contains_all(set_missing));
+ EXPECT_TRUE(single.contains_all(set_one));
+ EXPECT_FALSE(single.contains_all(set_missing));
+
+ config::enable_set_in_bitmap_value = old_enable_set;
+}
+
+TEST(BitmapValueTest, deserialize_set_duplicate_values) {
+ bool old_enable_set = config::enable_set_in_bitmap_value;
+ config::enable_set_in_bitmap_value = true;
+
+ {
+ char data[] = {static_cast<char>(BitmapTypeCode::SET),
+ 2,
+ 7,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 7,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0};
+ BitmapValue bitmap;
+ EXPECT_THROW(bitmap.deserialize(data), Exception);
+ }
+
+ {
+ char data[] = {static_cast<char>(BitmapTypeCode::SET_V2),
+ 2,
+ 0,
+ 0,
+ 0,
+ 7,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 7,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0};
+ BitmapValue bitmap;
+ EXPECT_TRUE(bitmap.deserialize(data));
+ EXPECT_EQ(BitmapValue::SET, bitmap._type);
+ EXPECT_EQ(1, bitmap.cardinality());
+ EXPECT_TRUE(bitmap.contains(7));
+ }
+
+ config::enable_set_in_bitmap_value = old_enable_set;
+}
+
+TEST(BitmapValueTest, deserialize_set_v2_to_bitmap) {
+ const auto old_enable_set = config::enable_set_in_bitmap_value;
+ config::enable_set_in_bitmap_value = false;
+
+ char data[] = {static_cast<char>(BitmapTypeCode::SET_V2),
+ 2,
+ 0,
+ 0,
+ 0,
+ 7,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 9,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0,
+ 0};
+ BitmapValue bitmap;
+ EXPECT_TRUE(bitmap.deserialize(data));
+ EXPECT_EQ(BitmapValue::BITMAP, bitmap._type);
+ EXPECT_EQ(2, bitmap.cardinality());
+ EXPECT_TRUE(bitmap.contains(7));
+ EXPECT_TRUE(bitmap.contains(9));
+
+ config::enable_set_in_bitmap_value = old_enable_set;
+}
+
TEST(BitmapValueTest, invalid_data) {
BitmapValue bitmap;
char data[] = {0x02, static_cast<char>(0xff), 0x03};
diff --git
a/regression-test/data/query_p0/sql_functions/bitmap_functions/test_bitmap_function.out
b/regression-test/data/query_p0/sql_functions/bitmap_functions/test_bitmap_function.out
index a162f381a4a..8fa306872e2 100644
---
a/regression-test/data/query_p0/sql_functions/bitmap_functions/test_bitmap_function.out
+++
b/regression-test/data/query_p0/sql_functions/bitmap_functions/test_bitmap_function.out
@@ -47,6 +47,27 @@ true
-- !sql --
false
+-- !sql --
+false
+
+-- !sql --
+true
+
+-- !sql --
+true
+
+-- !sql --
+true
+
+-- !sql --
+false
+
+-- !sql --
+true
+
+-- !sql --
+false
+
-- !sql_bitmap_hash1 --
1
diff --git
a/regression-test/suites/query_p0/sql_functions/bitmap_functions/test_bitmap_function.groovy
b/regression-test/suites/query_p0/sql_functions/bitmap_functions/test_bitmap_function.groovy
index fabfef875d3..2c6640a6515 100644
---
a/regression-test/suites/query_p0/sql_functions/bitmap_functions/test_bitmap_function.groovy
+++
b/regression-test/suites/query_p0/sql_functions/bitmap_functions/test_bitmap_function.groovy
@@ -16,6 +16,12 @@
// under the License.
suite("test_bitmap_function") {
+ def low_bitmap =
"0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32"
+ def overlap_bitmap =
"32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64"
+ def disjoint_bitmap =
"33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65"
+ def high_bitmap =
"4294967296,4294967297,4294967298,4294967299,4294967300,4294967301,4294967302,4294967303,4294967304,4294967305,4294967306,4294967307,4294967308,4294967309,4294967310,4294967311,4294967312,4294967313,4294967314,4294967315,4294967316,4294967317,4294967318,4294967319,4294967320,4294967321,4294967322,4294967323,4294967324,4294967325,4294967326,4294967327,4294967328"
+ def two_map_bitmap = "${low_bitmap},${high_bitmap}"
+
// BITMAP_AND
qt_sql """ select bitmap_count(bitmap_and(to_bitmap(1), to_bitmap(2))) cnt
"""
qt_sql """ select bitmap_count(bitmap_and(to_bitmap(1), to_bitmap(1))) cnt
"""
@@ -39,10 +45,17 @@ suite("test_bitmap_function") {
// BITMAP_HAS_ANY
qt_sql """ select bitmap_has_any(to_bitmap(1),to_bitmap(2)) cnt """
qt_sql """ select bitmap_has_any(to_bitmap(1),to_bitmap(1)) cnt """
+ qt_sql """ select bitmap_has_any(bitmap_from_string('${low_bitmap}'),
bitmap_from_string('${overlap_bitmap}')) cnt """
+ qt_sql """ select bitmap_has_any(bitmap_from_string('${low_bitmap}'),
bitmap_from_string('${disjoint_bitmap}')) cnt """
+ qt_sql """ select bitmap_has_any(bitmap_from_string('${low_bitmap}'),
bitmap_from_string('${high_bitmap}')) cnt """
+ qt_sql """ select bitmap_has_any(bitmap_from_string('${two_map_bitmap}'),
bitmap_from_string('${low_bitmap}')) cnt """
+ qt_sql """ select bitmap_has_any(bitmap_from_string('${low_bitmap}'),
bitmap_from_string('${two_map_bitmap}')) cnt """
// BITMAP_HAS_ALL
qt_sql """ select bitmap_has_all(bitmap_from_string("0, 1, 2"),
bitmap_from_string("1, 2")) cnt """
qt_sql """ select bitmap_has_all(bitmap_empty(), bitmap_from_string("1,
2")) cnt """
+ qt_sql """ select bitmap_has_all(bitmap_from_string('${low_bitmap}'),
bitmap_from_string('${low_bitmap}')) cnt """
+ qt_sql """ select bitmap_has_all(bitmap_from_string('${low_bitmap}'),
bitmap_from_string('${overlap_bitmap}')) cnt """
// BITMAP_HASH
qt_sql_bitmap_hash1 """ select bitmap_count(bitmap_hash('hello')) """
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]