-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathhll_test.cpp
More file actions
280 lines (254 loc) · 8.22 KB
/
Copy pathhll_test.cpp
File metadata and controls
280 lines (254 loc) · 8.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
/**
* @file hll_test.cpp
* @brief Unit tests for HyperLogLog implementation
*/
#include "common/hll.hpp"
#include <gtest/gtest.h>
#include <algorithm>
#include <cstdint>
#include <string>
#include <vector>
#include "common/value.hpp"
using namespace cloudsql::common;
namespace {
/**
* @brief Tests empty HLL returns 0 cardinality.
*/
TEST(HyperLogLogTests, EmptyCardinality) {
HyperLogLog hll;
EXPECT_EQ(hll.cardinality(), 0U);
}
/**
* @brief Tests that inserting a value produces a non-zero cardinality.
*/
TEST(HyperLogLogTests, NonEmptyAfterInsert) {
HyperLogLog hll;
hll.insert(42);
uint64_t card = hll.cardinality();
EXPECT_GT(card, 0U);
}
/**
* @brief Tests that inserting the same value many times gives consistent cardinality.
*/
TEST(HyperLogLogTests, RepeatedValueConsistency) {
HyperLogLog hll;
for (int i = 0; i < 1000; ++i) {
hll.insert(42);
}
uint64_t card = hll.cardinality();
EXPECT_GT(card, 0U);
}
/**
* @brief Tests that inserting many distinct values gives non-trivial cardinality.
*/
TEST(HyperLogLogTests, DistinctValuesProduceCardinality) {
HyperLogLog hll;
uint64_t val = 123456789ULL;
for (int i = 0; i < 1000; ++i) {
hll.insert(val);
val = val * 6364136223846793005ULL + 1442695043ULL;
}
uint64_t card = hll.cardinality();
EXPECT_GT(card, 0U);
}
/**
* @brief Tests that both small and large distinct value sets produce non-zero cardinality.
*/
TEST(HyperLogLogTests, DistinctValueSetsProduceCardinality) {
HyperLogLog hll_small;
HyperLogLog hll_large;
uint64_t val = 123456789ULL;
for (int i = 0; i < 100; ++i) {
hll_small.insert(val);
val = val * 6364136223846793005ULL + 1442695043ULL;
}
for (int i = 0; i < 1000; ++i) {
hll_large.insert(val);
val = val * 6364136223846793005ULL + 1442695043ULL;
}
EXPECT_GT(hll_small.cardinality(), 0U);
EXPECT_GT(hll_large.cardinality(), 0U);
}
/**
* @brief Tests hash_bytes produces consistent hashes.
*/
TEST(HyperLogLogTests, HashBytesConsistency) {
std::string data = "hello world";
uint64_t h1 = HyperLogLog::hash_bytes(data.data(), data.size());
uint64_t h2 = HyperLogLog::hash_bytes(data.data(), data.size());
EXPECT_EQ(h1, h2);
}
/**
* @brief Tests hash_bytes differs for different inputs.
*/
TEST(HyperLogLogTests, HashBytesDiffersForDifferentInput) {
std::string a = "hello";
std::string b = "world";
uint64_t ha = HyperLogLog::hash_bytes(a.data(), a.size());
uint64_t hb = HyperLogLog::hash_bytes(b.data(), b.size());
EXPECT_NE(ha, hb);
}
/**
* @brief Tests reset clears all registers back to zero.
*/
TEST(HyperLogLogTests, ResetClearsRegisters) {
HyperLogLog hll;
uint64_t val = 123456789ULL;
for (int i = 0; i < 100; ++i) {
hll.insert(val);
val = val * 6364136223846793005ULL + 1442695043ULL;
}
hll.reset();
EXPECT_EQ(hll.cardinality(), 0U);
}
/**
* @brief Tests merge combines two HLLs by taking element-wise max.
*/
TEST(HyperLogLogTests, MergeCombinesDistinctSets) {
HyperLogLog hll1;
HyperLogLog hll2;
uint64_t val = 123456789ULL;
for (int i = 0; i < 100; ++i) {
hll1.insert(val);
val = val * 6364136223846793005ULL + 1442695043ULL;
}
for (int i = 0; i < 100; ++i) {
hll2.insert(val);
val = val * 6364136223846793005ULL + 1442695043ULL;
}
hll1.merge(hll2);
EXPECT_GT(hll1.cardinality(), 0U);
}
/**
* @brief Tests with text values via hash_bytes.
*/
TEST(HyperLogLogTests, TextValueInsertion) {
HyperLogLog hll;
std::vector<std::string> texts = {"alpha", "beta", "gamma", "delta", "epsilon",
"zeta", "eta", "theta", "iota", "kappa"};
for (const auto& t : texts) {
uint64_t hash = HyperLogLog::hash_bytes(t.data(), t.size());
hll.insert(hash);
}
uint64_t card = hll.cardinality();
EXPECT_GT(card, 0U);
}
/**
* @brief Tests accuracy bounds for distinct values.
* HLL is a probabilistic estimator with ~1.6% standard error for large cardinalities.
* For smaller cardinalities the error can be larger, so we use a very loose bound
* (cardinality > 0 and reasonable upper bound).
*/
TEST(HyperLogLogTests, AccuracyBoundsDistinct) {
HyperLogLog hll;
uint64_t val = 123456789ULL;
for (int i = 0; i < 1000; ++i) {
hll.insert(val);
val = val * 6364136223846793005ULL + 1442695043ULL;
}
uint64_t card = hll.cardinality();
// Must be positive
EXPECT_GT(card, 0U);
// Upper bound: 1000 distinct values can't estimate more than 100000
EXPECT_LT(card, 100000U);
}
/**
* @brief Tests merge with overlapping sets.
* Uses distinct LCG-generated values for hll1 and hll2 to ensure good
* hash distribution across registers (avoids sequential value collisions).
*/
TEST(HyperLogLogTests, MergeOverlappingSets) {
HyperLogLog hll1;
HyperLogLog hll2;
uint64_t val = 123456789ULL;
for (int i = 0; i < 100; ++i) {
hll1.insert(val);
val = val * 6364136223846793005ULL + 1442695043ULL;
}
uint64_t val2 = 987654321ULL;
for (int i = 0; i < 100; ++i) {
hll2.insert(val2);
val2 = val2 * 6364136223846793005ULL + 1442695043ULL;
}
uint64_t card1 = hll1.cardinality();
uint64_t card2 = hll2.cardinality();
hll1.merge(hll2);
uint64_t merged = hll1.cardinality();
// Merged cardinality should be >= either individual
EXPECT_GE(merged, card1);
EXPECT_GE(merged, card2);
// Both sets are disjoint with good distribution, merged should be in a reasonable range
EXPECT_LT(merged, 50000U); // Sanity upper bound
}
/**
* @brief Tests seed reproducibility — same seed gives same cardinality.
*/
TEST(HyperLogLogTests, SeedReproducibility) {
HyperLogLog hll1(42);
HyperLogLog hll2(42);
uint64_t val = 123456789ULL;
for (int i = 0; i < 500; ++i) {
hll1.insert(val);
val = val * 6364136223846793005ULL + 1442695043ULL;
}
val = 123456789ULL;
for (int i = 0; i < 500; ++i) {
hll2.insert(val);
val = val * 6364136223846793005ULL + 1442695043ULL;
}
EXPECT_EQ(hll1.cardinality(), hll2.cardinality());
}
/**
* @brief Tests different seeds produce different cardinalities.
* Seed is XORed onto the hash, so different seeds produce different
* register distributions and thus different cardinality estimates.
*/
TEST(HyperLogLogTests, DifferentSeedsDiffer) {
HyperLogLog hll1(0);
HyperLogLog hll2(12345); // Large seed difference ensures different register distributions
uint64_t val = 123456789ULL;
for (int i = 0; i < 500; ++i) {
hll1.insert(val);
hll2.insert(val);
val = val * 6364136223846793005ULL + 1442695043ULL;
}
EXPECT_NE(hll1.cardinality(), hll2.cardinality());
}
/**
* @brief Tests HLL with different ValueType columns.
* Verifies the integration path used by execute_analyze() — Value::Hash{}
* for numeric types, hash_bytes() for text types.
*/
TEST(HyperLogLogTests, ValueTypeColumnCoverage) {
HyperLogLog hll_int;
HyperLogLog hll_bigint;
HyperLogLog hll_double;
HyperLogLog hll_text;
// INT64 values
for (int64_t i = 0; i < 200; ++i) {
Value v = Value::make_int64(i);
hll_int.insert(static_cast<uint64_t>(Value::Hash{}(v)));
}
EXPECT_GT(hll_int.cardinality(), 0U);
// BIGINT values (larger range)
for (int64_t i = 0; i < 200; ++i) {
Value v = Value::make_int64(i * 1000000000LL);
hll_bigint.insert(static_cast<uint64_t>(Value::Hash{}(v)));
}
EXPECT_GT(hll_bigint.cardinality(), 0U);
// DOUBLE (float64) values
for (int i = 0; i < 200; ++i) {
Value v = Value::make_float64(static_cast<double>(i) * 1.5);
hll_double.insert(static_cast<uint64_t>(Value::Hash{}(v)));
}
EXPECT_GT(hll_double.cardinality(), 0U);
// TEXT values via hash_bytes (mimics execute_analyze path)
std::vector<std::string> texts = {"alpha", "beta", "gamma", "delta", "epsilon",
"zeta", "eta", "theta", "iota", "kappa"};
for (const auto& t : texts) {
uint64_t hash = HyperLogLog::hash_bytes(t.data(), t.size());
hll_text.insert(hash);
}
EXPECT_GT(hll_text.cardinality(), 0U);
}
} // namespace