YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/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