datasketches 0.5.1 → 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.
Files changed (120) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +9 -0
  3. data/ext/datasketches/cpc_wrapper.cpp +8 -9
  4. data/ext/datasketches/fi_wrapper.cpp +4 -5
  5. data/ext/datasketches/hll_wrapper.cpp +9 -10
  6. data/ext/datasketches/kll_wrapper.cpp +13 -12
  7. data/ext/datasketches/theta_wrapper.cpp +11 -13
  8. data/ext/datasketches/vo_wrapper.cpp +1 -2
  9. data/lib/datasketches/version.rb +1 -1
  10. data/vendor/datasketches-cpp/CMakeLists.txt +5 -3
  11. data/vendor/datasketches-cpp/CODE_OF_CONDUCT.md +1 -1
  12. data/vendor/datasketches-cpp/LICENSE +14 -0
  13. data/vendor/datasketches-cpp/README.md +67 -73
  14. data/vendor/datasketches-cpp/benchmarks/CMakeLists.txt +52 -0
  15. data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch.cpp +153 -0
  16. data/vendor/datasketches-cpp/benchmarks/benchmark_count_min_sketch_serialization.cpp +161 -0
  17. data/vendor/datasketches-cpp/common/CMakeLists.txt +1 -0
  18. data/vendor/datasketches-cpp/common/include/binomial_bounds.hpp +2 -2
  19. data/vendor/datasketches-cpp/common/include/fdlibm_log.hpp +101 -0
  20. data/vendor/datasketches-cpp/common/include/serde.hpp +6 -0
  21. data/vendor/datasketches-cpp/common/test/CMakeLists.txt +44 -2
  22. data/vendor/datasketches-cpp/common/test/binomial_bounds_test.cpp +279 -0
  23. data/vendor/datasketches-cpp/common/test/deserialize_hardening_test.cpp +188 -0
  24. data/vendor/datasketches-cpp/count/include/count_min.hpp +17 -4
  25. data/vendor/datasketches-cpp/count/include/count_min_impl.hpp +65 -83
  26. data/vendor/datasketches-cpp/count/test/count_min_test.cpp +63 -7
  27. data/vendor/datasketches-cpp/cpc/include/compression_data.hpp +2 -0
  28. data/vendor/datasketches-cpp/cpc/include/cpc_compressor_impl.hpp +16 -7
  29. data/vendor/datasketches-cpp/cpc/include/cpc_sketch.hpp +1 -0
  30. data/vendor/datasketches-cpp/cpc/include/cpc_sketch_impl.hpp +54 -36
  31. data/vendor/datasketches-cpp/cpc/include/cpc_union_impl.hpp +27 -27
  32. data/vendor/datasketches-cpp/cpc/include/cpc_util.hpp +7 -7
  33. data/vendor/datasketches-cpp/cpc/include/icon_estimator.hpp +5 -5
  34. data/vendor/datasketches-cpp/cpc/include/u32_table_impl.hpp +16 -16
  35. data/vendor/datasketches-cpp/cpc/test/cpc_sketch_test.cpp +35 -0
  36. data/vendor/datasketches-cpp/fi/include/frequent_items_sketch.hpp +28 -3
  37. data/vendor/datasketches-cpp/fi/include/frequent_items_sketch_impl.hpp +41 -25
  38. data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map.hpp +3 -1
  39. data/vendor/datasketches-cpp/fi/include/reverse_purge_hash_map_impl.hpp +10 -5
  40. data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_serialize_for_java.cpp +24 -0
  41. data/vendor/datasketches-cpp/fi/test/frequent_items_sketch_test.cpp +116 -0
  42. data/vendor/datasketches-cpp/filters/include/bloom_filter.hpp +1 -1
  43. data/vendor/datasketches-cpp/filters/include/bloom_filter_impl.hpp +32 -12
  44. data/vendor/datasketches-cpp/filters/test/bloom_filter_test.cpp +28 -1
  45. data/vendor/datasketches-cpp/hll/include/CouponHashSet-internal.hpp +1 -2
  46. data/vendor/datasketches-cpp/hll/include/CouponList-internal.hpp +19 -5
  47. data/vendor/datasketches-cpp/hll/include/CubicInterpolation-internal.hpp +4 -4
  48. data/vendor/datasketches-cpp/hll/include/HarmonicNumbers-internal.hpp +2 -1
  49. data/vendor/datasketches-cpp/hll/include/Hll4Array-internal.hpp +4 -4
  50. data/vendor/datasketches-cpp/hll/include/Hll4Array.hpp +1 -1
  51. data/vendor/datasketches-cpp/hll/include/Hll6Array-internal.hpp +3 -3
  52. data/vendor/datasketches-cpp/hll/include/Hll6Array.hpp +1 -1
  53. data/vendor/datasketches-cpp/hll/include/Hll8Array-internal.hpp +6 -3
  54. data/vendor/datasketches-cpp/hll/include/Hll8Array.hpp +1 -1
  55. data/vendor/datasketches-cpp/hll/include/HllArray-internal.hpp +35 -29
  56. data/vendor/datasketches-cpp/hll/include/HllArray.hpp +1 -1
  57. data/vendor/datasketches-cpp/hll/include/HllSketch-internal.hpp +3 -5
  58. data/vendor/datasketches-cpp/hll/include/HllSketchImpl-internal.hpp +5 -11
  59. data/vendor/datasketches-cpp/hll/include/HllSketchImpl.hpp +2 -4
  60. data/vendor/datasketches-cpp/hll/include/HllSketchImplFactory.hpp +11 -10
  61. data/vendor/datasketches-cpp/hll/include/HllUnion-internal.hpp +17 -8
  62. data/vendor/datasketches-cpp/hll/include/HllUtil.hpp +12 -3
  63. data/vendor/datasketches-cpp/hll/include/coupon_iterator-internal.hpp +2 -2
  64. data/vendor/datasketches-cpp/hll/include/coupon_iterator.hpp +3 -0
  65. data/vendor/datasketches-cpp/hll/include/hll.hpp +11 -4
  66. data/vendor/datasketches-cpp/hll/include/hll.private.hpp +19 -0
  67. data/vendor/datasketches-cpp/hll/test/CMakeLists.txt +2 -0
  68. data/vendor/datasketches-cpp/hll/test/CouponListTest.cpp +64 -0
  69. data/vendor/datasketches-cpp/hll/test/HllFullSizeTest.cpp +137 -0
  70. data/vendor/datasketches-cpp/hll/test/HllKxqRebuildTest.cpp +150 -0
  71. data/vendor/datasketches-cpp/hll/test/HllSketchTest.cpp +3 -3
  72. data/vendor/datasketches-cpp/hll/test/HllUnionTest.cpp +71 -1
  73. data/vendor/datasketches-cpp/kll/include/kll_helper_impl.hpp +10 -10
  74. data/vendor/datasketches-cpp/kll/include/kll_sketch.hpp +10 -1
  75. data/vendor/datasketches-cpp/kll/include/kll_sketch_impl.hpp +33 -24
  76. data/vendor/datasketches-cpp/kll/test/kll_sketch_deserialize_from_java_test.cpp +24 -0
  77. data/vendor/datasketches-cpp/kll/test/kll_sketch_serialize_for_java.cpp +10 -0
  78. data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch.hpp +11 -2
  79. data/vendor/datasketches-cpp/quantiles/include/quantiles_sketch_impl.hpp +24 -15
  80. data/vendor/datasketches-cpp/req/include/req_sketch.hpp +9 -0
  81. data/vendor/datasketches-cpp/req/include/req_sketch_impl.hpp +24 -15
  82. data/vendor/datasketches-cpp/req/test/req_sketch_deserialize_from_java_test.cpp +46 -0
  83. data/vendor/datasketches-cpp/req/test/req_sketch_serialize_for_java.cpp +20 -0
  84. data/vendor/datasketches-cpp/req/test/req_sketch_test.cpp +70 -0
  85. data/vendor/datasketches-cpp/sampling/include/ebpps_sample_impl.hpp +17 -8
  86. data/vendor/datasketches-cpp/sampling/include/ebpps_sketch.hpp +13 -0
  87. data/vendor/datasketches-cpp/sampling/include/var_opt_sketch.hpp +10 -1
  88. data/vendor/datasketches-cpp/sampling/include/var_opt_sketch_impl.hpp +6 -7
  89. data/vendor/datasketches-cpp/sampling/include/var_opt_union.hpp +5 -1
  90. data/vendor/datasketches-cpp/sampling/include/var_opt_union_impl.hpp +2 -1
  91. data/vendor/datasketches-cpp/sampling/test/ebpps_allocation_test.cpp +1 -1
  92. data/vendor/datasketches-cpp/sampling/test/ebpps_sketch_test.cpp +2 -2
  93. data/vendor/datasketches-cpp/sampling/test/var_opt_allocation_test.cpp +1 -1
  94. data/vendor/datasketches-cpp/sampling/test/var_opt_sketch_test.cpp +10 -4
  95. data/vendor/datasketches-cpp/sampling/test/var_opt_union_test.cpp +12 -0
  96. data/vendor/datasketches-cpp/tdigest/include/tdigest.hpp +38 -2
  97. data/vendor/datasketches-cpp/tdigest/include/tdigest_impl.hpp +168 -9
  98. data/vendor/datasketches-cpp/tdigest/test/CMakeLists.txt +1 -0
  99. data/vendor/datasketches-cpp/tdigest/test/tdigest_iterator_test.cpp +274 -0
  100. data/vendor/datasketches-cpp/tdigest/test/tdigest_test.cpp +275 -0
  101. data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser.hpp +2 -0
  102. data/vendor/datasketches-cpp/theta/include/compact_theta_sketch_parser_impl.hpp +24 -3
  103. data/vendor/datasketches-cpp/theta/include/theta_constants.hpp +4 -2
  104. data/vendor/datasketches-cpp/theta/include/theta_helpers.hpp +32 -0
  105. data/vendor/datasketches-cpp/theta/include/theta_set_difference_base_impl.hpp +4 -2
  106. data/vendor/datasketches-cpp/theta/include/theta_sketch.hpp +22 -4
  107. data/vendor/datasketches-cpp/theta/include/theta_sketch_impl.hpp +60 -38
  108. data/vendor/datasketches-cpp/theta/include/theta_union_base_impl.hpp +2 -6
  109. data/vendor/datasketches-cpp/theta/include/theta_update_sketch_base_impl.hpp +2 -2
  110. data/vendor/datasketches-cpp/theta/test/bit_packing_test.cpp +50 -0
  111. data/vendor/datasketches-cpp/theta/test/theta_a_not_b_test.cpp +22 -0
  112. data/vendor/datasketches-cpp/theta/test/theta_sketch_test.cpp +315 -0
  113. data/vendor/datasketches-cpp/tools/rat-check.sh +68 -0
  114. data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch.hpp +35 -4
  115. data/vendor/datasketches-cpp/tuple/include/array_tuple_sketch_impl.hpp +2 -2
  116. data/vendor/datasketches-cpp/tuple/include/tuple_sketch.hpp +41 -0
  117. data/vendor/datasketches-cpp/tuple/include/tuple_sketch_impl.hpp +5 -4
  118. data/vendor/datasketches-cpp/tuple/test/tuple_sketch_test.cpp +59 -0
  119. data/vendor/datasketches-cpp/version.cfg.in +1 -1
  120. metadata +12 -2
