datasketches 0.5.2 → 0.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/ext/datasketches/theta_wrapper.cpp +1 -1
- data/lib/datasketches/version.rb +1 -1
- data/vendor/datasketches-cpp/CMakeLists.txt +5 -3
- data/vendor/datasketches-cpp/CODE_OF_CONDUCT.md +1 -1
- data/vendor/datasketches-cpp/LICENSE +14 -0
- data/vendor/datasketches-cpp/README.md +67 -73
- data/vendor/datasketches-cpp/benchmarks/CMakeLists.txt +52 -0
- data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch.cpp +153 -0
- data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch_serialization.cpp +161 -0
- data/vendor/datasketches-cpp/common/CMakeLists.txt +1 -0
- data/vendor/datasketches-cpp/common/include/binomial_bounds.hpp +2 -2
- data/vendor/datasketches-cpp/common/include/fdlibm_log.hpp +101 -0
- data/vendor/datasketches-cpp/common/include/serde.hpp +6 -0
- data/vendor/datasketches-cpp/common/test/CMakeLists.txt +44 -2
- data/vendor/datasketches-cpp/common/test/binomial_bounds_test.cpp +279 -0
- data/vendor/datasketches-cpp/common/test/deserialize_hardening_test.cpp +188 -0
- data/vendor/datasketches-cpp/count/include/count_min.hpp +17 -4
- data/vendor/datasketches-cpp/count/include/count_min_impl.hpp +65 -83
- data/vendor/datasketches-cpp/count/test/count_min_test.cpp +63 -7
- data/vendor/datasketches-cpp/cpc/include/compression_data.hpp +2 -0
- data/vendor/datasketches-cpp/cpc/include/cpc_compressor_impl.hpp +16 -7
- data/vendor/datasketches-cpp/cpc/include/cpc_sketch.hpp +1 -0
- data/vendor/datasketches-cpp/cpc/include/cpc_sketch_impl.hpp +54 -36
- data/vendor/datasketches-cpp/cpc/include/cpc_union_impl.hpp +27 -27
- data/vendor/datasketches-cpp/cpc/include/cpc_util.hpp +7 -7
- data/vendor/datasketches-cpp/cpc/include/icon_estimator.hpp +5 -5
- data/vendor/datasketches-cpp/cpc/include/u32_table_impl.hpp +16 -16
- data/vendor/datasketches-cpp/cpc/test/cpc_sketch_test.cpp +35 -0
- data/vendor/datasketches-cpp/fi/include/frequent_items_sketch.hpp +28 -3
- data/vendor/datasketches-cpp/fi/include/frequent_items_sketch_impl.hpp +41 -25
- data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map.hpp +3 -1
- data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map_impl.hpp +10 -5
- data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_serialize_for_java.cpp +24 -0
- data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_test.cpp +116 -0
- data/vendor/datasketches-cpp/filters/include/bloom_filter.hpp +1 -1
- data/vendor/datasketches-cpp/filters/include/bloom_filter_impl.hpp +32 -12
- data/vendor/datasketches-cpp/filters/test/bloom_filter_test.cpp +28 -1
- data/vendor/datasketches-cpp/hll/include/CouponHashSet-internal.hpp +1 -2
- data/vendor/datasketches-cpp/hll/include/CouponList-internal.hpp +19 -5
- data/vendor/datasketches-cpp/hll/include/CubicInterpolation-internal.hpp +4 -4
- data/vendor/datasketches-cpp/hll/include/HarmonicNumbers-internal.hpp +2 -1
- data/vendor/datasketches-cpp/hll/include/Hll4Array-internal.hpp +4 -4
- data/vendor/datasketches-cpp/hll/include/Hll4Array.hpp +1 -1
- data/vendor/datasketches-cpp/hll/include/Hll6Array-internal.hpp +3 -3
- data/vendor/datasketches-cpp/hll/include/Hll6Array.hpp +1 -1
- data/vendor/datasketches-cpp/hll/include/Hll8Array-internal.hpp +6 -3
- data/vendor/datasketches-cpp/hll/include/Hll8Array.hpp +1 -1
- data/vendor/datasketches-cpp/hll/include/HllArray-internal.hpp +35 -29
- data/vendor/datasketches-cpp/hll/include/HllArray.hpp +1 -1
- data/vendor/datasketches-cpp/hll/include/HllSketch-internal.hpp +3 -5
- data/vendor/datasketches-cpp/hll/include/HllSketchImpl-internal.hpp +5 -11
- data/vendor/datasketches-cpp/hll/include/HllSketchImpl.hpp +2 -4
- data/vendor/datasketches-cpp/hll/include/HllSketchImplFactory.hpp +11 -10
- data/vendor/datasketches-cpp/hll/include/HllUnion-internal.hpp +17 -8
- data/vendor/datasketches-cpp/hll/include/HllUtil.hpp +12 -3
- data/vendor/datasketches-cpp/hll/include/coupon_iterator-internal.hpp +2 -2
- data/vendor/datasketches-cpp/hll/include/coupon_iterator.hpp +3 -0
- data/vendor/datasketches-cpp/hll/include/hll.hpp +11 -4
- data/vendor/datasketches-cpp/hll/include/hll.private.hpp +19 -0
- data/vendor/datasketches-cpp/hll/test/CMakeLists.txt +2 -0
- data/vendor/datasketches-cpp/hll/test/CouponListTest.cpp +64 -0
- data/vendor/datasketches-cpp/hll/test/HllFullSizeTest.cpp +137 -0
- data/vendor/datasketches-cpp/hll/test/HllKxqRebuildTest.cpp +150 -0
- data/vendor/datasketches-cpp/hll/test/HllSketchTest.cpp +3 -3
- data/vendor/datasketches-cpp/hll/test/HllUnionTest.cpp +71 -1
- data/vendor/datasketches-cpp/kll/include/kll_helper_impl.hpp +10 -10
- data/vendor/datasketches-cpp/kll/include/kll_sketch.hpp +10 -1
- data/vendor/datasketches-cpp/kll/include/kll_sketch_impl.hpp +33 -24
- data/vendor/datasketches-cpp/kll/test/kll_sketch_deserialize_from_java_test.cpp +24 -0
- data/vendor/datasketches-cpp/kll/test/kll_sketch_serialize_for_java.cpp +10 -0
- data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch.hpp +11 -2
- data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch_impl.hpp +24 -15
- data/vendor/datasketches-cpp/req/include/req_sketch.hpp +9 -0
- data/vendor/datasketches-cpp/req/include/req_sketch_impl.hpp +24 -15
- data/vendor/datasketches-cpp/req/test/req_sketch_deserialize_from_java_test.cpp +46 -0
- data/vendor/datasketches-cpp/req/test/req_sketch_serialize_for_java.cpp +20 -0
- data/vendor/datasketches-cpp/req/test/req_sketch_test.cpp +70 -0
- data/vendor/datasketches-cpp/sampling/include/ebpps_sample_impl.hpp +17 -8
- data/vendor/datasketches-cpp/sampling/include/ebpps_sketch.hpp +13 -0
- data/vendor/datasketches-cpp/sampling/include/var_opt_sketch.hpp +10 -1
- data/vendor/datasketches-cpp/sampling/include/var_opt_sketch_impl.hpp +6 -7
- data/vendor/datasketches-cpp/sampling/include/var_opt_union.hpp +5 -1
- data/vendor/datasketches-cpp/sampling/include/var_opt_union_impl.hpp +2 -1
- data/vendor/datasketches-cpp/sampling/test/ebpps_allocation_test.cpp +1 -1
- data/vendor/datasketches-cpp/sampling/test/ebpps_sketch_test.cpp +2 -2
- data/vendor/datasketches-cpp/sampling/test/var_opt_allocation_test.cpp +1 -1
- data/vendor/datasketches-cpp/sampling/test/var_opt_sketch_test.cpp +10 -4
- data/vendor/datasketches-cpp/sampling/test/var_opt_union_test.cpp +12 -0
- data/vendor/datasketches-cpp/tdigest/include/tdigest.hpp +38 -2
- data/vendor/datasketches-cpp/tdigest/include/tdigest_impl.hpp +168 -9
- data/vendor/datasketches-cpp/tdigest/test/CMakeLists.txt +1 -0
- data/vendor/datasketches-cpp/tdigest/test/tdigest_iterator_test.cpp +274 -0
- data/vendor/datasketches-cpp/tdigest/test/tdigest_test.cpp +275 -0
- data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser.hpp +2 -0
- data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser_impl.hpp +24 -3
- data/vendor/datasketches-cpp/theta/include/theta_constants.hpp +4 -2
- data/vendor/datasketches-cpp/theta/include/theta_helpers.hpp +32 -0
- data/vendor/datasketches-cpp/theta/include/theta_set_difference_base_impl.hpp +4 -2
- data/vendor/datasketches-cpp/theta/include/theta_sketch.hpp +22 -4
- data/vendor/datasketches-cpp/theta/include/theta_sketch_impl.hpp +60 -38
- data/vendor/datasketches-cpp/theta/include/theta_union_base_impl.hpp +2 -6
- data/vendor/datasketches-cpp/theta/include/theta_update_sketch_base_impl.hpp +2 -2
- data/vendor/datasketches-cpp/theta/test/bit_packing_test.cpp +50 -0
- data/vendor/datasketches-cpp/theta/test/theta_a_not_b_test.cpp +22 -0
- data/vendor/datasketches-cpp/theta/test/theta_sketch_test.cpp +315 -0
- data/vendor/datasketches-cpp/tools/rat-check.sh +68 -0
- data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch.hpp +35 -4
- data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch_impl.hpp +2 -2
- data/vendor/datasketches-cpp/tuple/include/tuple_sketch.hpp +41 -0
- data/vendor/datasketches-cpp/tuple/include/tuple_sketch_impl.hpp +5 -4
- data/vendor/datasketches-cpp/tuple/test/tuple_sketch_test.cpp +59 -0
- data/vendor/datasketches-cpp/version.cfg.in +1 -1
- metadata +12 -2
|
@@ -379,4 +379,39 @@ TEST_CASE("cpc sketch: max serialized size", "[cpc_sketch]") {
|
|
|
379
379
|
REQUIRE(cpc_sketch::get_max_serialized_size_bytes(26) == static_cast<size_t>((0.6 * (1 << 26)) + 40));
|
|
380
380
|
}
|
|
381
381
|
|
|
382
|
+
TEST_CASE("cpc sketch: deserialize corrupt num_coupons", "[cpc_sketch]") {
|
|
383
|
+
cpc_sketch sketch(11);
|
|
384
|
+
for (int i = 0; i < 100; i++) sketch.update(i);
|
|
385
|
+
auto bytes = sketch.serialize();
|
|
386
|
+
REQUIRE((bytes[5] & (1 << 3)) != 0); // sparse flavor: table present
|
|
387
|
+
REQUIRE((bytes[5] & (1 << 4)) == 0); // and no window
|
|
388
|
+
uint32_t num_coupons;
|
|
389
|
+
std::memcpy(&num_coupons, bytes.data() + 8, sizeof(num_coupons));
|
|
390
|
+
|
|
391
|
+
// more pairs than the compressed table holds: decoder must not read past it
|
|
392
|
+
auto corrupt = bytes;
|
|
393
|
+
const uint32_t more_coupons = num_coupons + 50;
|
|
394
|
+
std::memcpy(corrupt.data() + 8, &more_coupons, sizeof(more_coupons));
|
|
395
|
+
REQUIRE_THROWS_AS(cpc_sketch::deserialize(corrupt.data(), corrupt.size()), std::out_of_range);
|
|
396
|
+
|
|
397
|
+
// more coupons than the k x 64 bit matrix can hold
|
|
398
|
+
corrupt = bytes;
|
|
399
|
+
const uint32_t too_many_coupons = 64 * 2048 + 1;
|
|
400
|
+
std::memcpy(corrupt.data() + 8, &too_many_coupons, sizeof(too_many_coupons));
|
|
401
|
+
REQUIRE_THROWS_AS(cpc_sketch::deserialize(corrupt.data(), corrupt.size()), std::invalid_argument);
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
TEST_CASE("cpc sketch: deserialize corrupt lg_k", "[cpc_sketch]") {
|
|
405
|
+
cpc_sketch sketch(11);
|
|
406
|
+
for (int i = 0; i < 100; i++) sketch.update(i);
|
|
407
|
+
auto bytes = sketch.serialize();
|
|
408
|
+
for (uint8_t lg_k: {0, 3, 27, 255}) {
|
|
409
|
+
bytes[3] = lg_k;
|
|
410
|
+
REQUIRE_THROWS_AS(cpc_sketch::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
|
|
411
|
+
std::stringstream s;
|
|
412
|
+
s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
|
413
|
+
REQUIRE_THROWS_AS(cpc_sketch::deserialize(s), std::invalid_argument);
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
|
|
382
417
|
} /* namespace datasketches */
|
|
@@ -44,6 +44,11 @@ enum frequent_items_error_type {
|
|
|
44
44
|
* Based on Java implementation here:
|
|
45
45
|
* https://github.com/apache/datasketches-java/blob/master/src/main/java/org/apache/datasketches/frequencies/ItemsSketch.java
|
|
46
46
|
* @author Alexander Saydakov
|
|
47
|
+
*
|
|
48
|
+
* Sketch that may retain string values.
|
|
49
|
+
* For sketches containing strings, cross-language portability depends on
|
|
50
|
+
* using compatible string encodings. This class does not by itself enforce
|
|
51
|
+
* UTF-8 validity for all string inputs.
|
|
47
52
|
*/
|
|
48
53
|
template<
|
|
49
54
|
typename T,
|
|
@@ -74,6 +79,8 @@ public:
|
|
|
74
79
|
|
|
75
80
|
/**
|
|
76
81
|
* Update this sketch with an item and a positive weight (frequency count).
|
|
82
|
+
* If cross-language portability is required, callers should ensure that
|
|
83
|
+
* the input string uses a compatible encoding (valid UTF-8).
|
|
77
84
|
* @param item for which the weight should be increased (lvalue)
|
|
78
85
|
* @param weight the amount by which the weight of the item should be increased
|
|
79
86
|
* A count of zero is a no-op, and a negative count will throw an exception.
|
|
@@ -82,6 +89,8 @@ public:
|
|
|
82
89
|
|
|
83
90
|
/**
|
|
84
91
|
* Update this sketch with an item and a positive weight (frequency count).
|
|
92
|
+
* If cross-language portability is required, callers should ensure that
|
|
93
|
+
* the input string uses a compatible encoding (valid UTF-8).
|
|
85
94
|
* @param item for which the weight should be increased (rvalue)
|
|
86
95
|
* @param weight the amount by which the weight of the item should be increased
|
|
87
96
|
* A count of zero is a no-op, and a negative count will throw an exception.
|
|
@@ -91,6 +100,8 @@ public:
|
|
|
91
100
|
/**
|
|
92
101
|
* This function merges the other sketch into this one.
|
|
93
102
|
* The other sketch may be of a different size.
|
|
103
|
+
* If sketches contain strings, callers are responsible for ensuring that
|
|
104
|
+
* both sketches were built using compatible string encodings.
|
|
94
105
|
* @param other sketch to be merged into this (lvalue)
|
|
95
106
|
*/
|
|
96
107
|
void merge(const frequent_items_sketch& other);
|
|
@@ -98,11 +109,22 @@ public:
|
|
|
98
109
|
/**
|
|
99
110
|
* This function merges the other sketch into this one.
|
|
100
111
|
* The other sketch may be of a different size.
|
|
112
|
+
* If sketches contain strings, callers are responsible for ensuring that
|
|
113
|
+
* both sketches were built using compatible string encodings.
|
|
101
114
|
* @param other sketch to be merged into this (rvalue)
|
|
102
115
|
*/
|
|
103
116
|
void merge(frequent_items_sketch&& other);
|
|
104
117
|
|
|
105
118
|
/**
|
|
119
|
+
* Resets this sketch to the empty state, as if newly constructed.
|
|
120
|
+
* The maximum map size, equality operator and allocator are retained.
|
|
121
|
+
* The internal hash map restarts at its minimum size.
|
|
122
|
+
*/
|
|
123
|
+
void reset();
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* A sketch is empty if it has not been updated with any positive weight.
|
|
127
|
+
* A non-empty sketch may retain no items if a purge removed all of them.
|
|
106
128
|
* @return true if this sketch is empty
|
|
107
129
|
*/
|
|
108
130
|
bool is_empty() const;
|
|
@@ -298,13 +320,16 @@ private:
|
|
|
298
320
|
static const uint8_t PREAMBLE_LONGS_EMPTY = 1;
|
|
299
321
|
static const uint8_t PREAMBLE_LONGS_NONEMPTY = 4;
|
|
300
322
|
static constexpr double EPSILON_FACTOR = 3.5;
|
|
301
|
-
//
|
|
302
|
-
//
|
|
323
|
+
// Emptiness of a serialized image is determined by preamble longs (1 for empty, 4 otherwise).
|
|
324
|
+
// The flags byte is only cross-checked against it. Due to a mistake different bits were used
|
|
325
|
+
// in C++ and Java to indicate an empty sketch, therefore both are set for compatibility with
|
|
326
|
+
// the historical binary format, and either one is accepted on read. No other flag bits are defined.
|
|
303
327
|
enum flags { IS_EMPTY_1 = 0, IS_EMPTY_2 = 2 };
|
|
304
328
|
W total_weight;
|
|
305
329
|
W offset;
|
|
306
330
|
reverse_purge_hash_map<T, W, H, E, A> map;
|
|
307
|
-
static void check_preamble_longs(uint8_t preamble_longs,
|
|
331
|
+
static void check_preamble_longs(uint8_t preamble_longs, uint8_t flags_byte);
|
|
332
|
+
static void check_total_weight(W total_weight);
|
|
308
333
|
static void check_serial_version(uint8_t serial_version);
|
|
309
334
|
static void check_family_id(uint8_t family_id);
|
|
310
335
|
static void check_size(uint8_t lg_cur_size, uint8_t lg_max_size);
|
|
@@ -45,13 +45,13 @@ map(
|
|
|
45
45
|
allocator
|
|
46
46
|
)
|
|
47
47
|
{
|
|
48
|
-
if (lg_start_map_size > lg_max_map_size) throw std::invalid_argument("starting size must not be greater than maximum size");
|
|
48
|
+
if (lg_start_map_size > lg_max_map_size) { throw std::invalid_argument("starting size must not be greater than maximum size"); }
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
template<typename T, typename W, typename H, typename E, typename A>
|
|
52
52
|
void frequent_items_sketch<T, W, H, E, A>::update(const T& item, W weight) {
|
|
53
53
|
check_weight(weight);
|
|
54
|
-
if (weight == 0) return;
|
|
54
|
+
if (weight == 0) { return; }
|
|
55
55
|
total_weight += weight;
|
|
56
56
|
offset += map.adjust_or_insert(item, weight);
|
|
57
57
|
}
|
|
@@ -59,14 +59,14 @@ void frequent_items_sketch<T, W, H, E, A>::update(const T& item, W weight) {
|
|
|
59
59
|
template<typename T, typename W, typename H, typename E, typename A>
|
|
60
60
|
void frequent_items_sketch<T, W, H, E, A>::update(T&& item, W weight) {
|
|
61
61
|
check_weight(weight);
|
|
62
|
-
if (weight == 0) return;
|
|
62
|
+
if (weight == 0) { return; }
|
|
63
63
|
total_weight += weight;
|
|
64
64
|
offset += map.adjust_or_insert(std::move(item), weight);
|
|
65
65
|
}
|
|
66
66
|
|
|
67
67
|
template<typename T, typename W, typename H, typename E, typename A>
|
|
68
68
|
void frequent_items_sketch<T, W, H, E, A>::merge(const frequent_items_sketch& other) {
|
|
69
|
-
if (other.is_empty()) return;
|
|
69
|
+
if (other.is_empty()) { return; }
|
|
70
70
|
const W merged_total_weight = total_weight + other.get_total_weight(); // for correction at the end
|
|
71
71
|
for (auto it: other.map) {
|
|
72
72
|
update(it.first, it.second);
|
|
@@ -77,7 +77,7 @@ void frequent_items_sketch<T, W, H, E, A>::merge(const frequent_items_sketch& ot
|
|
|
77
77
|
|
|
78
78
|
template<typename T, typename W, typename H, typename E, typename A>
|
|
79
79
|
void frequent_items_sketch<T, W, H, E, A>::merge(frequent_items_sketch&& other) {
|
|
80
|
-
if (other.is_empty()) return;
|
|
80
|
+
if (other.is_empty()) { return; }
|
|
81
81
|
const W merged_total_weight = total_weight + other.get_total_weight(); // for correction at the end
|
|
82
82
|
for (auto it: other.map) {
|
|
83
83
|
update(std::move(it.first), it.second);
|
|
@@ -86,9 +86,18 @@ void frequent_items_sketch<T, W, H, E, A>::merge(frequent_items_sketch&& other)
|
|
|
86
86
|
total_weight = merged_total_weight;
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
+
template<typename T, typename W, typename H, typename E, typename A>
|
|
90
|
+
void frequent_items_sketch<T, W, H, E, A>::reset() {
|
|
91
|
+
map = reverse_purge_hash_map<T, W, H, E, A>(LG_MIN_MAP_SIZE, map.get_lg_max_size(), map.get_equal(), map.get_allocator());
|
|
92
|
+
total_weight = 0;
|
|
93
|
+
offset = 0;
|
|
94
|
+
}
|
|
95
|
+
|
|
89
96
|
template<typename T, typename W, typename H, typename E, typename A>
|
|
90
97
|
bool frequent_items_sketch<T, W, H, E, A>::is_empty() const {
|
|
91
|
-
|
|
98
|
+
// a purge may clear all counters while offset and total_weight remain non-zero;
|
|
99
|
+
// emptiness must mean "no observations", not "no retained items"
|
|
100
|
+
return total_weight == 0;
|
|
92
101
|
}
|
|
93
102
|
|
|
94
103
|
template<typename T, typename W, typename H, typename E, typename A>
|
|
@@ -105,7 +114,7 @@ template<typename T, typename W, typename H, typename E, typename A>
|
|
|
105
114
|
W frequent_items_sketch<T, W, H, E, A>::get_estimate(const T& item) const {
|
|
106
115
|
// if item is tracked estimate = weight + offset, otherwise 0
|
|
107
116
|
const W weight = map.get(item);
|
|
108
|
-
if (weight > 0) return weight + offset;
|
|
117
|
+
if (weight > 0) { return weight + offset; }
|
|
109
118
|
return 0;
|
|
110
119
|
}
|
|
111
120
|
|
|
@@ -210,7 +219,7 @@ void frequent_items_sketch<T, W, H, E, A>::serialize(std::ostream& os, const Ser
|
|
|
210
219
|
template<typename T, typename W, typename H, typename E, typename A>
|
|
211
220
|
template<typename SerDe>
|
|
212
221
|
size_t frequent_items_sketch<T, W, H, E, A>::get_serialized_size_bytes(const SerDe& sd) const {
|
|
213
|
-
if (is_empty()) return PREAMBLE_LONGS_EMPTY * sizeof(uint64_t);
|
|
222
|
+
if (is_empty()) { return PREAMBLE_LONGS_EMPTY * sizeof(uint64_t); }
|
|
214
223
|
size_t size = PREAMBLE_LONGS_NONEMPTY * sizeof(uint64_t) + map.get_num_active() * sizeof(W);
|
|
215
224
|
for (auto it: map) size += sd.size_of_item(it.first);
|
|
216
225
|
return size;
|
|
@@ -300,9 +309,8 @@ frequent_items_sketch<T, W, H, E, A> frequent_items_sketch<T, W, H, E, A>::deser
|
|
|
300
309
|
const auto flags_byte = read<uint8_t>(is);
|
|
301
310
|
read<uint16_t>(is); // unused
|
|
302
311
|
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
check_preamble_longs(preamble_longs, is_empty);
|
|
312
|
+
check_preamble_longs(preamble_longs, flags_byte);
|
|
313
|
+
const bool is_empty = preamble_longs == PREAMBLE_LONGS_EMPTY;
|
|
306
314
|
check_serial_version(serial_version);
|
|
307
315
|
check_family_id(family_id);
|
|
308
316
|
check_size(lg_cur_size, lg_max_size);
|
|
@@ -313,6 +321,7 @@ frequent_items_sketch<T, W, H, E, A> frequent_items_sketch<T, W, H, E, A>::deser
|
|
|
313
321
|
read<uint32_t>(is); // unused
|
|
314
322
|
const auto total_weight = read<W>(is);
|
|
315
323
|
const auto offset = read<W>(is);
|
|
324
|
+
check_total_weight(total_weight);
|
|
316
325
|
|
|
317
326
|
// batch deserialization with intermediate array of items and weights
|
|
318
327
|
using AllocW = typename std::allocator_traits<A>::template rebind_alloc<W>;
|
|
@@ -328,8 +337,7 @@ frequent_items_sketch<T, W, H, E, A> frequent_items_sketch<T, W, H, E, A>::deser
|
|
|
328
337
|
sketch.total_weight = total_weight;
|
|
329
338
|
sketch.offset = offset;
|
|
330
339
|
}
|
|
331
|
-
if (!is.good())
|
|
332
|
-
throw std::runtime_error("error reading from std::istream");
|
|
340
|
+
if (!is.good()) { throw std::runtime_error("error reading from std::istream"); }
|
|
333
341
|
return sketch;
|
|
334
342
|
}
|
|
335
343
|
|
|
@@ -354,9 +362,8 @@ frequent_items_sketch<T, W, H, E, A> frequent_items_sketch<T, W, H, E, A>::deser
|
|
|
354
362
|
ptr += copy_from_mem(ptr, flags_byte);
|
|
355
363
|
ptr += sizeof(uint16_t); // unused
|
|
356
364
|
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
check_preamble_longs(preamble_longs, is_empty);
|
|
365
|
+
check_preamble_longs(preamble_longs, flags_byte);
|
|
366
|
+
const bool is_empty = preamble_longs == PREAMBLE_LONGS_EMPTY;
|
|
360
367
|
check_serial_version(serial_version);
|
|
361
368
|
check_family_id(family_id);
|
|
362
369
|
check_size(lg_cur_size, lg_max_size);
|
|
@@ -371,6 +378,7 @@ frequent_items_sketch<T, W, H, E, A> frequent_items_sketch<T, W, H, E, A>::deser
|
|
|
371
378
|
ptr += copy_from_mem(ptr, total_weight);
|
|
372
379
|
W offset;
|
|
373
380
|
ptr += copy_from_mem(ptr, offset);
|
|
381
|
+
check_total_weight(total_weight);
|
|
374
382
|
|
|
375
383
|
ensure_minimum_memory(size, ptr - base + (sizeof(W) * num_items));
|
|
376
384
|
// batch deserialization with intermediate array of items and weights
|
|
@@ -393,15 +401,23 @@ frequent_items_sketch<T, W, H, E, A> frequent_items_sketch<T, W, H, E, A>::deser
|
|
|
393
401
|
}
|
|
394
402
|
|
|
395
403
|
template<typename T, typename W, typename H, typename E, typename A>
|
|
396
|
-
void frequent_items_sketch<T, W, H, E, A>::check_preamble_longs(uint8_t preamble_longs,
|
|
397
|
-
if (
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
404
|
+
void frequent_items_sketch<T, W, H, E, A>::check_preamble_longs(uint8_t preamble_longs, uint8_t flags_byte) {
|
|
405
|
+
if (preamble_longs != PREAMBLE_LONGS_EMPTY && preamble_longs != PREAMBLE_LONGS_NONEMPTY) {
|
|
406
|
+
throw std::invalid_argument("Possible corruption: preamble longs must be " + std::to_string(PREAMBLE_LONGS_EMPTY)
|
|
407
|
+
+ " or " + std::to_string(PREAMBLE_LONGS_NONEMPTY) + ": " + std::to_string(preamble_longs));
|
|
408
|
+
}
|
|
409
|
+
const bool empty_flag = (flags_byte & ((1 << flags::IS_EMPTY_1) | (1 << flags::IS_EMPTY_2))) != 0;
|
|
410
|
+
if (empty_flag != (preamble_longs == PREAMBLE_LONGS_EMPTY)) {
|
|
411
|
+
throw std::invalid_argument("Possible corruption: empty flag does not match preamble longs: flags "
|
|
412
|
+
+ std::to_string(flags_byte) + ", preamble longs " + std::to_string(preamble_longs));
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
template<typename T, typename W, typename H, typename E, typename A>
|
|
417
|
+
void frequent_items_sketch<T, W, H, E, A>::check_total_weight(W total_weight) {
|
|
418
|
+
// written as !(x > 0) to also reject NaN
|
|
419
|
+
if (!(total_weight > 0)) {
|
|
420
|
+
throw std::invalid_argument("Possible corruption: total weight of a non-empty sketch must be positive");
|
|
405
421
|
}
|
|
406
422
|
}
|
|
407
423
|
|
|
@@ -20,8 +20,9 @@
|
|
|
20
20
|
#ifndef REVERSE_PURGE_HASH_MAP_HPP_
|
|
21
21
|
#define REVERSE_PURGE_HASH_MAP_HPP_
|
|
22
22
|
|
|
23
|
-
#include <
|
|
23
|
+
#include <cstdint>
|
|
24
24
|
#include <iterator>
|
|
25
|
+
#include <memory>
|
|
25
26
|
|
|
26
27
|
namespace datasketches {
|
|
27
28
|
|
|
@@ -61,6 +62,7 @@ public:
|
|
|
61
62
|
uint32_t get_capacity() const;
|
|
62
63
|
uint32_t get_num_active() const;
|
|
63
64
|
const A& get_allocator() const;
|
|
65
|
+
const E& get_equal() const;
|
|
64
66
|
|
|
65
67
|
class iterator;
|
|
66
68
|
iterator begin() const;
|
|
@@ -74,7 +74,7 @@ states_(nullptr)
|
|
|
74
74
|
if (other.states_[i] > 0) {
|
|
75
75
|
new (&keys_[i]) K(other.keys_[i]);
|
|
76
76
|
values_[i] = other.values_[i];
|
|
77
|
-
if (--num == 0) break;
|
|
77
|
+
if (--num == 0) { break; }
|
|
78
78
|
}
|
|
79
79
|
}
|
|
80
80
|
}
|
|
@@ -105,7 +105,7 @@ reverse_purge_hash_map<K, V, H, E, A>::~reverse_purge_hash_map() {
|
|
|
105
105
|
for (uint32_t i = 0; i < size; i++) {
|
|
106
106
|
if (is_active(i)) {
|
|
107
107
|
keys_[i].~K();
|
|
108
|
-
if (--num_active_ == 0) break;
|
|
108
|
+
if (--num_active_ == 0) { break; }
|
|
109
109
|
}
|
|
110
110
|
}
|
|
111
111
|
}
|
|
@@ -166,7 +166,7 @@ V reverse_purge_hash_map<K, V, H, E, A>::get(const K& key) const {
|
|
|
166
166
|
const uint32_t mask = (1 << lg_cur_size_) - 1;
|
|
167
167
|
uint32_t probe = fmix64(H()(key)) & mask;
|
|
168
168
|
while (is_active(probe)) {
|
|
169
|
-
if (E()(keys_[probe], key)) return values_[probe];
|
|
169
|
+
if (E()(keys_[probe], key)) { return values_[probe]; }
|
|
170
170
|
probe = (probe + 1) & mask;
|
|
171
171
|
}
|
|
172
172
|
return 0;
|
|
@@ -197,6 +197,11 @@ const A& reverse_purge_hash_map<K, V, H, E, A>::get_allocator() const {
|
|
|
197
197
|
return allocator_;
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
+
template<typename K, typename V, typename H, typename E, typename A>
|
|
201
|
+
const E& reverse_purge_hash_map<K, V, H, E, A>::get_equal() const {
|
|
202
|
+
return equal_;
|
|
203
|
+
}
|
|
204
|
+
|
|
200
205
|
template<typename K, typename V, typename H, typename E, typename A>
|
|
201
206
|
typename reverse_purge_hash_map<K, V, H, E, A>::iterator reverse_purge_hash_map<K, V, H, E, A>::begin() const {
|
|
202
207
|
const uint32_t size = 1 << lg_cur_size_;
|
|
@@ -271,7 +276,7 @@ void reverse_purge_hash_map<K, V, H, E, A>::hash_delete(uint32_t delete_index) {
|
|
|
271
276
|
probe = (probe + 1) & mask;
|
|
272
277
|
drift++;
|
|
273
278
|
// only used for theoretical analysis
|
|
274
|
-
if (drift >= DRIFT_LIMIT) throw std::logic_error("drift: " + std::to_string(drift) + " >= DRIFT_LIMIT");
|
|
279
|
+
if (drift >= DRIFT_LIMIT) { throw std::logic_error("drift: " + std::to_string(drift) + " >= DRIFT_LIMIT"); }
|
|
275
280
|
}
|
|
276
281
|
}
|
|
277
282
|
|
|
@@ -289,7 +294,7 @@ uint32_t reverse_purge_hash_map<K, V, H, E, A>::internal_adjust_or_insert(const
|
|
|
289
294
|
index = (index + 1) & mask;
|
|
290
295
|
drift++;
|
|
291
296
|
// only used for theoretical analysis
|
|
292
|
-
if (drift >= DRIFT_LIMIT) throw std::logic_error("drift limit reached");
|
|
297
|
+
if (drift >= DRIFT_LIMIT) { throw std::logic_error("drift limit reached"); }
|
|
293
298
|
}
|
|
294
299
|
// adding the key and value to the table
|
|
295
300
|
if (num_active_ > get_capacity()) {
|
|
@@ -57,6 +57,30 @@ TEST_CASE("frequent strings sketch generate", "[serialize_for_java]") {
|
|
|
57
57
|
}
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
// lg_max_map_size=8 -> capacity 192; the 193rd distinct item triggers a purge
|
|
61
|
+
// whose median (1) removes every counter: non-empty with no retained items
|
|
62
|
+
TEST_CASE("frequent longs sketch purged to zero items", "[serialize_for_java]") {
|
|
63
|
+
frequent_items_sketch<long> sketch(8);
|
|
64
|
+
for (long i = 1; i <= 193; ++i) sketch.update(i);
|
|
65
|
+
REQUIRE_FALSE(sketch.is_empty());
|
|
66
|
+
REQUIRE(sketch.get_num_active_items() == 0);
|
|
67
|
+
REQUIRE(sketch.get_total_weight() == 193);
|
|
68
|
+
REQUIRE(sketch.get_maximum_error() == 1);
|
|
69
|
+
std::ofstream os("frequent_long_purged_cpp.sk", std::ios::binary);
|
|
70
|
+
sketch.serialize(os);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
TEST_CASE("frequent strings sketch purged to zero items", "[serialize_for_java]") {
|
|
74
|
+
frequent_items_sketch<std::string> sketch(8);
|
|
75
|
+
for (unsigned i = 1; i <= 193; ++i) sketch.update(std::to_string(i));
|
|
76
|
+
REQUIRE_FALSE(sketch.is_empty());
|
|
77
|
+
REQUIRE(sketch.get_num_active_items() == 0);
|
|
78
|
+
REQUIRE(sketch.get_total_weight() == 193);
|
|
79
|
+
REQUIRE(sketch.get_maximum_error() == 1);
|
|
80
|
+
std::ofstream os("frequent_string_purged_cpp.sk", std::ios::binary);
|
|
81
|
+
sketch.serialize(os);
|
|
82
|
+
}
|
|
83
|
+
|
|
60
84
|
TEST_CASE("frequent strings sketch ascii", "[serialize_for_java]") {
|
|
61
85
|
frequent_items_sketch<std::string> sketch(6);
|
|
62
86
|
sketch.update("aaaaaaaaaaaaaaaaaaaaaaaaaaaaa", 1);
|
|
@@ -131,6 +131,122 @@ TEST_CASE("frequent items: estimation mode", "[frequent_items_sketch]") {
|
|
|
131
131
|
REQUIRE(12 >= items.size()); // but not more than 12 items
|
|
132
132
|
}
|
|
133
133
|
|
|
134
|
+
TEST_CASE("frequent items: purge clearing all counters is not empty", "[frequent_items_sketch]") {
|
|
135
|
+
// lg_k=8 -> capacity 192; the 193rd distinct item triggers a purge whose
|
|
136
|
+
// median (1) wipes every counter. The sketch still holds observations:
|
|
137
|
+
// offset=1, total_weight=193. It must not be considered empty, and must
|
|
138
|
+
// survive serialization and merge without losing weight or error.
|
|
139
|
+
frequent_items_sketch<uint64_t> sketch(8);
|
|
140
|
+
for (uint64_t i = 0; i < 193; ++i) sketch.update(i);
|
|
141
|
+
|
|
142
|
+
REQUIRE(sketch.get_num_active_items() == 0); // purge wiped all counters
|
|
143
|
+
REQUIRE_FALSE(sketch.is_empty());
|
|
144
|
+
REQUIRE(sketch.get_total_weight() == 193);
|
|
145
|
+
REQUIRE(sketch.get_maximum_error() == 1);
|
|
146
|
+
|
|
147
|
+
auto bytes = sketch.serialize();
|
|
148
|
+
auto sketch2 = frequent_items_sketch<uint64_t>::deserialize(bytes.data(), bytes.size());
|
|
149
|
+
REQUIRE_FALSE(sketch2.is_empty());
|
|
150
|
+
REQUIRE(sketch2.get_total_weight() == 193);
|
|
151
|
+
REQUIRE(sketch2.get_maximum_error() == 1);
|
|
152
|
+
|
|
153
|
+
frequent_items_sketch<uint64_t> sketch3(8);
|
|
154
|
+
sketch3.update(999999);
|
|
155
|
+
sketch3.merge(sketch);
|
|
156
|
+
REQUIRE(sketch3.get_total_weight() == 194);
|
|
157
|
+
REQUIRE(sketch3.get_maximum_error() == 1);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
TEST_CASE("frequent items: purge clearing all counters serialized form", "[frequent_items_sketch]") {
|
|
161
|
+
frequent_items_sketch<uint64_t> sketch(8);
|
|
162
|
+
for (uint64_t i = 0; i < 193; ++i) sketch.update(i);
|
|
163
|
+
REQUIRE(sketch.get_num_active_items() == 0);
|
|
164
|
+
|
|
165
|
+
// full preamble with no items
|
|
166
|
+
auto bytes = sketch.serialize();
|
|
167
|
+
REQUIRE(bytes.size() == 32);
|
|
168
|
+
REQUIRE(bytes[0] == 4); // preamble longs
|
|
169
|
+
REQUIRE(bytes[5] == 0); // flags
|
|
170
|
+
|
|
171
|
+
std::stringstream s(std::ios::in | std::ios::out | std::ios::binary);
|
|
172
|
+
sketch.serialize(s);
|
|
173
|
+
auto sketch2 = frequent_items_sketch<uint64_t>::deserialize(s);
|
|
174
|
+
REQUIRE_FALSE(sketch2.is_empty());
|
|
175
|
+
REQUIRE(sketch2.get_num_active_items() == 0);
|
|
176
|
+
REQUIRE(sketch2.get_total_weight() == 193);
|
|
177
|
+
REQUIRE(sketch2.get_maximum_error() == 1);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
TEST_CASE("frequent items: reset", "[frequent_items_sketch]") {
|
|
181
|
+
frequent_items_sketch<uint64_t> sketch(8);
|
|
182
|
+
for (uint64_t i = 0; i < 1000; ++i) sketch.update(i % 300, i % 7 + 1);
|
|
183
|
+
REQUIRE_FALSE(sketch.is_empty());
|
|
184
|
+
REQUIRE(sketch.get_maximum_error() > 0);
|
|
185
|
+
const double epsilon = sketch.get_epsilon();
|
|
186
|
+
|
|
187
|
+
sketch.reset();
|
|
188
|
+
REQUIRE(sketch.is_empty());
|
|
189
|
+
REQUIRE(sketch.get_num_active_items() == 0);
|
|
190
|
+
REQUIRE(sketch.get_total_weight() == 0);
|
|
191
|
+
REQUIRE(sketch.get_maximum_error() == 0);
|
|
192
|
+
REQUIRE(sketch.get_epsilon() == epsilon); // max map size retained
|
|
193
|
+
REQUIRE(sketch.get_serialized_size_bytes() == 8);
|
|
194
|
+
|
|
195
|
+
// same behavior as a newly constructed sketch
|
|
196
|
+
frequent_items_sketch<uint64_t> fresh(8);
|
|
197
|
+
for (uint64_t i = 0; i < 193; ++i) {
|
|
198
|
+
sketch.update(i);
|
|
199
|
+
fresh.update(i);
|
|
200
|
+
}
|
|
201
|
+
REQUIRE(sketch.serialize() == fresh.serialize());
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
TEST_CASE("frequent items: empty image with either legacy empty flag", "[frequent_items_sketch]") {
|
|
205
|
+
frequent_items_sketch<uint64_t> sketch(8);
|
|
206
|
+
auto bytes = sketch.serialize();
|
|
207
|
+
REQUIRE(bytes.size() == 8);
|
|
208
|
+
REQUIRE(bytes[5] == 5); // both empty bits written
|
|
209
|
+
for (uint8_t flags: {1, 4, 5}) {
|
|
210
|
+
bytes[5] = flags;
|
|
211
|
+
auto sketch2 = frequent_items_sketch<uint64_t>::deserialize(bytes.data(), bytes.size());
|
|
212
|
+
REQUIRE(sketch2.is_empty());
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
TEST_CASE("frequent items: corrupt preamble", "[frequent_items_sketch]") {
|
|
217
|
+
frequent_items_sketch<uint64_t> empty_sketch(8);
|
|
218
|
+
frequent_items_sketch<uint64_t> sketch(8);
|
|
219
|
+
sketch.update(1);
|
|
220
|
+
|
|
221
|
+
SECTION("invalid preamble longs") {
|
|
222
|
+
auto bytes = sketch.serialize();
|
|
223
|
+
bytes[0] = 2;
|
|
224
|
+
REQUIRE_THROWS_AS(frequent_items_sketch<uint64_t>::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
|
|
225
|
+
}
|
|
226
|
+
SECTION("empty preamble longs, not empty flag") {
|
|
227
|
+
auto bytes = empty_sketch.serialize();
|
|
228
|
+
bytes[5] = 0;
|
|
229
|
+
REQUIRE_THROWS_AS(frequent_items_sketch<uint64_t>::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
|
|
230
|
+
}
|
|
231
|
+
SECTION("full preamble longs, empty flag") {
|
|
232
|
+
auto bytes = sketch.serialize();
|
|
233
|
+
bytes[5] = 5;
|
|
234
|
+
REQUIRE_THROWS_AS(frequent_items_sketch<uint64_t>::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
|
|
235
|
+
}
|
|
236
|
+
SECTION("full preamble longs, zero total weight, bytes") {
|
|
237
|
+
auto bytes = sketch.serialize();
|
|
238
|
+
for (size_t i = 16; i < 24; ++i) bytes[i] = 0;
|
|
239
|
+
REQUIRE_THROWS_AS(frequent_items_sketch<uint64_t>::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
|
|
240
|
+
}
|
|
241
|
+
SECTION("full preamble longs, zero total weight, stream") {
|
|
242
|
+
auto bytes = sketch.serialize();
|
|
243
|
+
for (size_t i = 16; i < 24; ++i) bytes[i] = 0;
|
|
244
|
+
std::stringstream s(std::ios::in | std::ios::out | std::ios::binary);
|
|
245
|
+
s.write(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
|
246
|
+
REQUIRE_THROWS_AS(frequent_items_sketch<uint64_t>::deserialize(s), std::invalid_argument);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
134
250
|
TEST_CASE("frequent items: merge exact mode", "[frequent_items_sketch]") {
|
|
135
251
|
frequent_items_sketch<int> sketch1(3);
|
|
136
252
|
sketch1.update(1);
|
|
@@ -624,7 +624,7 @@ private:
|
|
|
624
624
|
uint64_t capacity_bits_;
|
|
625
625
|
uint64_t num_bits_set_;
|
|
626
626
|
uint8_t* bit_array_; // data backing bit_array_, regardless of ownership
|
|
627
|
-
uint8_t* memory_; // if wrapped, pointer to the start of the filter,
|
|
627
|
+
uint8_t* memory_; // if wrapped, pointer to the start of the filter, otherwise nullptr
|
|
628
628
|
};
|
|
629
629
|
|
|
630
630
|
/**
|
|
@@ -37,6 +37,19 @@
|
|
|
37
37
|
|
|
38
38
|
namespace datasketches {
|
|
39
39
|
|
|
40
|
+
static uint64_t validate_bloom_filter_num_bits_set(uint64_t num_bits_set,
|
|
41
|
+
uint8_t* bit_array,
|
|
42
|
+
uint64_t length_bytes,
|
|
43
|
+
uint64_t dirty_bits_value)
|
|
44
|
+
{
|
|
45
|
+
const uint64_t counted_bits_set = bit_array_ops::count_num_bits_set(bit_array, length_bytes);
|
|
46
|
+
if (num_bits_set != dirty_bits_value && num_bits_set != counted_bits_set) {
|
|
47
|
+
throw std::invalid_argument("Possible corruption: invalid number of bits set. Expected "
|
|
48
|
+
+ std::to_string(counted_bits_set) + ", found " + std::to_string(num_bits_set));
|
|
49
|
+
}
|
|
50
|
+
return counted_bits_set;
|
|
51
|
+
}
|
|
52
|
+
|
|
40
53
|
template<typename A>
|
|
41
54
|
bloom_filter_alloc<A>::bloom_filter_alloc(uint64_t num_bits, uint16_t num_hashes, uint64_t seed, const A& allocator) :
|
|
42
55
|
allocator_(allocator),
|
|
@@ -143,9 +156,6 @@ bloom_filter_alloc<A>::bloom_filter_alloc(uint64_t seed,
|
|
|
143
156
|
{
|
|
144
157
|
// private constructor
|
|
145
158
|
// no consistency checks since we should have done those prior to calling this
|
|
146
|
-
if (is_read_only_ && memory_ != nullptr && num_bits_set == DIRTY_BITS_VALUE) {
|
|
147
|
-
num_bits_set_ = bit_array_ops::count_num_bits_set(bit_array_, capacity_bits_ >> 3);
|
|
148
|
-
}
|
|
149
159
|
}
|
|
150
160
|
|
|
151
161
|
template<typename A>
|
|
@@ -298,8 +308,7 @@ bloom_filter_alloc<A> bloom_filter_alloc<A>::deserialize(std::istream& is, const
|
|
|
298
308
|
return bloom_filter_alloc<A>(num_longs << 6, num_hashes, seed, allocator);
|
|
299
309
|
}
|
|
300
310
|
|
|
301
|
-
const uint64_t
|
|
302
|
-
const bool is_dirty = (num_bits_set == DIRTY_BITS_VALUE);
|
|
311
|
+
const uint64_t raw_num_bits_set = read<uint64_t>(is);
|
|
303
312
|
|
|
304
313
|
// allocate memory
|
|
305
314
|
const uint64_t num_bytes = num_longs << 3;
|
|
@@ -310,8 +319,17 @@ bloom_filter_alloc<A> bloom_filter_alloc<A>::deserialize(std::istream& is, const
|
|
|
310
319
|
}
|
|
311
320
|
read(is, bit_array, num_bytes);
|
|
312
321
|
|
|
322
|
+
uint64_t num_bits_set;
|
|
323
|
+
try {
|
|
324
|
+
num_bits_set = validate_bloom_filter_num_bits_set(
|
|
325
|
+
raw_num_bits_set, bit_array, num_bytes, DIRTY_BITS_VALUE);
|
|
326
|
+
} catch (...) {
|
|
327
|
+
alloc.deallocate(bit_array, num_bytes);
|
|
328
|
+
throw;
|
|
329
|
+
}
|
|
330
|
+
|
|
313
331
|
// pass to constructor
|
|
314
|
-
return bloom_filter_alloc<A>(seed, num_hashes,
|
|
332
|
+
return bloom_filter_alloc<A>(seed, num_hashes, false, true, false, num_longs << 6, num_bits_set, bit_array, nullptr, allocator);
|
|
315
333
|
}
|
|
316
334
|
|
|
317
335
|
template<typename A>
|
|
@@ -374,9 +392,13 @@ bloom_filter_alloc<A> bloom_filter_alloc<A>::internal_deserialize_or_wrap(void*
|
|
|
374
392
|
return bloom_filter_alloc<A>(num_longs << 6, num_hashes, seed, allocator);
|
|
375
393
|
}
|
|
376
394
|
|
|
377
|
-
uint64_t
|
|
378
|
-
ptr += copy_from_mem(ptr,
|
|
379
|
-
|
|
395
|
+
uint64_t raw_num_bits_set;
|
|
396
|
+
ptr += copy_from_mem(ptr, raw_num_bits_set);
|
|
397
|
+
|
|
398
|
+
const uint64_t num_bytes = num_longs << 3;
|
|
399
|
+
ensure_minimum_memory(end_ptr - ptr, num_bytes);
|
|
400
|
+
const uint64_t num_bits_set = validate_bloom_filter_num_bits_set(
|
|
401
|
+
raw_num_bits_set, const_cast<uint8_t*>(ptr), num_bytes, DIRTY_BITS_VALUE);
|
|
380
402
|
|
|
381
403
|
uint8_t* bit_array;
|
|
382
404
|
uint8_t* memory;
|
|
@@ -386,8 +408,6 @@ bloom_filter_alloc<A> bloom_filter_alloc<A>::internal_deserialize_or_wrap(void*
|
|
|
386
408
|
} else {
|
|
387
409
|
// allocate memory
|
|
388
410
|
memory = nullptr;
|
|
389
|
-
const uint64_t num_bytes = num_longs << 3;
|
|
390
|
-
ensure_minimum_memory(end_ptr - ptr, num_bytes);
|
|
391
411
|
AllocUint8 alloc(allocator);
|
|
392
412
|
bit_array = alloc.allocate(num_bytes);
|
|
393
413
|
if (bit_array == nullptr) {
|
|
@@ -397,7 +417,7 @@ bloom_filter_alloc<A> bloom_filter_alloc<A>::internal_deserialize_or_wrap(void*
|
|
|
397
417
|
}
|
|
398
418
|
|
|
399
419
|
// pass to constructor -- !wrap == is_owned_
|
|
400
|
-
return bloom_filter_alloc<A>(seed, num_hashes,
|
|
420
|
+
return bloom_filter_alloc<A>(seed, num_hashes, false, !wrap, read_only, num_longs << 6, num_bits_set, bit_array, memory, allocator);
|
|
401
421
|
}
|
|
402
422
|
|
|
403
423
|
template<typename A>
|
|
@@ -19,6 +19,9 @@
|
|
|
19
19
|
|
|
20
20
|
#include <catch2/catch.hpp>
|
|
21
21
|
|
|
22
|
+
#include <cstring>
|
|
23
|
+
#include <limits>
|
|
24
|
+
|
|
22
25
|
#include "bloom_filter.hpp"
|
|
23
26
|
|
|
24
27
|
#ifdef TEST_BINARY_INPUT_PATH
|
|
@@ -399,8 +402,32 @@ TEST_CASE("bloom_filter: non-empty serialization", "[bloom_filter]") {
|
|
|
399
402
|
REQUIRE(bf_writable.query(-1.0));
|
|
400
403
|
|
|
401
404
|
// not good memory management to do this, but because we wrapped the same bytes as both
|
|
402
|
-
// read-only
|
|
405
|
+
// read-only and writable, that update should have changed the read-only version, too
|
|
403
406
|
REQUIRE(bf_wrap.query(-1.0));
|
|
404
407
|
}
|
|
405
408
|
|
|
409
|
+
TEST_CASE("bloom_filter: inconsistent num bits set is rejected", "[bloom_filter]") {
|
|
410
|
+
const size_t num_bits_set_offset = 24;
|
|
411
|
+
|
|
412
|
+
auto bf = bloom_filter::builder::create_by_accuracy(100, 0.01);
|
|
413
|
+
bf.update("apple");
|
|
414
|
+
bf.update("banana");
|
|
415
|
+
const uint64_t actual_bits_set = bf.get_bits_used();
|
|
416
|
+
REQUIRE(actual_bits_set > 1);
|
|
417
|
+
|
|
418
|
+
const uint64_t invalid_counts[] = {0, actual_bits_set - 1, actual_bits_set + 1};
|
|
419
|
+
for (const uint64_t serialized_count: invalid_counts) {
|
|
420
|
+
auto bytes = bf.serialize();
|
|
421
|
+
std::memcpy(bytes.data() + num_bits_set_offset, &serialized_count, sizeof(serialized_count));
|
|
422
|
+
|
|
423
|
+
REQUIRE_THROWS_AS(bloom_filter::deserialize(bytes.data(), bytes.size()), std::invalid_argument);
|
|
424
|
+
REQUIRE_THROWS_AS(bloom_filter::wrap(bytes.data(), bytes.size()), std::invalid_argument);
|
|
425
|
+
REQUIRE_THROWS_AS(bloom_filter::writable_wrap(bytes.data(), bytes.size()), std::invalid_argument);
|
|
426
|
+
|
|
427
|
+
const std::string serialized(reinterpret_cast<const char*>(bytes.data()), bytes.size());
|
|
428
|
+
std::istringstream is(serialized, std::ios::in | std::ios::binary);
|
|
429
|
+
REQUIRE_THROWS_AS(bloom_filter::deserialize(is), std::invalid_argument);
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
406
433
|
} // namespace datasketches
|
|
@@ -176,8 +176,7 @@ CouponHashSet<A>* CouponHashSet<A>::newSet(std::istream& is, const A& allocator)
|
|
|
176
176
|
read(is, sketch->coupons_.data(), sketch->coupons_.size() * sizeof(uint32_t));
|
|
177
177
|
}
|
|
178
178
|
|
|
179
|
-
if (!is.good())
|
|
180
|
-
throw std::runtime_error("error reading from std::istream");
|
|
179
|
+
if (!is.good()) { throw std::runtime_error("error reading from std::istream"); }
|
|
181
180
|
|
|
182
181
|
return ptr.release();
|
|
183
182
|
}
|