/Users/deen/code/yugabyte-db/src/yb/docdb/doc_key-test.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright (c) YugaByte, Inc. |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except |
4 | | // in compliance with the License. You may obtain a copy of the License at |
5 | | // |
6 | | // http://www.apache.org/licenses/LICENSE-2.0 |
7 | | // |
8 | | // Unless required by applicable law or agreed to in writing, software distributed under the License |
9 | | // is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express |
10 | | // or implied. See the License for the specific language governing permissions and limitations |
11 | | // under the License. |
12 | | // |
13 | | |
14 | | #include "yb/docdb/doc_key.h" |
15 | | |
16 | | #include <memory> |
17 | | |
18 | | #include "yb/rocksdb/table.h" |
19 | | |
20 | | #include "yb/docdb/docdb_test_util.h" |
21 | | #include "yb/gutil/strings/substitute.h" |
22 | | #include "yb/util/bytes_formatter.h" |
23 | | #include "yb/util/decimal.h" |
24 | | #include "yb/util/net/net_util.h" |
25 | | #include "yb/util/string_trim.h" |
26 | | #include "yb/util/test_macros.h" |
27 | | #include "yb/util/test_util.h" |
28 | | |
29 | | using std::unique_ptr; |
30 | | using strings::Substitute; |
31 | | using yb::util::ApplyEagerLineContinuation; |
32 | | using yb::FormatSliceAsStr; |
33 | | using rocksdb::FilterBitsBuilder; |
34 | | using rocksdb::FilterBitsReader; |
35 | | |
36 | | using namespace std::placeholders; |
37 | | |
38 | | static constexpr int kNumDocOrSubDocKeysPerBatch = 1000; |
39 | | static constexpr int kNumTestDocOrSubDocKeyComparisons = 10000; |
40 | | |
41 | | static_assert(kNumDocOrSubDocKeysPerBatch < kNumTestDocOrSubDocKeyComparisons, |
42 | | "Number of document/subdocument key pairs to compare must be greater than the " |
43 | | "number of random document/subdocument keys to choose from."); |
44 | | static_assert(kNumTestDocOrSubDocKeyComparisons < |
45 | | kNumDocOrSubDocKeysPerBatch * kNumDocOrSubDocKeysPerBatch, // NOLINT |
46 | | "Number of document/subdocument key pairs to compare must be less than the maximum " |
47 | | "theoretical number of such pairs given how many keys we generate to choose from."); |
48 | | |
49 | | namespace yb { |
50 | | namespace docdb { |
51 | | |
52 | | // Note on the exact hash value we're using here: 0x4868 should show up as "Hh" in ASCII. |
53 | | const DocKeyHash kAsciiFriendlyHash = 0x4868; |
54 | | |
55 | | class DocKeyTest : public YBTest { |
56 | | protected: |
57 | 0 | vector<SubDocKey> GetVariedSubDocKeys() { |
58 | 0 | const int kMaxNumHashKeys = 3; |
59 | 0 | const int kMaxNumRangeKeys = 3; |
60 | 0 | const int kMaxNumSubKeys = 3; |
61 | 0 | vector<SubDocKey> sub_doc_keys; |
62 | 0 | Uuid cotable_id; |
63 | 0 | EXPECT_OK(cotable_id.FromHexString("0123456789abcdef0123456789abcdef")); |
64 | |
|
65 | 0 | std::vector<std::pair<Uuid, PgTableOid>> table_id_pairs; |
66 | 0 | table_id_pairs.emplace_back(cotable_id, 0); |
67 | 0 | table_id_pairs.emplace_back(Uuid::Nil(), 9911); |
68 | 0 | table_id_pairs.emplace_back(Uuid::Nil(), 0); |
69 | |
|
70 | 0 | for (const auto& table_id_pair : table_id_pairs) { |
71 | 0 | for (int num_hash_keys = 0; num_hash_keys <= kMaxNumHashKeys; ++num_hash_keys) { |
72 | 0 | for (int num_range_keys = 0; num_range_keys <= kMaxNumRangeKeys; ++num_range_keys) { |
73 | 0 | for (int num_sub_keys = 0; num_sub_keys <= kMaxNumSubKeys; ++num_sub_keys) { |
74 | 0 | for (bool has_hybrid_time : {false, true}) { |
75 | 0 | SubDocKey sub_doc_key; |
76 | |
|
77 | 0 | if (!table_id_pair.first.IsNil()) { |
78 | 0 | sub_doc_key.doc_key().set_cotable_id(cotable_id); |
79 | 0 | } else if (table_id_pair.second > 0) { |
80 | 0 | if ((num_hash_keys == 0 && num_range_keys == 0) && |
81 | 0 | (num_sub_keys > 0 || !has_hybrid_time)) { |
82 | | // This key format currently cannot ever appear because colocated table tombstones |
83 | | // should both have no subkeys and have a hybrid time, so skip it. |
84 | 0 | continue; |
85 | 0 | } |
86 | 0 | sub_doc_key.doc_key().set_pgtable_id(table_id_pair.second); |
87 | 0 | } |
88 | |
|
89 | 0 | if (num_hash_keys > 0) { |
90 | 0 | sub_doc_key.doc_key().set_hash(kAsciiFriendlyHash); |
91 | 0 | } |
92 | 0 | for (int hIndex = 0; hIndex < num_hash_keys; ++hIndex) { |
93 | 0 | sub_doc_key.doc_key().hashed_group().push_back( |
94 | 0 | PrimitiveValue(Format("h$0_$1", hIndex, std::string(hIndex + 1, 'h')))); |
95 | 0 | } |
96 | 0 | for (int rIndex = 0; rIndex < num_range_keys; ++rIndex) { |
97 | 0 | sub_doc_key.doc_key().range_group().push_back( |
98 | 0 | PrimitiveValue(Format("r$0_$1", rIndex, std::string(rIndex + 1, 'r')))); |
99 | 0 | } |
100 | 0 | for (int skIndex = 0; skIndex < num_sub_keys; ++skIndex) { |
101 | 0 | sub_doc_key.subkeys().push_back( |
102 | 0 | PrimitiveValue(Format("sk$0_$1", skIndex, std::string(skIndex + 1, 's')))); |
103 | 0 | } |
104 | 0 | if (has_hybrid_time) { |
105 | 0 | sub_doc_key.set_hybrid_time(DocHybridTime(HybridTime::FromMicros(123456), 1)); |
106 | 0 | } |
107 | 0 | sub_doc_keys.push_back(sub_doc_key); |
108 | 0 | } |
109 | 0 | } |
110 | 0 | } |
111 | 0 | } |
112 | 0 | } |
113 | 0 | return sub_doc_keys; |
114 | 0 | } |
115 | | |
116 | 0 | string GetTestDescriptionForSubDocKey(const SubDocKey& sub_doc_key) { |
117 | 0 | auto encoded_key = sub_doc_key.Encode(); |
118 | 0 | const char* kFormatStr = |
119 | 0 | "Encoded SubDocKey: $0\n" |
120 | 0 | "Encoded input binary data (partially human-readable): $1\n" |
121 | 0 | "Encoded input binary data (hex): $2\n" |
122 | 0 | "Encoded input binary data size: $3\n"; |
123 | 0 | return yb::Format( |
124 | 0 | kFormatStr, |
125 | 0 | sub_doc_key, |
126 | 0 | FormatSliceAsStr(encoded_key.AsSlice()), |
127 | 0 | encoded_key.AsSlice().ToDebugHexString(), |
128 | 0 | encoded_key.size() |
129 | 0 | ); |
130 | 0 | } |
131 | | |
132 | | }; |
133 | | |
134 | | namespace { |
135 | | |
136 | 0 | int Sign(int x) { |
137 | 0 | if (x < 0) return -1; |
138 | 0 | if (x > 0) return 1; |
139 | 0 | return 0; |
140 | 0 | } |
141 | | |
142 | | template<typename T> |
143 | | std::vector<T> GenRandomDocOrSubDocKeys(RandomNumberGenerator* rng, |
144 | | UseHash use_hash, |
145 | | int num_keys); |
146 | | |
147 | | template<> |
148 | | std::vector<DocKey> GenRandomDocOrSubDocKeys<DocKey>(RandomNumberGenerator* rng, |
149 | | UseHash use_hash, |
150 | 0 | int num_keys) { |
151 | 0 | return GenRandomDocKeys(rng, use_hash, num_keys); |
152 | 0 | } |
153 | | |
154 | | template<> |
155 | | std::vector<SubDocKey> GenRandomDocOrSubDocKeys<SubDocKey>(RandomNumberGenerator* rng, |
156 | | UseHash use_hash, |
157 | 0 | int num_keys) { |
158 | 0 | return GenRandomSubDocKeys(rng, use_hash, num_keys); |
159 | 0 | } |
160 | | |
161 | | template <typename DocOrSubDocKey> |
162 | 0 | void TestRoundTripDocOrSubDocKeyEncodingDecoding(const DocOrSubDocKey& doc_or_subdoc_key) { |
163 | 0 | KeyBytes encoded_key = doc_or_subdoc_key.Encode(); |
164 | 0 | DocOrSubDocKey decoded_key; |
165 | 0 | ASSERT_OK(decoded_key.FullyDecodeFrom(encoded_key.AsSlice())); |
166 | 0 | ASSERT_EQ(doc_or_subdoc_key, decoded_key); |
167 | 0 | KeyBytes reencoded_doc_key = decoded_key.Encode(); |
168 | 0 | ASSERT_EQ(encoded_key.ToString(), reencoded_doc_key.ToString()); |
169 | 0 | } Unexecuted instantiation: doc_key-test.cc:_ZN2yb5docdb12_GLOBAL__N_143TestRoundTripDocOrSubDocKeyEncodingDecodingINS0_6DocKeyEEEvRKT_ Unexecuted instantiation: doc_key-test.cc:_ZN2yb5docdb12_GLOBAL__N_143TestRoundTripDocOrSubDocKeyEncodingDecodingINS0_9SubDocKeyEEEvRKT_ |
170 | | |
171 | | template <typename DocOrSubDocKey> |
172 | 0 | void TestRoundTripDocOrSubDocKeyEncodingDecoding() { |
173 | 0 | RandomNumberGenerator rng; // Use the default seed to keep it deterministic. |
174 | 0 | for (auto use_hash : UseHash::kValues) { |
175 | 0 | auto doc_or_subdoc_keys = GenRandomDocOrSubDocKeys<DocOrSubDocKey>( |
176 | 0 | &rng, use_hash, kNumDocOrSubDocKeysPerBatch); |
177 | 0 | for (const auto& doc_or_subdoc_key : doc_or_subdoc_keys) { |
178 | 0 | TestRoundTripDocOrSubDocKeyEncodingDecoding(doc_or_subdoc_key); |
179 | 0 | } |
180 | 0 | } |
181 | 0 | } Unexecuted instantiation: doc_key-test.cc:_ZN2yb5docdb12_GLOBAL__N_143TestRoundTripDocOrSubDocKeyEncodingDecodingINS0_6DocKeyEEEvv Unexecuted instantiation: doc_key-test.cc:_ZN2yb5docdb12_GLOBAL__N_143TestRoundTripDocOrSubDocKeyEncodingDecodingINS0_9SubDocKeyEEEvv |
182 | | |
183 | | template <typename DocOrSubDocKey> |
184 | 0 | void TestDocOrSubDocKeyComparison() { |
185 | 0 | RandomNumberGenerator rng; // Use the default seed to keep it deterministic. |
186 | 0 | for (auto use_hash : UseHash::kValues) { |
187 | 0 | auto keys = GenRandomDocOrSubDocKeys<DocOrSubDocKey>( |
188 | 0 | &rng, use_hash, kNumDocOrSubDocKeysPerBatch); |
189 | 0 | for (int k = 0; k < kNumTestDocOrSubDocKeyComparisons; ++k) { |
190 | 0 | const auto& a = keys[rng() % keys.size()]; |
191 | 0 | const auto& b = keys[rng() % keys.size()]; |
192 | 0 | ASSERT_EQ(a == b, !(a != b)); |
193 | 0 | ASSERT_EQ(a == b, a.ToString() == b.ToString()); |
194 | |
|
195 | 0 | const int object_comparison = a.CompareTo(b); |
196 | 0 | const int reverse_object_comparison = b.CompareTo(a); |
197 | |
|
198 | 0 | const KeyBytes a_encoded = a.Encode(); |
199 | 0 | const KeyBytes b_encoded = b.Encode(); |
200 | 0 | const int encoded_comparison = a_encoded.CompareTo(b_encoded); |
201 | 0 | const int reverse_encoded_comparison = b_encoded.CompareTo(a_encoded); |
202 | |
|
203 | 0 | ASSERT_EQ(Sign(object_comparison), Sign(encoded_comparison)) |
204 | 0 | << "Object comparison inconsistent with encoded byte sequence comparison:\n" |
205 | 0 | << "a: " << a.ToString() << "\n" |
206 | 0 | << "b: " << b.ToString() << "\n" |
207 | 0 | << "a.Encode(): " << a.Encode().ToString() << "\n" |
208 | 0 | << "b.Encode(): " << b.Encode().ToString() << "\n" |
209 | 0 | << "a.CompareTo(b): " << object_comparison << "\n" |
210 | 0 | << "a.Encode().CompareTo(b.Encode()): " << encoded_comparison; |
211 | 0 | ASSERT_EQ(0, object_comparison + reverse_object_comparison); |
212 | 0 | ASSERT_EQ(0, encoded_comparison + reverse_encoded_comparison); |
213 | 0 | } |
214 | 0 | } |
215 | 0 | } Unexecuted instantiation: doc_key-test.cc:_ZN2yb5docdb12_GLOBAL__N_128TestDocOrSubDocKeyComparisonINS0_6DocKeyEEEvv Unexecuted instantiation: doc_key-test.cc:_ZN2yb5docdb12_GLOBAL__N_128TestDocOrSubDocKeyComparisonINS0_9SubDocKeyEEEvv |
216 | | |
217 | | } // unnamed namespace |
218 | | |
219 | 0 | TEST_F(DocKeyTest, TestDocKeyToString) { |
220 | 0 | ASSERT_EQ( |
221 | 0 | "DocKey([], [10, \"foo\", 20, \"bar\"])", |
222 | 0 | DocKey(PrimitiveValues(10, "foo", 20, "bar")).ToString()); |
223 | 0 | ASSERT_EQ( |
224 | 0 | "DocKey(0x1234, " |
225 | 0 | "[\"hashed_key1\", 123, \"hashed_key2\", 234], [10, \"foo\", 20, \"bar\"])", |
226 | 0 | DocKey(0x1234, |
227 | 0 | PrimitiveValues("hashed_key1", 123, "hashed_key2", 234), |
228 | 0 | PrimitiveValues(10, "foo", 20, "bar")).ToString()); |
229 | 0 | } |
230 | | |
231 | 0 | TEST_F(DocKeyTest, TestSubDocKeyToString) { |
232 | 0 | ASSERT_EQ( |
233 | 0 | "SubDocKey(DocKey([], [\"range_key1\", 1000, \"range_key_3\"]), [HT{ physical: 12345 }])", |
234 | 0 | SubDocKey(DocKey(PrimitiveValues("range_key1", 1000, "range_key_3")), |
235 | 0 | HybridTime::FromMicros(12345L)).ToString()); |
236 | 0 | ASSERT_EQ( |
237 | 0 | "SubDocKey(DocKey([], [\"range_key1\", 1000, \"range_key_3\"]), " |
238 | 0 | "[\"subkey1\"; HT{ physical: 20000 }])", |
239 | 0 | SubDocKey( |
240 | 0 | DocKey(PrimitiveValues("range_key1", 1000, "range_key_3")), |
241 | 0 | PrimitiveValue("subkey1"), HybridTime::FromMicros(20000L) |
242 | 0 | ).ToString()); |
243 | |
|
244 | 0 | } |
245 | | |
246 | 0 | TEST_F(DocKeyTest, TestDocKeyEncoding) { |
247 | | // A few points to make it easier to understand the expected binary representations here: |
248 | | // - Initial bytes such as 'S', 'I' correspond the ValueType enum. |
249 | | // - Strings are terminated with \x00\x00. |
250 | | // - Groups of key components in the document key ("hashed" and "range" components) are separated |
251 | | // with '!'. |
252 | | // - 64-bit signed integers are encoded using big-endian format with sign bit inverted. |
253 | 0 | ASSERT_STR_EQ_VERBOSE_TRIMMED( |
254 | 0 | ApplyEagerLineContinuation( |
255 | 0 | R"#( |
256 | 0 | "Sval1\x00\x00\ |
257 | 0 | I\x80\x00\x00\x00\x00\x00\x03\xe8\ |
258 | 0 | Sval2\x00\x00\ |
259 | 0 | I\x80\x00\x00\x00\x00\x00\x07\xd0\ |
260 | 0 | !" |
261 | 0 | )#"), |
262 | 0 | FormatSliceAsStr(DocKey(PrimitiveValues("val1", 1000, "val2", 2000)).Encode().AsSlice())); |
263 | |
|
264 | 0 | InetAddress addr(ASSERT_RESULT(ParseIpAddress("1.2.3.4"))); |
265 | | |
266 | | // To get a descending sorting, we store the negative of a decimal type. 100.2 gets converted to |
267 | | // -100.2 which in the encoded form is equal to \x1c\xea\xfe\xd7. |
268 | 0 | ASSERT_STR_EQ_VERBOSE_TRIMMED( |
269 | 0 | ApplyEagerLineContinuation( |
270 | 0 | R"#( |
271 | 0 | "a\x89\x9e\x93\xce\xff\xff\ |
272 | 0 | I\x80\x00\x00\x00\x00\x00\x03\xe8\ |
273 | 0 | b\x7f\xff\xff\xff\xff\xff\xfc\x17\ |
274 | 0 | a\x89\x9e\x93\xce\xff\xfe\xff\xff\ |
275 | 0 | .\xfe\xfd\xfc\xfb\xff\xff\ |
276 | 0 | c\x7f\xff\xff\xff\xff\xff\xfc\x17\ |
277 | 0 | d\x1c\xea\xfe\xd7\ |
278 | 0 | E\xdd\x14\ |
279 | 0 | !" |
280 | 0 | )#"), |
281 | 0 | FormatSliceAsStr(DocKey({ |
282 | 0 | PrimitiveValue("val1", SortOrder::kDescending), |
283 | 0 | PrimitiveValue(1000), |
284 | 0 | PrimitiveValue(1000, SortOrder::kDescending), |
285 | 0 | PrimitiveValue(BINARY_STRING("val1""\x00"), SortOrder::kDescending), |
286 | 0 | PrimitiveValue(addr, SortOrder::kDescending), |
287 | 0 | PrimitiveValue(Timestamp(1000), SortOrder::kDescending), |
288 | 0 | PrimitiveValue::Decimal(util::Decimal("100.02").EncodeToComparable(), |
289 | 0 | SortOrder::kDescending), |
290 | 0 | PrimitiveValue::Decimal(util::Decimal("0.001").EncodeToComparable(), |
291 | 0 | SortOrder::kAscending), |
292 | 0 | }).Encode().AsSlice())); |
293 | |
|
294 | 0 | ASSERT_STR_EQ_VERBOSE_TRIMMED( |
295 | 0 | ApplyEagerLineContinuation( |
296 | 0 | R"#("G\ |
297 | 0 | \xca\xfe\ |
298 | 0 | Shashed1\x00\x00\ |
299 | 0 | Shashed2\x00\x00\ |
300 | 0 | !\ |
301 | 0 | Srange1\x00\x00\ |
302 | 0 | I\x80\x00\x00\x00\x00\x00\x03\xe8\ |
303 | 0 | Srange2\x00\x00\ |
304 | 0 | I\x80\x00\x00\x00\x00\x00\x07\xd0\ |
305 | 0 | !")#"), |
306 | 0 | FormatSliceAsStr(DocKey( |
307 | 0 | 0xcafe, |
308 | 0 | PrimitiveValues("hashed1", "hashed2"), |
309 | 0 | PrimitiveValues("range1", 1000, "range2", 2000)).Encode().AsSlice())); |
310 | 0 | } |
311 | | |
312 | 0 | TEST_F(DocKeyTest, TestBasicSubDocKeyEncodingDecoding) { |
313 | 0 | const SubDocKey subdoc_key(DocKey({PrimitiveValue("some_doc_key")}), |
314 | 0 | PrimitiveValue("sk1"), |
315 | 0 | PrimitiveValue("sk2"), |
316 | 0 | PrimitiveValue(BINARY_STRING("sk3""\x00"), SortOrder::kDescending), |
317 | 0 | HybridTime::FromMicros(1000)); |
318 | 0 | const KeyBytes encoded_subdoc_key(subdoc_key.Encode()); |
319 | 0 | ASSERT_STR_EQ_VERBOSE_TRIMMED( |
320 | 0 | ApplyEagerLineContinuation( |
321 | 0 | R"#("Ssome_doc_key\x00\x00\ |
322 | 0 | !\ |
323 | 0 | Ssk1\x00\x00\ |
324 | 0 | Ssk2\x00\x00\ |
325 | 0 | a\x8c\x94\xcc\xff\xfe\xff\xff\ |
326 | 0 | #\x80\xff\x05T=\xf7)\xbc\x18\x80K" |
327 | 0 | )#" |
328 | 0 | ), |
329 | 0 | encoded_subdoc_key.ToString() |
330 | 0 | ); |
331 | 0 | SubDocKey decoded_subdoc_key; |
332 | 0 | ASSERT_OK(decoded_subdoc_key.FullyDecodeFrom(encoded_subdoc_key.AsSlice())); |
333 | 0 | ASSERT_EQ(subdoc_key, decoded_subdoc_key); |
334 | 0 | Slice source = encoded_subdoc_key.AsSlice(); |
335 | 0 | boost::container::small_vector<Slice, 20> slices; |
336 | 0 | ASSERT_OK(SubDocKey::PartiallyDecode(&source, &slices)); |
337 | 0 | const DocKey& dockey = subdoc_key.doc_key(); |
338 | 0 | const auto& range_group = dockey.range_group(); |
339 | 0 | size_t size = slices.size(); |
340 | 0 | ASSERT_EQ(range_group.size() + 1, size); |
341 | 0 | --size; // the last one is time |
342 | 0 | for (size_t i = 0; i != size; ++i) { |
343 | 0 | PrimitiveValue value; |
344 | 0 | Slice temp = slices[i]; |
345 | 0 | ASSERT_OK(value.DecodeFromKey(&temp)); |
346 | 0 | ASSERT_TRUE(temp.empty()); |
347 | 0 | ASSERT_EQ(range_group[i], value); |
348 | 0 | } |
349 | 0 | DocHybridTime time; |
350 | 0 | Slice temp = slices[size]; |
351 | 0 | ASSERT_OK(time.DecodeFrom(&temp)); |
352 | 0 | ASSERT_TRUE(temp.empty()); |
353 | 0 | ASSERT_EQ(subdoc_key.doc_hybrid_time(), time); |
354 | 0 | } |
355 | | |
356 | 0 | TEST_F(DocKeyTest, TestRandomizedDocKeyRoundTripEncodingDecoding) { |
357 | 0 | TestRoundTripDocOrSubDocKeyEncodingDecoding<DocKey>(); |
358 | 0 | } |
359 | | |
360 | 0 | TEST_F(DocKeyTest, TestRandomizedSubDocKeyRoundTripEncodingDecoding) { |
361 | 0 | TestRoundTripDocOrSubDocKeyEncodingDecoding<SubDocKey>(); |
362 | 0 | } |
363 | | |
364 | 0 | TEST_F(DocKeyTest, TestDocKeyComparison) { |
365 | 0 | TestDocOrSubDocKeyComparison<DocKey>(); |
366 | 0 | } |
367 | | |
368 | 0 | TEST_F(DocKeyTest, TestSubDocKeyComparison) { |
369 | 0 | TestDocOrSubDocKeyComparison<SubDocKey>(); |
370 | 0 | } |
371 | | |
372 | | TEST_F(DocKeyTest, TestSubDocKeyStartsWith) { |
373 | | RandomNumberGenerator rng; // Use the default seed to keep it deterministic. |
374 | | auto subdoc_keys = GenRandomSubDocKeys(&rng, UseHash::kFalse, 1000); |
375 | | for (const auto& subdoc_key : subdoc_keys) { |
376 | | if (subdoc_key.num_subkeys() > 0) { |
377 | | const SubDocKey doc_key_only = SubDocKey(subdoc_key.doc_key()); |
378 | | const SubDocKey doc_key_only_with_ht = |
379 | | SubDocKey(subdoc_key.doc_key(), subdoc_key.hybrid_time()); |
380 | | ASSERT_TRUE(subdoc_key.StartsWith(doc_key_only)); |
381 | | ASSERT_FALSE(doc_key_only.StartsWith(subdoc_key)); |
382 | | SubDocKey with_another_doc_gen_ht(subdoc_key); |
383 | | with_another_doc_gen_ht.set_hybrid_time( |
384 | | DocHybridTime(subdoc_key.hybrid_time().ToUint64() + 1, 0, kMinWriteId)); |
385 | | ASSERT_FALSE(with_another_doc_gen_ht.StartsWith(doc_key_only_with_ht)); |
386 | | ASSERT_FALSE(with_another_doc_gen_ht.StartsWith(subdoc_key)); |
387 | | ASSERT_FALSE(subdoc_key.StartsWith(with_another_doc_gen_ht)); |
388 | | } |
389 | | } |
390 | | } |
391 | | |
392 | | std::string EncodeSubDocKey(const std::string& hash_key, |
393 | 0 | const std::string& range_key, const std::string& sub_key, uint64_t time) { |
394 | 0 | DocKey dk(DocKey(0, PrimitiveValues(hash_key), PrimitiveValues(range_key))); |
395 | 0 | return SubDocKey( |
396 | 0 | dk, PrimitiveValue(sub_key), HybridTime::FromMicros(time)).Encode().ToStringBuffer(); |
397 | 0 | } |
398 | | |
399 | 0 | std::string EncodeSimpleSubDocKey(const std::string& hash_key) { |
400 | 0 | return EncodeSubDocKey(hash_key, "range_key", "sub_key", 12345L); |
401 | 0 | } |
402 | | |
403 | 0 | std::string EncodeSimpleSubDocKeyWithDifferentNonHashPart(const std::string& hash_key) { |
404 | 0 | return EncodeSubDocKey(hash_key, "another_range_key", "another_sub_key", 55555L); |
405 | 0 | } |
406 | | |
407 | 0 | TEST_F(DocKeyTest, TestKeyMatching) { |
408 | 0 | DocDbAwareV2FilterPolicy policy(rocksdb::FilterPolicy::kDefaultFixedSizeFilterBits, nullptr); |
409 | 0 | std::string keys[] = { "foo", "bar", "test" }; |
410 | 0 | std::string absent_key = "fake"; |
411 | |
|
412 | 0 | std::unique_ptr<FilterBitsBuilder> builder(policy.GetFilterBitsBuilder()); |
413 | 0 | ASSERT_NE(builder, nullptr); |
414 | | // Policy supports GetFilterBitsBuilder/Reader interface (see description in filter_policy.h) - |
415 | | // lets test it. |
416 | 0 | for (const auto& key : keys) { |
417 | 0 | builder->AddKey(policy.GetKeyTransformer()->Transform(EncodeSimpleSubDocKey(key))); |
418 | 0 | } |
419 | 0 | std::unique_ptr<const char[]> buf; |
420 | 0 | rocksdb::Slice filter = builder->Finish(&buf); |
421 | |
|
422 | 0 | std::unique_ptr<FilterBitsReader> reader(policy.GetFilterBitsReader(filter)); |
423 | |
|
424 | 0 | auto may_match = [&](const std::string& sub_doc_key_str) { |
425 | 0 | return reader->MayMatch(policy.GetKeyTransformer()->Transform(sub_doc_key_str)); |
426 | 0 | }; |
427 | |
|
428 | 0 | for (const auto &key : keys) { |
429 | 0 | ASSERT_TRUE(may_match(EncodeSimpleSubDocKey(key))) << "Key: " << key; |
430 | 0 | ASSERT_TRUE(may_match(EncodeSimpleSubDocKeyWithDifferentNonHashPart(key))) << "Key: " << key; |
431 | 0 | } |
432 | 0 | ASSERT_FALSE(may_match(EncodeSimpleSubDocKey(absent_key))) << "Key: " << absent_key; |
433 | 0 | } |
434 | | |
435 | 0 | TEST_F(DocKeyTest, TestWriteId) { |
436 | 0 | SubDocKey subdoc_key(DocKey({PrimitiveValue("a"), PrimitiveValue(135)}), |
437 | 0 | DocHybridTime(1000000, 4091, 135)); |
438 | 0 | TestRoundTripDocOrSubDocKeyEncodingDecoding(subdoc_key); |
439 | 0 | } |
440 | | |
441 | | struct CollectedIntent { |
442 | | IntentStrength strength; |
443 | | FullDocKey full_doc_key; |
444 | | KeyBytes intent_key; |
445 | | Slice value; |
446 | | |
447 | 0 | std::string ToString() const { |
448 | 0 | return Format("{ strength: $0 full_doc_key: $1 intent_key: $2 value: $3 }", |
449 | 0 | strength, full_doc_key, SubDocKey::DebugSliceToString(intent_key.AsSlice()), |
450 | 0 | value.ToDebugHexString()); |
451 | 0 | } |
452 | | }; |
453 | | |
454 | | class IntentCollector { |
455 | | public: |
456 | 0 | explicit IntentCollector(std::vector<CollectedIntent>* out) : out_(out) {} |
457 | | |
458 | | Status operator()( |
459 | 0 | IntentStrength strength, FullDocKey full_doc_key, Slice value, KeyBytes* key, LastKey) { |
460 | 0 | out_->push_back(CollectedIntent{ |
461 | 0 | .strength = strength, |
462 | 0 | .full_doc_key = full_doc_key, |
463 | 0 | .intent_key = *key, |
464 | 0 | .value = value |
465 | 0 | }); |
466 | 0 | return Status::OK(); |
467 | 0 | } |
468 | | |
469 | | private: |
470 | | std::vector<CollectedIntent>* out_; |
471 | | }; |
472 | | |
473 | 0 | TEST_F(DocKeyTest, TestDecodePrefixLengths) { |
474 | 0 | for (const auto& sub_doc_key : GetVariedSubDocKeys()) { |
475 | 0 | const auto encoded_input = sub_doc_key.Encode(); |
476 | 0 | const DocKey& doc_key = sub_doc_key.doc_key(); |
477 | 0 | const Slice subdockey_slice = encoded_input.AsSlice(); |
478 | |
|
479 | 0 | const string test_description = GetTestDescriptionForSubDocKey(sub_doc_key); |
480 | 0 | SCOPED_TRACE(test_description); |
481 | |
|
482 | 0 | SubDocKey cur_key; |
483 | 0 | boost::container::small_vector<size_t, 8> prefix_lengths; |
484 | 0 | std::vector<size_t> expected_prefix_lengths; |
485 | 0 | if (doc_key.has_hash() || doc_key.has_cotable_id() || doc_key.has_pgtable_id()) { |
486 | 0 | if (doc_key.has_hash()) { |
487 | 0 | cur_key.doc_key() = DocKey(doc_key.hash(), doc_key.hashed_group()); |
488 | 0 | } |
489 | 0 | if (doc_key.has_cotable_id()) { |
490 | 0 | cur_key.doc_key().set_cotable_id(doc_key.cotable_id()); |
491 | 0 | } else if (doc_key.has_pgtable_id()) { |
492 | 0 | cur_key.doc_key().set_pgtable_id(doc_key.pgtable_id()); |
493 | 0 | } |
494 | | |
495 | | // Subtract one to avoid counting the final kGroupEnd, unless this is the entire key. |
496 | 0 | if (doc_key.range_group().empty()) { |
497 | 0 | expected_prefix_lengths.push_back(cur_key.Encode().size()); |
498 | 0 | } else { |
499 | 0 | expected_prefix_lengths.push_back(cur_key.Encode().size() - 1); |
500 | 0 | } |
501 | 0 | } |
502 | 0 | const auto exp_hash_prefix(cur_key); |
503 | 0 | const auto exp_enc_hash_prefix = exp_hash_prefix.Encode(); |
504 | 0 | SCOPED_TRACE( |
505 | 0 | Format( |
506 | 0 | "Key used to determine expected hash prefix length: $0\n" |
507 | 0 | "encoded (partially human readable): $1\n" |
508 | 0 | "encoded size: $2\n", |
509 | 0 | exp_hash_prefix, |
510 | 0 | FormatSliceAsStr(exp_enc_hash_prefix.AsSlice()), |
511 | 0 | exp_enc_hash_prefix.size())); |
512 | |
|
513 | 0 | const size_t num_range_keys = doc_key.range_group().size(); |
514 | 0 | for (size_t i = 0; i < num_range_keys; ++i) { |
515 | 0 | cur_key.doc_key().range_group().push_back(doc_key.range_group()[i]); |
516 | 0 | if (i < num_range_keys - 1) { |
517 | 0 | expected_prefix_lengths.push_back(cur_key.Encode().size() - 1); |
518 | 0 | } else { |
519 | | // Only cound the final kGroupEnd for the last range key. |
520 | 0 | expected_prefix_lengths.push_back(cur_key.Encode().size()); |
521 | 0 | } |
522 | 0 | } |
523 | |
|
524 | 0 | for (const auto& subkey : sub_doc_key.subkeys()) { |
525 | 0 | cur_key.subkeys().push_back(subkey); |
526 | 0 | expected_prefix_lengths.push_back(cur_key.Encode().size()); |
527 | 0 | } |
528 | |
|
529 | 0 | ASSERT_OK(SubDocKey::DecodePrefixLengths(subdockey_slice, &prefix_lengths)); |
530 | 0 | ASSERT_EQ(yb::ToString(expected_prefix_lengths), yb::ToString(prefix_lengths)); |
531 | 0 | } |
532 | 0 | } |
533 | | |
534 | 0 | TEST_F(DocKeyTest, DecodeDocKeyAndSubKeyEnds) { |
535 | 0 | for (const SubDocKey& sub_doc_key : GetVariedSubDocKeys()) { |
536 | 0 | const DocKey& doc_key = sub_doc_key.doc_key(); |
537 | 0 | const string test_description = GetTestDescriptionForSubDocKey(sub_doc_key); |
538 | 0 | boost::container::small_vector<size_t, 8> actual_ends; |
539 | 0 | boost::container::small_vector<size_t, 8> input_ends; |
540 | 0 | std::vector<size_t> expected_ends; |
541 | 0 | SubDocKey cur_key; |
542 | |
|
543 | 0 | SCOPED_TRACE(test_description); |
544 | | |
545 | | // Find ID end. |
546 | 0 | if (doc_key.has_cotable_id() || doc_key.has_pgtable_id()) { |
547 | 0 | if (doc_key.has_cotable_id()) { |
548 | 0 | cur_key.doc_key().set_cotable_id(doc_key.cotable_id()); |
549 | 0 | } else if (doc_key.has_pgtable_id()) { |
550 | 0 | cur_key.doc_key().set_pgtable_id(doc_key.pgtable_id()); |
551 | 0 | } |
552 | | // Subtract one because kGroupEnd doesn't count. |
553 | 0 | expected_ends.push_back(cur_key.Encode().size() - 1); |
554 | 0 | } else { |
555 | 0 | expected_ends.push_back(0); |
556 | 0 | } |
557 | | |
558 | | // Find whole DocKey end. |
559 | 0 | if (doc_key.has_hash()) { |
560 | 0 | cur_key.doc_key().set_hash(doc_key.hash()); |
561 | 0 | for (const PrimitiveValue& hashed_group_elem : doc_key.hashed_group()) { |
562 | 0 | cur_key.doc_key().hashed_group().push_back(hashed_group_elem); |
563 | 0 | } |
564 | 0 | } |
565 | 0 | for (const PrimitiveValue& range_group_elem : doc_key.range_group()) { |
566 | 0 | cur_key.doc_key().range_group().push_back(range_group_elem); |
567 | 0 | } |
568 | 0 | if (doc_key.has_pgtable_id() && |
569 | 0 | doc_key.hashed_group().empty() && |
570 | 0 | doc_key.range_group().empty()) { |
571 | | // ...but an empty key doesn't count (for colocated table tombstones). |
572 | 0 | } else { |
573 | 0 | expected_ends.push_back(cur_key.Encode().size()); |
574 | 0 | } |
575 | | |
576 | | // Find subkey ends. |
577 | 0 | for (const auto& subkey : sub_doc_key.subkeys()) { |
578 | 0 | cur_key.subkeys().push_back(subkey); |
579 | 0 | expected_ends.push_back(cur_key.Encode().size()); |
580 | 0 | } |
581 | | |
582 | | // Verify DecodeDocKeyAndSubKeyEnds, supplying all possible valid arguments for its out |
583 | | // parameter. |
584 | | // Verify with empty out. |
585 | 0 | ASSERT_OK(SubDocKey::DecodeDocKeyAndSubKeyEnds(sub_doc_key.Encode().AsSlice(), &actual_ends)); |
586 | 0 | ASSERT_EQ(yb::ToString(expected_ends), yb::ToString(actual_ends)); |
587 | 0 | for (const size_t input_end : expected_ends) { |
588 | 0 | input_ends.push_back(input_end); |
589 | 0 | actual_ends = input_ends; |
590 | | // Verify with partially filled out. |
591 | 0 | ASSERT_OK(SubDocKey::DecodeDocKeyAndSubKeyEnds(sub_doc_key.Encode().AsSlice(), &actual_ends)); |
592 | 0 | ASSERT_EQ(yb::ToString(expected_ends), yb::ToString(actual_ends)); |
593 | 0 | } |
594 | 0 | } |
595 | 0 | } |
596 | | |
597 | 0 | TEST_F(DocKeyTest, TestEnumerateIntents) { |
598 | 0 | for (const auto& sub_doc_key : GetVariedSubDocKeys()) { |
599 | 0 | for (auto partial_range_key_intents : PartialRangeKeyIntents::kValues) { |
600 | 0 | const auto encoded_input = sub_doc_key.Encode(); |
601 | 0 | const string test_description = Format( |
602 | 0 | "$0. Partial range key intents: $1", |
603 | 0 | GetTestDescriptionForSubDocKey(sub_doc_key), |
604 | 0 | partial_range_key_intents ? "yes" : "no"); |
605 | 0 | SCOPED_TRACE(test_description); |
606 | 0 | const Slice subdockey_slice = encoded_input.AsSlice(); |
607 | |
|
608 | 0 | vector<CollectedIntent> collected_intents; |
609 | 0 | vector<CollectedIntent> collected_intents_old; |
610 | 0 | KeyBytes encoded_key_buffer; |
611 | |
|
612 | 0 | VLOG(1) << "EnumerateIntents for: " << test_description; |
613 | 0 | ASSERT_OK(EnumerateIntents( |
614 | 0 | subdockey_slice, |
615 | 0 | /* value */ Slice("some_value"), |
616 | 0 | IntentCollector(&collected_intents), |
617 | 0 | &encoded_key_buffer, |
618 | 0 | partial_range_key_intents)); |
619 | |
|
620 | 0 | if (VLOG_IS_ON(1)) { |
621 | 0 | for (const auto& intent : collected_intents) { |
622 | 0 | auto intent_slice = intent.intent_key.AsSlice(); |
623 | |
|
624 | 0 | VLOG(1) << "Found intent: " << SubDocKey::DebugSliceToString(intent_slice) |
625 | 0 | << ", raw bytes: " << FormatSliceAsStr(intent_slice) |
626 | 0 | << ", strength: " << intent.strength; |
627 | 0 | } |
628 | 0 | } |
629 | |
|
630 | 0 | std::vector<SubDocKey> expected_intents; |
631 | 0 | SubDocKey current_expected_intent; |
632 | |
|
633 | 0 | if (sub_doc_key.doc_key().has_cotable_id() || sub_doc_key.doc_key().has_pgtable_id()) { |
634 | 0 | DocKey table_id_only_doc_key; |
635 | 0 | if (sub_doc_key.doc_key().has_cotable_id()) { |
636 | 0 | table_id_only_doc_key.set_cotable_id(sub_doc_key.doc_key().cotable_id()); |
637 | 0 | } else { |
638 | 0 | table_id_only_doc_key.set_pgtable_id(sub_doc_key.doc_key().pgtable_id()); |
639 | 0 | } |
640 | 0 | current_expected_intent = SubDocKey(table_id_only_doc_key); |
641 | 0 | expected_intents.push_back(current_expected_intent); |
642 | 0 | } else { |
643 | 0 | expected_intents.push_back(SubDocKey()); |
644 | 0 | } |
645 | |
|
646 | 0 | if (!sub_doc_key.doc_key().hashed_group().empty()) { |
647 | 0 | if (sub_doc_key.doc_key().has_cotable_id()) { |
648 | 0 | current_expected_intent = SubDocKey(DocKey( |
649 | 0 | sub_doc_key.doc_key().cotable_id(), |
650 | 0 | sub_doc_key.doc_key().hash(), |
651 | 0 | sub_doc_key.doc_key().hashed_group())); |
652 | 0 | } else { |
653 | 0 | current_expected_intent = SubDocKey(DocKey( |
654 | 0 | sub_doc_key.doc_key().pgtable_id(), |
655 | 0 | sub_doc_key.doc_key().hash(), |
656 | 0 | sub_doc_key.doc_key().hashed_group())); |
657 | 0 | } |
658 | 0 | expected_intents.push_back(current_expected_intent); |
659 | 0 | } |
660 | |
|
661 | 0 | const auto& range_group = sub_doc_key.doc_key().range_group(); |
662 | 0 | for (size_t range_idx = 0; range_idx < range_group.size(); ++range_idx) { |
663 | 0 | current_expected_intent.doc_key().range_group().push_back(range_group[range_idx]); |
664 | 0 | if (partial_range_key_intents || range_idx == range_group.size() - 1) { |
665 | 0 | expected_intents.push_back(current_expected_intent); |
666 | 0 | } |
667 | 0 | } |
668 | |
|
669 | 0 | if (!sub_doc_key.doc_key().empty()) { |
670 | 0 | for (const auto& subkey : sub_doc_key.subkeys()) { |
671 | 0 | current_expected_intent.AppendSubKey(subkey); |
672 | 0 | expected_intents.push_back(current_expected_intent); |
673 | 0 | } |
674 | 0 | } |
675 | |
|
676 | 0 | { |
677 | 0 | std::set<SubDocKey> expected_intents_set(expected_intents.begin(), expected_intents.end()); |
678 | 0 | SCOPED_TRACE(Format("Expected intents: $0", yb::ToString(expected_intents_set))); |
679 | | |
680 | | // There should be no duplicate intents in our set of expected intents. |
681 | 0 | ASSERT_EQ(expected_intents_set.size(), expected_intents.size()); |
682 | |
|
683 | 0 | const SubDocKey doc_key_only(sub_doc_key.doc_key()); |
684 | 0 | ASSERT_TRUE(expected_intents_set.count(doc_key_only)) |
685 | 0 | << "doc_key_only: " << doc_key_only; |
686 | | |
687 | 0 | SubDocKey hash_part_only(sub_doc_key); |
688 | 0 | hash_part_only.subkeys().clear(); |
689 | 0 | hash_part_only.doc_key().range_group().clear(); |
690 | 0 | hash_part_only.remove_hybrid_time(); |
691 | 0 | ASSERT_TRUE(expected_intents_set.count(hash_part_only)) |
692 | 0 | << "hash_part_only: " << hash_part_only; |
693 | 0 | } |
694 | | |
695 | 0 | const size_t num_intents = std::min(expected_intents.size(), collected_intents.size()); |
696 | 0 | for (size_t i = 0; i < num_intents; ++i) { |
697 | 0 | SubDocKey decoded_intent_key; |
698 | 0 | const auto& intent = collected_intents[i]; |
699 | 0 | SCOPED_TRACE(Format("i=$0: generated intent bytes, partially human-readable: $1", |
700 | 0 | i, FormatSliceAsStr(intent.intent_key.AsSlice()))); |
701 | 0 | Slice intent_key_slice = intent.intent_key.AsSlice(); |
702 | 0 | ASSERT_OK(decoded_intent_key.DecodeFrom(&intent_key_slice, HybridTimeRequired::kFalse)); |
703 | 0 | ASSERT_EQ(0, intent_key_slice.size()); |
704 | 0 | ASSERT_FALSE(decoded_intent_key.has_hybrid_time()); |
705 | 0 | ASSERT_EQ(expected_intents[i].ToString(), decoded_intent_key.ToString()); |
706 | |
|
707 | 0 | SubDocKey a = expected_intents[i]; |
708 | 0 | SubDocKey b = decoded_intent_key; |
709 | 0 | VLOG(1) << "Doc key matches: " << (a.doc_key() == b.doc_key()); |
710 | 0 | VLOG(1) << "has_hybrid_time matches: " << (a.has_hybrid_time() == b.has_hybrid_time()); |
711 | 0 | if (a.has_hybrid_time() && b.has_hybrid_time()) { |
712 | 0 | VLOG(1) << "HT matches: " << (a.hybrid_time() == b.hybrid_time()); |
713 | 0 | } |
714 | 0 | VLOG(1) << "Subkeys match: " << (a.subkeys() == b.subkeys()); |
715 | |
|
716 | 0 | ASSERT_EQ(intent.full_doc_key, a.doc_key() == sub_doc_key.doc_key()); |
717 | 0 | ASSERT_EQ(expected_intents[i], decoded_intent_key); |
718 | 0 | if (i < num_intents - 1) { |
719 | 0 | ASSERT_EQ(IntentStrength::kWeak, intent.strength); |
720 | 0 | ASSERT_EQ(0, intent.value.size()); |
721 | 0 | } else { |
722 | 0 | ASSERT_EQ(IntentStrength::kStrong, intent.strength); |
723 | 0 | ASSERT_GT(intent.value.size(), 0); |
724 | 0 | } |
725 | 0 | } |
726 | |
|
727 | 0 | ASSERT_EQ(expected_intents.size(), collected_intents.size()) |
728 | 0 | << "Expected: " << yb::ToString(expected_intents) << "\n" |
729 | 0 | << "Collected: " << yb::ToString(collected_intents); |
730 | 0 | } |
731 | 0 | } |
732 | 0 | } |
733 | | |
734 | | } // namespace docdb |
735 | | } // namespace yb |