@@ -22,6 +22,8 @@
22
22
  #include <sstream>
23
23
  #include <vector>
24
24
  #include <stdexcept>
25
+ #include <algorithm>
26
+ #include <cstring>
25
27
 
26
28
  #include <catch2/catch.hpp>
27
29
  #include <theta_sketch.hpp>
@@ -57,6 +59,48 @@ TEST_CASE("theta sketch: empty", "[theta_sketch]") {
57
59
  REQUIRE(update_sketch.compact(false).is_ordered());
58
60
  }
59
61
 
62
+ TEST_CASE("theta sketch: min lg_k", "[theta_sketch]") {
63
+ // lg_k = 4 (nominal 16) is the smallest allowed nominal size, matching Java's
64
+ // ThetaUtil.MIN_LG_NOM_LONGS. Anything below it must throw.
65
+ REQUIRE(theta_constants::MIN_LG_K == 4);
66
+ REQUIRE_THROWS_AS(update_theta_sketch::builder().set_lg_k(theta_constants::MIN_LG_K - 1),
67
+ std::invalid_argument);
68
+ update_theta_sketch min_sketch = update_theta_sketch::builder().set_lg_k(theta_constants::MIN_LG_K).build();
69
+ REQUIRE(min_sketch.get_lg_k() == theta_constants::MIN_LG_K);
70
+
71
+ // update well past the nominal size to force estimation mode and exercise the rebuild path,
72
+ // tracking the peak number of retained entries seen between rebuilds.
73
+ const int n = 10000;
74
+ uint32_t max_retained = 0;
75
+ for (int i = 0; i < n; ++i) {
76
+ min_sketch.update(i);
77
+ max_retained = std::max(max_retained, min_sketch.get_num_retained());
78
+ }
79
+ REQUIRE(min_sketch.is_estimation_mode());
80
+ REQUIRE(min_sketch.get_theta() < 1.0);
81
+
82
+ // The internal hash table is floored at MIN_LG_ARR (5, i.e. 32 slots), one lg above the
83
+ // nominal size. This is exactly what MIN_LG_ARR guarantees: between rebuilds the sketch holds
84
+ // more than the nominal 2^MIN_LG_K (16) entries, but never more than the 2^MIN_LG_ARR (32)
85
+ // slots of the table. Were the table sized to the nominal 16, it could not retain more than 16.
86
+ REQUIRE(max_retained > (1 << theta_constants::MIN_LG_K));
87
+ REQUIRE(max_retained <= (1 << theta_constants::MIN_LG_ARR));
88
+
89
+ // the true count is bracketed by the 2-standard-deviation confidence bounds
90
+ REQUIRE(min_sketch.get_lower_bound(2) <= n);
91
+ REQUIRE(min_sketch.get_upper_bound(2) >= n);
92
+
93
+ // trimming reduces the sketch to exactly the nominal number of entries (2^4 = 16)
94
+ min_sketch.trim();
95
+ REQUIRE(min_sketch.get_num_retained() == (1 << theta_constants::MIN_LG_K));
96
+
97
+ // a compacted min-size sketch round trips through serialization
98
+ auto bytes = min_sketch.compact().serialize();
99
+ compact_theta_sketch deserialized = compact_theta_sketch::deserialize(bytes.data(), bytes.size());
100
+ REQUIRE(deserialized.get_num_retained() == min_sketch.get_num_retained());
101
+ REQUIRE(deserialized.get_estimate() == min_sketch.get_estimate());
102
+ }
103
+
60
104
  TEST_CASE("theta sketch: non empty no retained keys", "[theta_sketch]") {
61
105
  update_theta_sketch update_sketch = update_theta_sketch::builder().set_p(0.001f).build();
62
106
  update_sketch.update(1);
@@ -109,6 +153,196 @@ TEST_CASE("theta sketch: single item", "[theta_sketch]") {
109
153
  REQUIRE(update_sketch.compact(false).is_ordered());
110
154
  }
111
155
 
156
+ // Java writes exactly these images (EmptyCompactSketch.EMPTY_COMPACT_SKETCH_ARR and SingleItemSketch),
157
+ // and the cross-language tests compare the two languages byte for byte.
158
+ // An empty sketch carries no hashes, so its seed hash bytes are zero; a single item keeps its seed hash.
159
+ static std::vector<uint8_t> serialized_bytes(const compact_theta_sketch& sketch) {
160
+ auto bytes = sketch.serialize();
161
+ return std::vector<uint8_t>(bytes.begin(), bytes.end());
162
+ }
163
+
164
+ static std::vector<uint8_t> serialized_stream(const compact_theta_sketch& sketch) {
165
+ std::stringstream s(std::ios::in | std::ios::out | std::ios::binary);
166
+ sketch.serialize(s);
167
+ const std::string str = s.str();
168
+ return std::vector<uint8_t>(str.begin(), str.end());
169
+ }
170
+
171
+ static std::vector<uint8_t> serialized_compressed(const compact_theta_sketch& sketch) {
172
+ auto bytes = sketch.serialize_compressed();
173
+ return std::vector<uint8_t>(bytes.begin(), bytes.end());
174
+ }
175
+
176
+ TEST_CASE("theta sketch: empty serialized image", "[theta_sketch]") {
177
+ const uint64_t seed = 12345; // not the default, to prove the seed hash is not written
178
+ auto sketch = update_theta_sketch::builder().set_seed(seed).build().compact();
179
+ const std::vector<uint8_t> expected = {1, 3, 3, 0, 0, 0x1E, 0, 0};
180
+ REQUIRE(serialized_bytes(sketch) == expected);
181
+ REQUIRE(serialized_stream(sketch) == expected);
182
+ REQUIRE(serialized_compressed(sketch) == expected); // no compressed form of an empty sketch
183
+ }
184
+
185
+ TEST_CASE("theta sketch: single item serialized image", "[theta_sketch]") {
186
+ const uint64_t seed = 12345;
187
+ const uint16_t seed_hash = compute_seed_hash(seed);
188
+ update_theta_sketch update_sketch = update_theta_sketch::builder().set_seed(seed).build();
189
+ update_sketch.update(1);
190
+ auto sketch = update_sketch.compact();
191
+ const uint64_t hash = *sketch.begin();
192
+
193
+ // flags: single item | ordered | compact | read-only, then seed hash and the one hash, little-endian
194
+ std::vector<uint8_t> expected = {1, 3, 3, 0, 0, 0x3A,
195
+ static_cast<uint8_t>(seed_hash), static_cast<uint8_t>(seed_hash >> 8)};
196
+ for (unsigned i = 0; i < 8; ++i) expected.push_back(static_cast<uint8_t>(hash >> (i * 8)));
197
+ REQUIRE(serialized_bytes(sketch) == expected);
198
+ REQUIRE(serialized_stream(sketch) == expected);
199
+ REQUIRE(serialized_compressed(sketch) == expected); // no compressed form of a single item
200
+
201
+ // the single item flag is informational: the reader does not depend on it
202
+ auto deserialized = compact_theta_sketch::deserialize(expected.data(), expected.size(), seed);
203
+ REQUIRE(deserialized.get_num_retained() == 1);
204
+ REQUIRE(*deserialized.begin() == hash);
205
+ expected[5] = 0x1A; // as written before this flag existed
206
+ deserialized = compact_theta_sketch::deserialize(expected.data(), expected.size(), seed);
207
+ REQUIRE(deserialized.get_num_retained() == 1);
208
+ REQUIRE(*deserialized.begin() == hash);
209
+ }
210
+
211
+ TEST_CASE("theta sketch: one entry in estimation mode is not a single item", "[theta_sketch]") {
212
+ update_theta_sketch update_sketch = update_theta_sketch::builder().set_p(0.5f).build();
213
+ for (uint64_t i = 0; update_sketch.get_num_retained() < 1; ++i) update_sketch.update(i);
214
+ auto sketch = update_sketch.compact();
215
+ REQUIRE(sketch.get_num_retained() == 1);
216
+ REQUIRE(sketch.is_estimation_mode());
217
+ const auto bytes = serialized_bytes(sketch);
218
+ REQUIRE(bytes[0] == 3); // theta is present, so this is the 3-long preamble
219
+ REQUIRE(bytes[5] == 0x1A); // ordered | compact | read-only, no single item flag
220
+ }
221
+
222
+ TEST_CASE("theta sketch: compact with trim, all four cases", "[theta_sketch]") {
223
+ update_theta_sketch update_sketch = update_theta_sketch::builder().build();
224
+ for (int i = 0; i < 8000; i++) update_sketch.update(i);
225
+ const uint32_t k = 1 << theta_constants::DEFAULT_LG_K;
226
+ // an update sketch retains more than k between rebuilds, so trimming has work to do
227
+ REQUIRE(update_sketch.get_num_retained() > k);
228
+ const uint32_t retained_before = update_sketch.get_num_retained();
229
+ const uint64_t theta_before = update_sketch.get_theta64();
230
+
231
+ // the trimmed result is what trim() + compact() would produce
232
+ update_theta_sketch trimmed = update_sketch;
233
+ trimmed.trim();
234
+ compact_theta_sketch expected = trimmed.compact(true);
235
+ const std::vector<uint64_t> expected_entries(expected.begin(), expected.end());
236
+
237
+ // case 1: ordered, not trimmed (the default) keeps every retained entry
238
+ compact_theta_sketch c1 = update_sketch.compact(true, false);
239
+ REQUIRE(c1.is_ordered());
240
+ REQUIRE(c1.get_num_retained() == retained_before);
241
+ REQUIRE(c1.get_theta64() == theta_before);
242
+ REQUIRE(std::is_sorted(c1.begin(), c1.end()));
243
+
244
+ // case 2: unordered, not trimmed
245
+ compact_theta_sketch c2 = update_sketch.compact(false, false);
246
+ REQUIRE_FALSE(c2.is_ordered());
247
+ REQUIRE(c2.get_num_retained() == retained_before);
248
+ REQUIRE(c2.get_theta64() == theta_before);
249
+
250
+ // case 3: ordered and trimmed
251
+ compact_theta_sketch c3 = update_sketch.compact(true, true);
252
+ REQUIRE(c3.is_ordered());
253
+ REQUIRE(c3.get_num_retained() == k);
254
+ REQUIRE(c3.get_theta64() < theta_before); // new theta is the k-th smallest hash
255
+ REQUIRE(c3.get_theta64() == expected.get_theta64());
256
+ REQUIRE(std::is_sorted(c3.begin(), c3.end()));
257
+ REQUIRE(std::vector<uint64_t>(c3.begin(), c3.end()) == expected_entries);
258
+ // every retained hash is strictly below the new theta
259
+ for (auto h: c3) REQUIRE(h < c3.get_theta64());
260
+
261
+ // case 4: unordered and trimmed: same set and theta, no sort
262
+ compact_theta_sketch c4 = update_sketch.compact(false, true);
263
+ REQUIRE_FALSE(c4.is_ordered());
264
+ REQUIRE(c4.get_num_retained() == k);
265
+ REQUIRE(c4.get_theta64() == expected.get_theta64());
266
+ std::vector<uint64_t> c4_entries(c4.begin(), c4.end());
267
+ std::sort(c4_entries.begin(), c4_entries.end());
268
+ REQUIRE(c4_entries == expected_entries);
269
+
270
+ // the source sketch must be untouched by any of the four
271
+ REQUIRE(update_sketch.get_num_retained() == retained_before);
272
+ REQUIRE(update_sketch.get_theta64() == theta_before);
273
+ }
274
+
275
+ TEST_CASE("theta sketch: compact with trim widens the bounds in estimation mode", "[theta_sketch]") {
276
+ // trimming is lossy even when the source is already estimating: it discards
277
+ // retained entries, and the relative error scales with 1 / sqrt(retained)
278
+ const uint32_t k = 1 << theta_constants::DEFAULT_LG_K;
279
+ update_theta_sketch sketch = update_theta_sketch::builder().build();
280
+ for (int i = 0; i < 40000; i++) sketch.update(i);
281
+ REQUIRE(sketch.is_estimation_mode());
282
+ REQUIRE(sketch.get_num_retained() > k);
283
+
284
+ compact_theta_sketch plain = sketch.compact(true, false);
285
+ compact_theta_sketch trimmed = sketch.compact(true, true);
286
+ REQUIRE(trimmed.get_num_retained() == k);
287
+ REQUIRE(plain.get_num_retained() > trimmed.get_num_retained());
288
+
289
+ // fewer retained entries -> strictly wider confidence interval
290
+ const double plain_width = plain.get_upper_bound(2) - plain.get_lower_bound(2);
291
+ const double trimmed_width = trimmed.get_upper_bound(2) - trimmed.get_lower_bound(2);
292
+ REQUIRE(trimmed_width > plain_width);
293
+ }
294
+
295
+ TEST_CASE("theta sketch: compact with trim converts exact mode to estimation", "[theta_sketch]") {
296
+ // an update sketch can retain far more than k entries while still in exact mode:
297
+ // nothing has been evicted yet, so theta is still 1.0 and the count is exact
298
+ const uint32_t k = 1 << theta_constants::DEFAULT_LG_K;
299
+ update_theta_sketch sketch = update_theta_sketch::builder().build();
300
+ const int n = 5000;
301
+ for (int i = 0; i < n; i++) sketch.update(i);
302
+ REQUIRE(sketch.get_num_retained() > k);
303
+ REQUIRE_FALSE(sketch.is_estimation_mode());
304
+ REQUIRE(sketch.get_theta() == 1.0);
305
+
306
+ // not trimming keeps every entry and the exact count
307
+ compact_theta_sketch exact = sketch.compact(true, false);
308
+ REQUIRE_FALSE(exact.is_estimation_mode());
309
+ REQUIRE(exact.get_num_retained() == static_cast<uint32_t>(n));
310
+ REQUIRE(exact.get_estimate() == Approx(n));
311
+
312
+ // trimming is lossy: it discards real data, lowers theta below 1.0 and the
313
+ // result is an estimate carrying error where the source held an exact count
314
+ compact_theta_sketch trimmed = sketch.compact(true, true);
315
+ REQUIRE(trimmed.is_estimation_mode());
316
+ REQUIRE(trimmed.get_num_retained() == k);
317
+ REQUIRE(trimmed.get_theta() < 1.0);
318
+ REQUIRE(trimmed.get_estimate() != Approx(n));
319
+ // the estimate is still sound: n must lie inside the 3-sigma bounds
320
+ REQUIRE(trimmed.get_lower_bound(3) <= n);
321
+ REQUIRE(trimmed.get_upper_bound(3) >= n);
322
+ }
323
+
324
+ TEST_CASE("theta sketch: compact with trim, empty and below k", "[theta_sketch]") {
325
+ // empty: trimming changes nothing
326
+ update_theta_sketch empty_sketch = update_theta_sketch::builder().build();
327
+ compact_theta_sketch empty_result = empty_sketch.compact(true, true);
328
+ REQUIRE(empty_result.is_empty());
329
+ REQUIRE(empty_result.get_num_retained() == 0);
330
+ REQUIRE(empty_result.is_ordered());
331
+
332
+ // below k: nothing to trim, theta and entries are preserved
333
+ update_theta_sketch small = update_theta_sketch::builder().build();
334
+ for (int i = 0; i < 100; i++) small.update(i);
335
+ REQUIRE_FALSE(small.is_estimation_mode());
336
+
337
+ compact_theta_sketch small_result = small.compact(true, true);
338
+ REQUIRE_FALSE(small_result.is_estimation_mode());
339
+ REQUIRE(small_result.get_num_retained() == 100);
340
+ REQUIRE(small_result.get_theta64() == small.get_theta64());
341
+ REQUIRE(small_result.get_estimate() == Approx(100.0));
342
+ REQUIRE(std::is_sorted(small_result.begin(), small_result.end()));
343
+ REQUIRE_FALSE(small.compact(false, true).is_ordered());
344
+ }
345
+
112
346
  TEST_CASE("theta sketch: resize exact", "[theta_sketch]") {
113
347
  update_theta_sketch update_sketch = update_theta_sketch::builder().build();
114
348
  for (int i = 0; i < 2000; i++) update_sketch.update(i);
@@ -337,6 +571,50 @@ TEST_CASE("theta sketch: deserialize estimation mode buffer overrun", "[theta_sk
337
571
  REQUIRE_THROWS_AS(compact_theta_sketch::deserialize(bytes.data(), bytes.size() - 1), std::out_of_range);
338
572
  }
339
573
 
574
+ // The header fields of serial versions 1 to 3 that follow the first 8 bytes must not be read
575
+ // before the size of the buffer is checked. Each buffer below holds exactly the preamble bytes,
576
+ // so that reading beyond them would be an out-of-bounds read, and is followed by the bytes
577
+ // that would make the sketch look empty if they were read.
578
+ TEST_CASE("theta sketch: deserialize truncated preamble", "[theta_sketch]") {
579
+ const uint16_t seed_hash = compute_seed_hash(DEFAULT_SEED);
580
+ const uint8_t seed_hash_lo = seed_hash & 0xff;
581
+ const uint8_t seed_hash_hi = seed_hash >> 8;
582
+ const uint8_t max_theta[8] = {0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x7f};
583
+
584
+ // serial version 1: three preamble longs, num_entries = 0 and theta = max would be empty
585
+ std::vector<uint8_t> v1 = {3, 1, 3, 0, 0, 0x1a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
586
+ v1.insert(v1.end(), max_theta, max_theta + 8);
587
+ for (size_t size: {8, 16}) {
588
+ const std::vector<uint8_t> bytes(v1.begin(), v1.begin() + size);
589
+ REQUIRE_THROWS_AS(compact_theta_sketch::deserialize(bytes.data(), bytes.size()), std::out_of_range);
590
+ REQUIRE_THROWS_AS(wrapped_compact_theta_sketch::wrap(bytes.data(), bytes.size()), std::out_of_range);
591
+ }
592
+
593
+ // serial version 2: two preamble longs, num_entries = 0 would be empty
594
+ std::vector<uint8_t> v2_exact = {2, 2, 3, 0, 0, 0x1a, seed_hash_lo, seed_hash_hi, 0, 0, 0, 0, 0, 0, 0, 0};
595
+ {
596
+ const std::vector<uint8_t> bytes(v2_exact.begin(), v2_exact.begin() + 8);
597
+ REQUIRE_THROWS_AS(compact_theta_sketch::deserialize(bytes.data(), bytes.size()), std::out_of_range);
598
+ REQUIRE_THROWS_AS(wrapped_compact_theta_sketch::wrap(bytes.data(), bytes.size()), std::out_of_range);
599
+ }
600
+ REQUIRE(compact_theta_sketch::deserialize(v2_exact.data(), v2_exact.size()).is_empty());
601
+
602
+ // serial version 2: three preamble longs, num_entries = 0 and theta = max would be empty
603
+ std::vector<uint8_t> v2_estimation = {3, 2, 3, 0, 0, 0x1a, seed_hash_lo, seed_hash_hi, 0, 0, 0, 0, 0, 0, 0, 0};
604
+ v2_estimation.insert(v2_estimation.end(), max_theta, max_theta + 8);
605
+ for (size_t size: {8, 16}) {
606
+ const std::vector<uint8_t> bytes(v2_estimation.begin(), v2_estimation.begin() + size);
607
+ REQUIRE_THROWS_AS(compact_theta_sketch::deserialize(bytes.data(), bytes.size()), std::out_of_range);
608
+ REQUIRE_THROWS_AS(wrapped_compact_theta_sketch::wrap(bytes.data(), bytes.size()), std::out_of_range);
609
+ }
610
+ REQUIRE(compact_theta_sketch::deserialize(v2_estimation.data(), v2_estimation.size()).is_empty());
611
+
612
+ // serial version 3: two preamble longs, not empty
613
+ const std::vector<uint8_t> v3 = {2, 3, 3, 0, 0, 0x1a, seed_hash_lo, seed_hash_hi};
614
+ REQUIRE_THROWS_AS(compact_theta_sketch::deserialize(v3.data(), v3.size()), std::out_of_range);
615
+ REQUIRE_THROWS_AS(wrapped_compact_theta_sketch::wrap(v3.data(), v3.size()), std::out_of_range);
616
+ }
617
+
340
618
  TEST_CASE("theta sketch: conversion constructor and wrapped compact", "[theta_sketch]") {
341
619
  update_theta_sketch update_sketch = update_theta_sketch::builder().build();
342
620
  const int n = 8192;
@@ -625,4 +903,41 @@ TEST_CASE("max serialized size", "[theta_sketch]") {
625
903
  REQUIRE(max_size_bytes == compact_theta_sketch::get_max_serialized_size_bytes(lg_k));
626
904
  }
627
905
 
906
+ TEST_CASE("theta sketch: deserialize v4 corrupt header", "[theta_sketch]") {
907
+ update_theta_sketch update_sketch = update_theta_sketch::builder().build();
908
+ for (int i = 0; i < 100; ++i) update_sketch.update(i);
909
+ const auto bytes = update_sketch.compact().serialize_compressed();
910
+ REQUIRE(bytes[1] == 4);
911
+ for (uint8_t entry_bits: {0, 64, 255}) {
912
+ auto corrupt = bytes;
913
+ corrupt[3] = entry_bits;
914
+ REQUIRE_THROWS_AS(compact_theta_sketch::deserialize(corrupt.data(), corrupt.size()), std::invalid_argument);
915
+ std::stringstream s;
916
+ s.write(reinterpret_cast<const char*>(corrupt.data()), corrupt.size());
917
+ REQUIRE_THROWS_AS(compact_theta_sketch::deserialize(s), std::invalid_argument);
918
+ }
919
+ for (uint8_t num_entries_bytes: {0, 5, 255}) {
920
+ auto corrupt = bytes;
921
+ corrupt[4] = num_entries_bytes;
922
+ REQUIRE_THROWS_AS(compact_theta_sketch::deserialize(corrupt.data(), corrupt.size()), std::invalid_argument);
923
+ std::stringstream s;
924
+ s.write(reinterpret_cast<const char*>(corrupt.data()), corrupt.size());
925
+ REQUIRE_THROWS_AS(compact_theta_sketch::deserialize(s), std::invalid_argument);
926
+ }
927
+ }
928
+
929
+ TEST_CASE("theta sketch: deserialize v4 entry bits overflow", "[theta_sketch]") {
930
+ update_theta_sketch update_sketch = update_theta_sketch::builder().build();
931
+ for (int i = 0; i < 100; ++i) update_sketch.update(i);
932
+ auto bytes = update_sketch.compact().serialize_compressed();
933
+ REQUIRE(bytes[0] == 1); // exact mode, num_entries follows the first preamble long
934
+ // 8 bits * 2^29 entries = 2^32 bits wraps to 0 in 32-bit arithmetic
935
+ bytes.resize(12);
936
+ bytes[3] = 8;
937
+ bytes[4] = 4;
938
+ const uint32_t num_entries = 1U << 29;
939
+ std::memcpy(bytes.data() + 8, &num_entries, sizeof(num_entries));
940
+ REQUIRE_THROWS_AS(compact_theta_sketch::deserialize(bytes.data(), bytes.size()), std::out_of_range);
941
+ }
942
+
628
943
  } /* namespace datasketches */
@@ -0,0 +1,68 @@
1
+ #!/usr/bin/env bash
2
+ #
3
+ # Licensed to the Apache Software Foundation (ASF) under one
4
+ # or more contributor license agreements. See the NOTICE file
5
+ # distributed with this work for additional information
6
+ # regarding copyright ownership. The ASF licenses this file
7
+ # to you under the Apache License, Version 2.0 (the
8
+ # "License"); you may not use this file except in compliance
9
+ # with the License. You may obtain a copy of the License at
10
+ #
11
+ # http://www.apache.org/licenses/LICENSE-2.0
12
+ #
13
+ # Unless required by applicable law or agreed to in writing,
14
+ # software distributed under the License is distributed on an
15
+ # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
16
+ # KIND, either express or implied. See the License for the
17
+ # specific language governing permissions and limitations
18
+ # under the License.
19
+ #
20
+ # Runs the Apache Release Audit Tool over the tracked files of this repository
21
+ # and fails if any file lacks an approved license header.
22
+ #
23
+ # Only tracked files are examined: the tree is exported with `git archive`, so a
24
+ # local build directory or other untracked scratch cannot affect the result.
25
+ #
26
+ # Usage: tools/rat-check.sh [git-ref] (default: HEAD)
27
+
28
+ set -euo pipefail
29
+
30
+ RAT_VERSION="${RAT_VERSION:-0.16.1}"
31
+ RAT_JAR="${RAT_JAR:-}"
32
+ REF="${1:-HEAD}"
33
+
34
+ repo_root="$(git rev-parse --show-toplevel)"
35
+ cd "$repo_root"
36
+
37
+ work="$(mktemp -d)"
38
+ trap 'rm -rf "$work"' EXIT
39
+
40
+ if [[ -z "$RAT_JAR" ]]; then
41
+ RAT_JAR="$work/apache-rat.jar"
42
+ url="https://repo1.maven.org/maven2/org/apache/rat/apache-rat/${RAT_VERSION}/apache-rat-${RAT_VERSION}.jar"
43
+ echo "Downloading Apache RAT ${RAT_VERSION}"
44
+ curl -fsSL -o "$RAT_JAR" "$url"
45
+ fi
46
+
47
+ mkdir -p "$work/src"
48
+ git archive "$REF" | tar -x -C "$work/src"
49
+ cp .rat-excludes "$work/src/.rat-excludes"
50
+
51
+ report="$work/rat-report.txt"
52
+ java -jar "$RAT_JAR" -E "$work/src/.rat-excludes" -d "$work/src" > "$report" 2>/dev/null
53
+
54
+ unknown="$(sed -n 's/^\([0-9][0-9]*\) Unknown Licenses$/\1/p' "$report" | head -1)"
55
+ unknown="${unknown:-0}"
56
+
57
+ if [[ "$unknown" -ne 0 ]]; then
58
+ echo
59
+ echo "Apache RAT found ${unknown} file(s) without an approved license header:"
60
+ awk '/^Files with unapproved licenses:/{f=1;next} f&&/^\*\*\*\*/{exit} f&&NF{print " " $0}' "$report" \
61
+ | sed "s|$work/src/||"
62
+ echo
63
+ echo "Add the ASF header to each file. If a file is third-party source that must"
64
+ echo "keep its own notice, add it to .rat-excludes AND record it in LICENSE."
65
+ exit 1
66
+ fi
67
+
68
+ echo "Apache RAT: all tracked files carry an approved license header."
@@ -22,6 +22,8 @@
22
22
 
23
23
  #include <vector>
24
24
  #include <memory>
25
+ #include <type_traits>
26
+ #include <algorithm>
25
27
 
26
28
  #include "serde.hpp"
27
29
  #include "tuple_sketch.hpp"
@@ -34,17 +36,18 @@ class array {
34
36
  public:
35
37
  using value_type = T;
36
38
  using allocator_type = Allocator;
39
+ using alloc_traits = std::allocator_traits<Allocator>;
37
40
 
38
- explicit array(uint8_t size, T value, const Allocator& allocator = Allocator()):
41
+ explicit array(uint8_t size, const T& value, const Allocator& allocator = Allocator()):
39
42
  allocator_(allocator), size_(size), array_(allocator_.allocate(size_)) {
40
- std::fill(array_, array_ + size_, value);
43
+ init_values(value, std::is_trivially_copyable<T>());
41
44
  }
42
45
  array(const array& other):
43
46
  allocator_(other.allocator_),
44
47
  size_(other.size_),
45
48
  array_(allocator_.allocate(size_))
46
49
  {
47
- std::copy(other.array_, other.array_ + size_, array_);
50
+ copy_from(other, std::is_trivially_copyable<T>());
48
51
  }
49
52
  array(array&& other) noexcept:
50
53
  allocator_(std::move(other.allocator_)),
@@ -52,9 +55,13 @@ public:
52
55
  array_(other.array_)
53
56
  {
54
57
  other.array_ = nullptr;
58
+ other.size_ = 0;
55
59
  }
56
60
  ~array() {
57
- if (array_ != nullptr) allocator_.deallocate(array_, size_);
61
+ if (array_ != nullptr) {
62
+ destroy_values(std::is_trivially_destructible<T>());
63
+ allocator_.deallocate(array_, size_);
64
+ }
58
65
  }
59
66
  array& operator=(const array& other) {
60
67
  array copy(other);
@@ -75,10 +82,34 @@ public:
75
82
  T* data() { return array_; }
76
83
  const T* data() const { return array_; }
77
84
  bool operator==(const array& other) const {
85
+ if (size_ != other.size_) return false;
78
86
  for (uint8_t i = 0; i < size_; ++i) if (array_[i] != other.array_[i]) return false;
79
87
  return true;
80
88
  }
81
89
  private:
90
+ void init_values(const T& value, std::true_type) {
91
+ std::fill(array_, array_ + size_, value);
92
+ }
93
+ void init_values(const T& value, std::false_type) {
94
+ for (uint8_t i = 0; i < size_; ++i) {
95
+ alloc_traits::construct(allocator_, array_ + i, value);
96
+ }
97
+ }
98
+ void copy_from(const array& other, std::true_type) {
99
+ std::copy(other.array_, other.array_ + size_, array_);
100
+ }
101
+ void copy_from(const array& other, std::false_type) {
102
+ for (uint8_t i = 0; i < size_; ++i) {
103
+ alloc_traits::construct(allocator_, array_ + i, other.array_[i]);
104
+ }
105
+ }
106
+ void destroy_values(std::true_type) {}
107
+ void destroy_values(std::false_type) {
108
+ for (uint8_t i = 0; i < size_; ++i) {
109
+ alloc_traits::destroy(allocator_, array_ + i);
110
+ }
111
+ }
112
+
82
113
  Allocator allocator_;
83
114
  uint8_t size_;
84
115
  T* array_;
@@ -166,7 +166,7 @@ compact_array_tuple_sketch<Array, Allocator> compact_array_tuple_sketch<Array, A
166
166
  for (size_t i = 0; i < num_entries; ++i) {
167
167
  Array summary(num_values, 0, allocator);
168
168
  read(is, summary.data(), num_values * sizeof(typename Array::value_type));
169
- entries.push_back(Entry(keys[i], std::move(summary)));
169
+ entries.emplace_back(keys[i], std::move(summary));
170
170
  }
171
171
  }
172
172
  if (!is.good()) throw std::runtime_error("error reading from std::istream");
@@ -213,7 +213,7 @@ compact_array_tuple_sketch<Array, Allocator> compact_array_tuple_sketch<Array, A
213
213
  for (size_t i = 0; i < num_entries; ++i) {
214
214
  Array summary(num_values, 0, allocator);
215
215
  ptr += copy_from_mem(ptr, summary.data(), num_values * sizeof(typename Array::value_type));
216
- entries.push_back(Entry(keys[i], std::move(summary)));
216
+ entries.emplace_back(keys[i], std::move(summary));
217
217
  }
218
218
  }
219
219
  const bool is_empty = flags_byte & (1 << flags::IS_EMPTY);
@@ -46,6 +46,11 @@ struct pair_extract_key {
46
46
  /**
47
47
  * Base class for Tuple sketch.
48
48
  * This is an extension of Theta sketch that allows keeping arbitrary Summary associated with each retained key.
49
+ *
50
+ * Summary that may retain string values.
51
+ * For Summary containing strings, cross-language portability depends on
52
+ * using compatible string encodings. This class does not by itself enforce
53
+ * UTF-8 validity for all string inputs.
49
54
  */
50
55
  template<
51
56
  typename Summary,
@@ -253,6 +258,9 @@ public:
253
258
 
254
259
  /**
255
260
  * Update this sketch with a given string.
261
+ * If the summary contains strings and cross-language portability is required,
262
+ * callers should ensure that any strings in the summary
263
+ * use a compatible encoding (valid UTF-8).
256
264
  * @param key string to update the sketch with
257
265
  * @param value to update the sketch with
258
266
  */
@@ -261,6 +269,9 @@ public:
261
269
 
262
270
  /**
263
271
  * Update this sketch with a given unsigned 64-bit integer.
272
+ * If the summary contains strings and cross-language portability is required,
273
+ * callers should ensure that any strings in the summary
274
+ * use a compatible encoding (valid UTF-8).
264
275
  * @param key uint64_t to update the sketch with
265
276
  * @param value to update the sketch with
266
277
  */
@@ -269,6 +280,9 @@ public:
269
280
 
270
281
  /**
271
282
  * Update this sketch with a given signed 64-bit integer.
283
+ * If the summary contains strings and cross-language portability is required,
284
+ * callers should ensure that any strings in the summary
285
+ * use a compatible encoding (valid UTF-8).
272
286
  * @param key int64_t to update the sketch with
273
287
  * @param value to update the sketch with
274
288
  */
@@ -277,6 +291,9 @@ public:
277
291
 
278
292
  /**
279
293
  * Update this sketch with a given unsigned 32-bit integer.
294
+ * If the summary contains strings and cross-language portability is required,
295
+ * callers should ensure that any strings in the summary
296
+ * use a compatible encoding (valid UTF-8).
280
297
  * For compatibility with Java implementation.
281
298
  * @param key uint32_t to update the sketch with
282
299
  * @param value to update the sketch with
@@ -286,6 +303,9 @@ public:
286
303
 
287
304
  /**
288
305
  * Update this sketch with a given signed 32-bit integer.
306
+ * If the summary contains strings and cross-language portability is required,
307
+ * callers should ensure that any strings in the summary
308
+ * use a compatible encoding (valid UTF-8).
289
309
  * For compatibility with Java implementation.
290
310
  * @param key int32_t to update the sketch with
291
311
  * @param value to update the sketch with
@@ -295,6 +315,9 @@ public:
295
315
 
296
316
  /**
297
317
  * Update this sketch with a given unsigned 16-bit integer.
318
+ * If the summary contains strings and cross-language portability is required,
319
+ * callers should ensure that any strings in the summary
320
+ * use a compatible encoding (valid UTF-8).
298
321
  * For compatibility with Java implementation.
299
322
  * @param key uint16_t to update the sketch with
300
323
  * @param value to update the sketch with
@@ -304,6 +327,9 @@ public:
304
327
 
305
328
  /**
306
329
  * Update this sketch with a given signed 16-bit integer.
330
+ * If the summary contains strings and cross-language portability is required,
331
+ * callers should ensure that any strings in the summary
332
+ * use a compatible encoding (valid UTF-8).
307
333
  * For compatibility with Java implementation.
308
334
  * @param key int16_t to update the sketch with
309
335
  * @param value to update the sketch with
@@ -313,6 +339,9 @@ public:
313
339
 
314
340
  /**
315
341
  * Update this sketch with a given unsigned 8-bit integer.
342
+ * If the summary contains strings and cross-language portability is required,
343
+ * callers should ensure that any strings in the summary
344
+ * use a compatible encoding (valid UTF-8).
316
345
  * For compatibility with Java implementation.
317
346
  * @param key uint8_t to update the sketch with
318
347
  * @param value to update the sketch with
@@ -322,6 +351,9 @@ public:
322
351
 
323
352
  /**
324
353
  * Update this sketch with a given signed 8-bit integer.
354
+ * If the summary contains strings and cross-language portability is required,
355
+ * callers should ensure that any strings in the summary
356
+ * use a compatible encoding (valid UTF-8).
325
357
  * For compatibility with Java implementation.
326
358
  * @param key int8_t to update the sketch with
327
359
  * @param value to update the sketch with
@@ -331,6 +363,9 @@ public:
331
363
 
332
364
  /**
333
365
  * Update this sketch with a given double-precision floating point value.
366
+ * If the summary contains strings and cross-language portability is required,
367
+ * callers should ensure that any strings in the summary
368
+ * use a compatible encoding (valid UTF-8).
334
369
  * For compatibility with Java implementation.
335
370
  * @param key double to update the sketch with
336
371
  * @param value to update the sketch with
@@ -340,6 +375,9 @@ public:
340
375
 
341
376
  /**
342
377
  * Update this sketch with a given floating point value.
378
+ * If the summary contains strings and cross-language portability is required,
379
+ * callers should ensure that any strings in the summary
380
+ * use a compatible encoding (valid UTF-8).
343
381
  * For compatibility with Java implementation.
344
382
  * @param key float to update the sketch with
345
383
  * @param value to update the sketch with
@@ -357,6 +395,9 @@ public:
357
395
  * Otherwise two sketches that should represent overlapping sets will be disjoint
358
396
  * For instance, for signed 32-bit values call update(int32_t) method above,
359
397
  * which does widening conversion to int64_t, if compatibility with Java is expected
398
+ * If the summary contains strings and cross-language portability is required,
399
+ * callers should ensure that any strings in the summary
400
+ * use a compatible encoding (valid UTF-8).
360
401
  * @param key pointer to the data
361
402
  * @param length of the data in bytes
362
403
  * @param value to update the sketch with
@@ -315,7 +315,7 @@ entries_(other.get_allocator())
315
315
  {
316
316
  entries_.reserve(other.get_num_retained());
317
317
  for (uint64_t hash: other) {
318
- entries_.push_back(Entry(hash, summary));
318
+ entries_.emplace_back(hash, summary);
319
319
  }
320
320
  if (ordered && !other.is_ordered()) std::sort(entries_.begin(), entries_.end(), comparator());
321
321
  }
@@ -347,7 +347,7 @@ uint32_t compact_tuple_sketch<S, A>::get_num_retained() const {
347
347
 
348
348
  template<typename S, typename A>
349
349
  uint16_t compact_tuple_sketch<S, A>::get_seed_hash() const {
350
- return seed_hash_;
350
+ return is_empty_ ? 0 : seed_hash_;
351
351
  }
352
352
 
353
353
  template<typename S, typename A>
@@ -518,7 +518,7 @@ compact_tuple_sketch<S, A> compact_tuple_sketch<S, A>::deserialize(std::istream&
518
518
  for (size_t i = 0; i < num_entries; ++i) {
519
519
  const auto key = read<uint64_t>(is);
520
520
  sd.deserialize(is, summary.get(), 1);
521
- entries.push_back(Entry(key, std::move(*summary)));
521
+ entries.emplace_back(key, std::move(*summary));
522
522
  (*summary).~S();
523
523
  }
524
524
  }
@@ -583,9 +583,10 @@ compact_tuple_sketch<S, A> compact_tuple_sketch<S, A>::deserialize(const void* b
583
583
  std::unique_ptr<S, deleter_of_summaries> summary(alloc.allocate(1), deleter_of_summaries(1, false, allocator));
584
584
  for (size_t i = 0; i < num_entries; ++i) {
585
585
  uint64_t key;
586
+ ensure_minimum_memory(base + size - ptr, sizeof(uint64_t));
586
587
  ptr += copy_from_mem(ptr, key);
587
588
  ptr += sd.deserialize(ptr, base + size - ptr, summary.get(), 1);
588
- entries.push_back(Entry(key, std::move(*summary)));
589
+ entries.emplace_back(key, std::move(*summary));
589
590
  (*summary).~S();
590
591
  }
591
592
  }