Skip to content

Commit 4894e5e

Browse files
committed
fix: allocation handling for string in deserialize
1 parent 189d22d commit 4894e5e

5 files changed

Lines changed: 124 additions & 62 deletions

tuple/include/array_of_strings_sketch.hpp

Lines changed: 34 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,22 @@
2828

2929
namespace datasketches {
3030

31+
template<typename Allocator>
32+
struct array_of_strings_types {
33+
using string_allocator = typename std::allocator_traits<Allocator>::template rebind_alloc<char>;
34+
using string_type = std::basic_string<char, std::char_traits<char>, string_allocator>;
35+
using array_allocator = typename std::allocator_traits<Allocator>::template rebind_alloc<string_type>;
36+
using array_of_strings = array<string_type, array_allocator>;
37+
};
38+
3139
// default update policy for an array of strings
32-
template<typename Allocator = std::allocator<std::string>>
40+
template<typename Allocator = std::allocator<char>>
3341
class default_array_of_strings_update_policy {
3442
public:
35-
using array_of_strings = array<std::string, Allocator>;
43+
using string_allocator = typename array_of_strings_types<Allocator>::string_allocator;
44+
using string_type = typename array_of_strings_types<Allocator>::string_type;
45+
using array_allocator = typename array_of_strings_types<Allocator>::array_allocator;
46+
using array_of_strings = typename array_of_strings_types<Allocator>::array_of_strings;
3647

3748
explicit default_array_of_strings_update_policy(const Allocator& allocator = Allocator());
3849

@@ -48,9 +59,12 @@ class default_array_of_strings_update_policy {
4859

4960
// serializer/deserializer for an array of strings
5061
// Requirements: all strings must be valid UTF-8 and array size must be <= 127.
51-
template<typename Allocator = std::allocator<std::string>>
62+
template<typename Allocator = std::allocator<char>>
5263
struct default_array_of_strings_serde {
53-
using array_of_strings = array<std::string, Allocator>;
64+
using string_allocator = typename array_of_strings_types<Allocator>::string_allocator;
65+
using string_type = typename array_of_strings_types<Allocator>::string_type;
66+
using array_allocator = typename array_of_strings_types<Allocator>::array_allocator;
67+
using array_of_strings = typename array_of_strings_types<Allocator>::array_of_strings;
5468
using summary_allocator = typename std::allocator_traits<Allocator>::template rebind_alloc<array_of_strings>;
5569

5670
explicit default_array_of_strings_serde(const Allocator& allocator = Allocator());
@@ -66,27 +80,29 @@ struct default_array_of_strings_serde {
6680
summary_allocator summary_allocator_;
6781
static void check_num_nodes(uint8_t num_nodes);
6882
static uint32_t compute_total_bytes(const array_of_strings& item);
69-
static void check_utf8(const std::string& value);
83+
static void check_utf8(const string_type& value);
7084
};
7185

7286
/**
7387
* Hashes an array of strings using ArrayOfStrings-compatible hashing.
7488
*/
75-
template<typename Allocator = std::allocator<std::string>>
76-
uint64_t hash_array_of_strings_key(const array<std::string, Allocator>& key);
89+
template<typename Allocator = std::allocator<char>>
90+
uint64_t hash_array_of_strings_key(const typename array_of_strings_types<Allocator>::array_of_strings& key);
7791

7892
/**
7993
* Extended class of compact_tuple_sketch for array of strings
8094
* Requirements: all strings must be valid UTF-8 and array size must be <= 127.
8195
*/
82-
template<typename Allocator = std::allocator<std::string>>
96+
template<typename Allocator = std::allocator<char>>
8397
class compact_array_of_strings_tuple_sketch:
8498
public compact_tuple_sketch<
85-
array<std::string, Allocator>,
86-
typename std::allocator_traits<Allocator>::template rebind_alloc<array<std::string, Allocator>>
99+
typename array_of_strings_types<Allocator>::array_of_strings,
100+
typename std::allocator_traits<Allocator>::template rebind_alloc<
101+
typename array_of_strings_types<Allocator>::array_of_strings
102+
>
87103
> {
88104
public:
89-
using array_of_strings = array<std::string, Allocator>;
105+
using array_of_strings = typename array_of_strings_types<Allocator>::array_of_strings;
90106
using summary_allocator = typename std::allocator_traits<Allocator>::template rebind_alloc<array_of_strings>;
91107
using Base = compact_tuple_sketch<array_of_strings, summary_allocator>;
92108
using vector_bytes = typename Base::vector_bytes;
@@ -133,13 +149,15 @@ class compact_array_of_strings_tuple_sketch:
133149
/**
134150
* Convenience alias for update_tuple_sketch for array of strings
135151
*/
136-
template<typename Allocator = std::allocator<std::string>,
152+
template<typename Allocator = std::allocator<char>,
137153
typename Policy = default_array_of_strings_update_policy<Allocator>>
138154
using update_array_of_strings_tuple_sketch = update_tuple_sketch<
139-
array<std::string, Allocator>,
140-
array<std::string, Allocator>,
155+
typename array_of_strings_types<Allocator>::array_of_strings,
156+
typename array_of_strings_types<Allocator>::array_of_strings,
141157
Policy,
142-
typename std::allocator_traits<Allocator>::template rebind_alloc<array<std::string, Allocator>>
158+
typename std::allocator_traits<Allocator>::template rebind_alloc<
159+
typename array_of_strings_types<Allocator>::array_of_strings
160+
>
143161
>;
144162

145163
/**
@@ -148,7 +166,7 @@ using update_array_of_strings_tuple_sketch = update_tuple_sketch<
148166
* @param ordered optional flag to specify if an ordered sketch should be produced
149167
* @return compact array of strings sketch
150168
*/
151-
template<typename Allocator = std::allocator<std::string>, typename Policy = default_array_of_strings_update_policy<Allocator>>
169+
template<typename Allocator = std::allocator<char>, typename Policy = default_array_of_strings_update_policy<Allocator>>
152170
compact_array_of_strings_tuple_sketch<Allocator> compact_array_of_strings_sketch(
153171
const update_array_of_strings_tuple_sketch<Allocator, Policy>& sketch, bool ordered = true);
154172

tuple/include/array_of_strings_sketch_impl.hpp

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -33,15 +33,17 @@ default_array_of_strings_update_policy<Allocator>::default_array_of_strings_upda
3333

3434
template<typename Allocator>
3535
auto default_array_of_strings_update_policy<Allocator>::create() const -> array_of_strings {
36-
return array_of_strings(0, "", allocator_);
36+
const string_type empty{string_allocator(allocator_)};
37+
return array_of_strings(0, empty, array_allocator(allocator_));
3738
}
3839

3940
template<typename Allocator>
4041
void default_array_of_strings_update_policy<Allocator>::update(
4142
array_of_strings& array, const array_of_strings& input
4243
) const {
4344
const auto length = static_cast<size_t>(input.size());
44-
array = array_of_strings(static_cast<uint8_t>(length), "", allocator_);
45+
const string_type empty{string_allocator(allocator_)};
46+
array = array_of_strings(static_cast<uint8_t>(length), empty, array_allocator(allocator_));
4547
for (size_t i = 0; i < length; ++i) array[i] = input[i];
4648
}
4749

@@ -50,16 +52,18 @@ void default_array_of_strings_update_policy<Allocator>::update(
5052
array_of_strings& array, const array_of_strings* input
5153
) const {
5254
if (input == nullptr) {
53-
array = array_of_strings(0, "", allocator_);
55+
const string_type empty{string_allocator(allocator_)};
56+
array = array_of_strings(0, empty, array_allocator(allocator_));
5457
return;
5558
}
5659
const auto length = static_cast<size_t>(input->size());
57-
array = array_of_strings(static_cast<uint8_t>(length), "", allocator_);
60+
const string_type empty{string_allocator(allocator_)};
61+
array = array_of_strings(static_cast<uint8_t>(length), empty, array_allocator(allocator_));
5862
for (size_t i = 0; i < length; ++i) array[i] = (*input)[i];
5963
}
6064

6165
template<typename Allocator>
62-
uint64_t hash_array_of_strings_key(const array<std::string, Allocator>& key) {
66+
uint64_t hash_array_of_strings_key(const typename array_of_strings_types<Allocator>::array_of_strings& key) {
6367
// Matches Java Util.PRIME for ArrayOfStrings key hashing.
6468
static constexpr uint64_t STRING_ARR_HASH_SEED = 0x7A3CCA71ULL;
6569
XXHash64 hasher(STRING_ARR_HASH_SEED);
@@ -124,7 +128,7 @@ void default_array_of_strings_serde<Allocator>::serialize(
124128
const uint8_t num_nodes = static_cast<uint8_t>(items[i].size());
125129
write(os, total_bytes);
126130
write(os, num_nodes);
127-
const std::string* data = items[i].data();
131+
const string_type* data = items[i].data();
128132
for (uint8_t j = 0; j < num_nodes; ++j) {
129133
check_utf8(data[j]);
130134
const uint32_t length = static_cast<uint32_t>(data[j].size());
@@ -144,11 +148,12 @@ void default_array_of_strings_serde<Allocator>::deserialize(
144148
const uint8_t num_nodes = read<uint8_t>(is);
145149
if (!is) throw std::runtime_error("array_of_strings stream read failed");
146150
check_num_nodes(num_nodes);
147-
array_of_strings array(num_nodes, "", allocator_);
151+
const string_type empty{string_allocator(allocator_)};
152+
array_of_strings array(num_nodes, empty, array_allocator(allocator_));
148153
for (uint8_t j = 0; j < num_nodes; ++j) {
149154
const uint32_t length = read<uint32_t>(is);
150155
if (!is) throw std::runtime_error("array_of_strings stream read failed");
151-
std::string value(length, '\0');
156+
string_type value(length, '\0', string_allocator(allocator_));
152157
if (length != 0) {
153158
is.read(&value[0], length);
154159
if (!is) throw std::runtime_error("array_of_strings stream read failed");
@@ -174,7 +179,7 @@ size_t default_array_of_strings_serde<Allocator>::serialize(
174179
check_memory_size(bytes_written + total_bytes, capacity);
175180
bytes_written += copy_to_mem(total_bytes, ptr8 + bytes_written);
176181
bytes_written += copy_to_mem(num_nodes, ptr8 + bytes_written);
177-
const std::string* data = items[i].data();
182+
const string_type* data = items[i].data();
178183
for (uint8_t j = 0; j < num_nodes; ++j) {
179184
check_utf8(data[j]);
180185
const uint32_t length = static_cast<uint32_t>(data[j].size());
@@ -202,11 +207,12 @@ size_t default_array_of_strings_serde<Allocator>::deserialize(
202207
uint8_t num_nodes;
203208
bytes_read += copy_from_mem(ptr8 + bytes_read, num_nodes);
204209
check_num_nodes(num_nodes);
205-
array_of_strings array(num_nodes, "", allocator_);
210+
const string_type empty{string_allocator(allocator_)};
211+
array_of_strings array(num_nodes, empty, array_allocator(allocator_));
206212
for (uint8_t j = 0; j < num_nodes; ++j) {
207213
uint32_t length;
208214
bytes_read += copy_from_mem(ptr8 + bytes_read, length);
209-
std::string value(length, '\0');
215+
string_type value(length, '\0', string_allocator(allocator_));
210216
if (length != 0) {
211217
bytes_read += copy_from_mem(ptr8 + bytes_read, &value[0], length);
212218
}
@@ -236,15 +242,15 @@ uint32_t default_array_of_strings_serde<Allocator>::compute_total_bytes(const ar
236242
const auto count = item.size();
237243
check_num_nodes(static_cast<uint8_t>(count));
238244
size_t total = sizeof(uint32_t) + sizeof(uint8_t) + count * sizeof(uint32_t);
239-
const std::string* data = item.data();
245+
const string_type* data = item.data();
240246
for (uint32_t j = 0; j < count; ++j) {
241247
total += data[j].size();
242248
}
243249
return static_cast<uint32_t>(total);
244250
}
245251

246252
template<typename Allocator>
247-
void default_array_of_strings_serde<Allocator>::check_utf8(const std::string& value) {
253+
void default_array_of_strings_serde<Allocator>::check_utf8(const string_type& value) {
248254
if (!utf8::is_valid(value.begin(), value.end())) {
249255
throw std::runtime_error("array_of_strings contains invalid UTF-8");
250256
}

tuple/test/aos_sketch_deserialize_from_java_test.cpp

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,13 +17,20 @@
1717
* under the License.
1818
*/
1919

20+
#include <algorithm>
2021
#include <catch2/catch.hpp>
2122
#include <fstream>
2223
#include <vector>
2324

2425
#include "array_of_strings_sketch.hpp"
2526

2627
namespace datasketches {
28+
using types = array_of_strings_types<std::allocator<char>>;
29+
using string_type = types::string_type;
30+
31+
static bool equals_string(const string_type& lhs, const std::string& rhs) {
32+
return lhs.size() == rhs.size() && std::equal(lhs.begin(), lhs.end(), rhs.begin());
33+
}
2734
// assume the binary sketches for this test have been generated by datasketches-java code
2835
// in the subdirectory called "java" in the root directory of this project
2936
static std::string testBinaryInputPath = std::string(TEST_BINARY_INPUT_PATH) + "../../java/";
@@ -193,7 +200,7 @@ namespace datasketches {
193200
if (entry.second.size() != expected.size()) continue;
194201
bool equal = true;
195202
for (size_t j = 0; j < expected.size(); ++j) {
196-
if (entry.second[j] != expected[j]) {
203+
if (!equals_string(entry.second[j], expected[j])) {
197204
equal = false;
198205
break;
199206
}
@@ -248,7 +255,7 @@ namespace datasketches {
248255
if (entry.second.size() != expected.size()) continue;
249256
bool equal = true;
250257
for (size_t j = 0; j < expected.size(); ++j) {
251-
if (entry.second[j] != expected[j]) {
258+
if (!equals_string(entry.second[j], expected[j])) {
252259
equal = false;
253260
break;
254261
}

tuple/test/aos_sketch_serialize_for_java.cpp

Lines changed: 38 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -26,13 +26,18 @@
2626
namespace datasketches {
2727

2828
using aos_sketch = update_array_of_strings_tuple_sketch<>;
29-
using array_of_strings = array<std::string>;
29+
using types = array_of_strings_types<std::allocator<char>>;
30+
using array_of_strings = types::array_of_strings;
31+
using string_allocator = types::string_allocator;
32+
using string_type = types::string_type;
33+
using array_allocator = types::array_allocator;
3034

3135
static array_of_strings make_array(std::initializer_list<std::string> items) {
32-
array_of_strings array(static_cast<uint8_t>(items.size()), "");
36+
const string_type empty{string_allocator()};
37+
array_of_strings array(static_cast<uint8_t>(items.size()), empty, array_allocator());
3338
size_t i = 0;
3439
for (const auto& item: items) {
35-
array[static_cast<uint8_t>(i)] = item;
40+
array[static_cast<uint8_t>(i)] = string_type(item.data(), item.size(), string_allocator());
3641
++i;
3742
}
3843
return array;
@@ -43,10 +48,13 @@ TEST_CASE("aos sketch generate one value", "[serialize_for_java]") {
4348
for (const unsigned n: n_arr) {
4449
auto sketch = aos_sketch::builder().build();
4550
for (unsigned i = 0; i < n; ++i) {
46-
array_of_strings key(1, "");
47-
key[0] = std::to_string(i);
48-
array_of_strings value(1, "");
49-
value[0] = "value" + std::to_string(i);
51+
const string_type empty{string_allocator()};
52+
array_of_strings key(1, empty, array_allocator());
53+
const std::string key_value = std::to_string(i);
54+
key[0] = string_type(key_value.data(), key_value.size(), string_allocator());
55+
array_of_strings value(1, empty, array_allocator());
56+
const std::string value_str = "value" + std::to_string(i);
57+
value[0] = string_type(value_str.data(), value_str.size(), string_allocator());
5058
sketch.update(hash_array_of_strings_key(key), value);
5159
}
5260
REQUIRE(sketch.is_empty() == (n == 0));
@@ -61,12 +69,17 @@ TEST_CASE("aos sketch generate three values", "[serialize_for_java]") {
6169
for (const unsigned n: n_arr) {
6270
auto sketch = aos_sketch::builder().build();
6371
for (unsigned i = 0; i < n; ++i) {
64-
array_of_strings key(1, "");
65-
key[0] = std::to_string(i);
66-
array_of_strings value(3, "");
67-
value[0] = "a" + std::to_string(i);
68-
value[1] = "b" + std::to_string(i);
69-
value[2] = "c" + std::to_string(i);
72+
const string_type empty{string_allocator()};
73+
array_of_strings key(1, empty, array_allocator());
74+
const std::string key_value = std::to_string(i);
75+
key[0] = string_type(key_value.data(), key_value.size(), string_allocator());
76+
array_of_strings value(3, empty, array_allocator());
77+
const std::string value_a = "a" + std::to_string(i);
78+
const std::string value_b = "b" + std::to_string(i);
79+
const std::string value_c = "c" + std::to_string(i);
80+
value[0] = string_type(value_a.data(), value_a.size(), string_allocator());
81+
value[1] = string_type(value_b.data(), value_b.size(), string_allocator());
82+
value[2] = string_type(value_c.data(), value_c.size(), string_allocator());
7083
sketch.update(hash_array_of_strings_key(key), value);
7184
}
7285
REQUIRE(sketch.is_empty() == (n == 0));
@@ -82,9 +95,10 @@ TEST_CASE("aos sketch generate non-empty no entries", "[serialize_for_java]") {
8295
.set_resize_factor(resize_factor::X8)
8396
.set_p(0.01f)
8497
.build();
85-
array_of_strings key(1, "");
98+
const string_type empty{string_allocator()};
99+
array_of_strings key(1, empty, array_allocator());
86100
key[0] = "key1";
87-
array_of_strings value(1, "");
101+
array_of_strings value(1, empty, array_allocator());
88102
value[0] = "value1";
89103
sketch.update(hash_array_of_strings_key(key), value);
90104
REQUIRE_FALSE(sketch.is_empty());
@@ -98,11 +112,15 @@ TEST_CASE("aos sketch generate multi key strings", "[serialize_for_java]") {
98112
for (const unsigned n: n_arr) {
99113
auto sketch = aos_sketch::builder().build();
100114
for (unsigned i = 0; i < n; ++i) {
101-
array_of_strings key(2, "");
102-
key[0] = "key" + std::to_string(i);
103-
key[1] = "subkey" + std::to_string(i % 10);
104-
array_of_strings value(1, "");
105-
value[0] = "value" + std::to_string(i);
115+
const string_type empty{string_allocator()};
116+
array_of_strings key(2, empty, array_allocator());
117+
const std::string key0 = "key" + std::to_string(i);
118+
const std::string key1 = "subkey" + std::to_string(i % 10);
119+
key[0] = string_type(key0.data(), key0.size(), string_allocator());
120+
key[1] = string_type(key1.data(), key1.size(), string_allocator());
121+
array_of_strings value(1, empty, array_allocator());
122+
const std::string value_str = "value" + std::to_string(i);
123+
value[0] = string_type(value_str.data(), value_str.size(), string_allocator());
106124
sketch.update(hash_array_of_strings_key(key), value);
107125
}
108126
REQUIRE(sketch.is_empty() == (n == 0));

0 commit comments

Comments
 (0)