YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/docdb/doc_reader_redis.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_reader_redis.h"
15
16
#include <string>
17
#include <vector>
18
19
#include "yb/common/hybrid_time.h"
20
#include "yb/common/transaction.h"
21
22
#include "yb/docdb/deadline_info.h"
23
#include "yb/docdb/doc_key.h"
24
#include "yb/docdb/doc_ttl_util.h"
25
#include "yb/docdb/docdb-internal.h"
26
#include "yb/docdb/docdb_rocksdb_util.h"
27
#include "yb/docdb/docdb_types.h"
28
#include "yb/docdb/intent_aware_iterator.h"
29
#include "yb/docdb/subdocument.h"
30
#include "yb/docdb/value.h"
31
#include "yb/docdb/value_type.h"
32
33
#include "yb/util/result.h"
34
#include "yb/util/status.h"
35
#include "yb/util/status_format.h"
36
37
using std::vector;
38
39
using yb::HybridTime;
40
41
namespace yb {
42
namespace docdb {
43
44
2.32M
const SliceKeyBound& SliceKeyBound::Invalid() {
45
2.32M
  static SliceKeyBound result;
46
2.32M
  return result;
47
2.32M
}
48
49
0
std::string SliceKeyBound::ToString() const {
50
0
  if (!is_valid()) {
51
0
    return "{ empty }";
52
0
  }
53
0
  return Format("{ $0$1 $2 }", is_lower() ? ">" : "<", is_exclusive() ? "" : "=",
54
0
                SubDocKey::DebugSliceToString(key_));
55
0
}
56
57
2.32M
const IndexBound& IndexBound::Empty() {
58
2.32M
  static IndexBound result;
59
2.32M
  return result;
60
2.32M
}
61
62
63
// ------------------------------------------------------------------------------------------------
64
// Standalone functions
65
// ------------------------------------------------------------------------------------------------
66
67
namespace {
68
69
5.42k
void SeekToLowerBound(const SliceKeyBound& lower_bound, IntentAwareIterator* iter) {
70
5.42k
  if (lower_bound.is_exclusive()) {
71
890
    iter->SeekPastSubKey(lower_bound.key());
72
4.53k
  } else {
73
4.53k
    iter->SeekForward(lower_bound.key());
74
4.53k
  }
75
5.42k
}
76
77
// This function does not assume that object init_markers are present. If no init marker is present,
78
// or if a tombstone is found at some level, it still looks for subkeys inside it if they have
79
// larger timestamps.
80
//
81
// TODO(akashnil): ENG-1152: If object init markers were required, this read path may be optimized.
82
// We look at all rocksdb keys with prefix = subdocument_key, and construct a subdocument out of
83
// them, between the timestamp range high_ts and low_ts.
84
//
85
// The iterator is expected to be placed at the smallest key that is subdocument_key or later, and
86
// after the function returns, the iterator should be placed just completely outside the
87
// subdocument_key prefix. Although if high_subkey is specified, the iterator is only guaranteed
88
// to be positioned after the high_subkey and not necessarily outside the subdocument_key prefix.
89
// num_values_observed is used for queries on indices, and keeps track of the number of primitive
90
// values observed thus far. In a query with lower index bound k, ignore the first k primitive
91
// values before building the subdocument.
92
CHECKED_STATUS BuildSubDocument(
93
    IntentAwareIterator* iter,
94
    const GetRedisSubDocumentData& data,
95
    DocHybridTime low_ts,
96
1.00M
    int64* num_values_observed) {
97
0
  VLOG(3) << "BuildSubDocument data: " << data << " read_time: " << iter->read_time()
98
0
          << " low_ts: " << low_ts;
99
1.95M
  while (iter->valid()) {
100
1.93M
    if (data.deadline_info && data.deadline_info->CheckAndSetDeadlinePassed()) {
101
0
      return STATUS(Expired, "Deadline for query passed.");
102
0
    }
103
    // Since we modify num_values_observed on recursive calls, we keep a local copy of the value.
104
1.93M
    int64 current_values_observed = *num_values_observed;
105
1.93M
    auto key_data = VERIFY_RESULT(iter->FetchKey());
106
1.93M
    auto key = key_data.key;
107
1.93M
    const auto write_time = key_data.write_time;
108
0
    VLOG(4) << "iter: " << SubDocKey::DebugSliceToString(key)
109
0
            << ", key: " << SubDocKey::DebugSliceToString(data.subdocument_key);
110
0
    DCHECK(key.starts_with(data.subdocument_key))
111
0
        << "iter: " << SubDocKey::DebugSliceToString(key)
112
0
        << ", key: " << SubDocKey::DebugSliceToString(data.subdocument_key);
113
114
    // Key could be invalidated because we could move iterator, so back it up.
115
1.93M
    KeyBytes key_copy(key);
116
1.93M
    key = key_copy.AsSlice();
117
1.93M
    rocksdb::Slice value = iter->value();
118
    // Checking that IntentAwareIterator returns an entry with correct time.
119
0
    DCHECK(key_data.same_transaction ||
120
0
           iter->read_time().global_limit >= write_time.hybrid_time())
121
0
        << "Bad key: " << SubDocKey::DebugSliceToString(key)
122
0
        << ", global limit: " << iter->read_time().global_limit
123
0
        << ", write time: " << write_time.hybrid_time();
124
125
1.93M
    if (low_ts > write_time) {
126
0
      VLOG(3) << "SeekPastSubKey: " << SubDocKey::DebugSliceToString(key);
127
0
      iter->SeekPastSubKey(key);
128
0
      continue;
129
0
    }
130
1.93M
    Value doc_value;
131
1.93M
    RETURN_NOT_OK(doc_value.Decode(value));
132
1.93M
    ValueType value_type = doc_value.value_type();
133
1.93M
    if (key == data.subdocument_key) {
134
986k
      if (write_time == DocHybridTime::kMin)
135
0
        return STATUS(Corruption, "No hybrid timestamp found on entry");
136
137
      // We may need to update the TTL in individual columns.
138
986k
      if (write_time.hybrid_time() >= data.exp.write_ht) {
139
        // We want to keep the default TTL otherwise.
140
986k
        if (doc_value.ttl() != Value::kMaxTtl) {
141
60
          data.exp.write_ht = write_time.hybrid_time();
142
60
          data.exp.ttl = doc_value.ttl();
143
986k
        } else if (data.exp.ttl.IsNegative()) {
144
321
          data.exp.ttl = -data.exp.ttl;
145
321
        }
146
986k
      }
147
148
      // If the hybrid time is kMin, then we must be using default TTL.
149
986k
      if (data.exp.write_ht == HybridTime::kMin) {
150
39.8k
        data.exp.write_ht = write_time.hybrid_time();
151
39.8k
      }
152
153
      // Treat an expired value as a tombstone written at the same time as the original value.
154
986k
      if (HasExpiredTTL(data.exp.write_ht, data.exp.ttl, iter->read_time().read)) {
155
42
        doc_value = Value::Tombstone();
156
42
        value_type = ValueType::kTombstone;
157
42
      }
158
159
986k
      const bool is_collection = IsCollectionType(value_type);
160
      // We have found some key that matches our entire subdocument_key, i.e. we didn't skip ahead
161
      // to a lower level key (with optional object init markers).
162
986k
      if (is_collection || value_type == ValueType::kTombstone) {
163
5.99k
        if (low_ts < write_time) {
164
150
          low_ts = write_time;
165
150
        }
166
5.99k
        if (is_collection) {
167
5.62k
          *data.result = SubDocument(value_type);
168
5.62k
        }
169
170
        // If the subkey lower bound filters out the key we found, we want to skip to the lower
171
        // bound. If it does not, we want to seek to the next key. This prevents an infinite loop
172
        // where the iterator keeps seeking to itself if the key we found matches the low subkey.
173
        // TODO: why are not we doing this for arrays?
174
5.99k
        if (IsObjectType(value_type) && !data.low_subkey->CanInclude(key)) {
175
          // Try to seek to the low_subkey for efficiency.
176
5.42k
          SeekToLowerBound(*data.low_subkey, iter);
177
570
        } else {
178
0
          VLOG(3) << "SeekPastSubKey: " << SubDocKey::DebugSliceToString(key);
179
570
          iter->SeekPastSubKey(key);
180
570
        }
181
5.99k
        continue;
182
980k
      } else if (IsPrimitiveValueType(value_type)) {
183
        // Choose the user supplied timestamp if present.
184
980k
        const UserTimeMicros user_timestamp = doc_value.user_timestamp();
185
980k
        doc_value.mutable_primitive_value()->SetWriteTime(
186
980k
            user_timestamp == Value::kInvalidUserTimestamp
187
980k
            ? write_time.hybrid_time().GetPhysicalValueMicros()
188
0
            : doc_value.user_timestamp());
189
980k
        if (!data.high_index->CanInclude(current_values_observed)) {
190
6
          iter->SeekOutOfSubDoc(&key_copy);
191
6
          return Status::OK();
192
6
        }
193
980k
        if (data.low_index->CanInclude(*num_values_observed)) {
194
980k
          *data.result = SubDocument(doc_value.primitive_value());
195
980k
        }
196
980k
        (*num_values_observed)++;
197
0
        VLOG(3) << "SeekOutOfSubDoc: " << SubDocKey::DebugSliceToString(key);
198
980k
        iter->SeekOutOfSubDoc(&key_copy);
199
980k
        return Status::OK();
200
0
      } else {
201
0
        return STATUS_FORMAT(Corruption, "Expected primitive value type, got $0", value_type);
202
0
      }
203
945k
    }
204
945k
    SubDocument descendant{PrimitiveValue(ValueType::kInvalid)};
205
    // TODO: what if the key we found is the same as before?
206
    //       We'll get into an infinite recursion then.
207
945k
    {
208
945k
      IntentAwareIteratorPrefixScope prefix_scope(key, iter);
209
945k
      RETURN_NOT_OK(BuildSubDocument(
210
945k
          iter, data.Adjusted(key, &descendant), low_ts,
211
945k
          num_values_observed));
212
213
945k
    }
214
945k
    if (descendant.value_type() == ValueType::kInvalid) {
215
      // The document was not found in this level (maybe a tombstone was encountered).
216
42
      continue;
217
42
    }
218
219
945k
    if (!data.low_subkey->CanInclude(key)) {
220
0
      VLOG(3) << "Filtered by low_subkey: " << data.low_subkey->ToString()
221
0
              << ", key: " << SubDocKey::DebugSliceToString(key);
222
      // The value provided is lower than what we are looking for, seek to the lower bound.
223
3
      SeekToLowerBound(*data.low_subkey, iter);
224
3
      continue;
225
3
    }
226
227
    // We use num_values_observed as a conservative figure for lower bound and
228
    // current_values_observed for upper bound so we don't lose any data we should be including.
229
945k
    if (!data.low_index->CanInclude(*num_values_observed)) {
230
21
      continue;
231
21
    }
232
233
945k
    if (!data.high_subkey->CanInclude(key)) {
234
0
      VLOG(3) << "Filtered by high_subkey: " << data.high_subkey->ToString()
235
0
              << ", key: " << SubDocKey::DebugSliceToString(key);
236
      // We have encountered a subkey higher than our constraints, we should stop here.
237
2.44k
      return Status::OK();
238
2.44k
    }
239
240
943k
    if (!data.high_index->CanInclude(current_values_observed)) {
241
6
      return Status::OK();
242
6
    }
243
244
943k
    if (!IsObjectType(data.result->value_type())) {
245
0
      *data.result = SubDocument();
246
0
    }
247
248
943k
    SubDocument* current = data.result;
249
943k
    size_t num_children;
250
943k
    RETURN_NOT_OK(current->NumChildren(&num_children));
251
943k
    if (data.limit != 0 && num_children >= data.limit) {
252
      // We have processed enough records.
253
729
      return Status::OK();
254
729
    }
255
256
942k
    if (data.count_only) {
257
      // We need to only count the records that we found.
258
63
      data.record_count++;
259
942k
    } else {
260
942k
      Slice temp = key;
261
942k
      temp.remove_prefix(data.subdocument_key.size());
262
942k
      for (;;) {
263
942k
        PrimitiveValue child;
264
942k
        RETURN_NOT_OK(child.DecodeFromKey(&temp));
265
942k
        if (temp.empty()) {
266
942k
          current->SetChild(child, std::move(descendant));
267
942k
          break;
268
942k
        }
269
0
        current = current->GetOrAddChild(child).first;
270
0
      }
271
942k
    }
272
942k
  }
273
274
25.8k
  return Status::OK();
275
1.00M
}
276
277
// If there is a key equal to key_bytes_without_ht + some timestamp, which is later than
278
// max_overwrite_time, we update max_overwrite_time, and result_value (unless it is nullptr).
279
// If there is a TTL with write time later than the write time in expiration, it is updated with
280
// the new write time and TTL, unless its value is kMaxTTL.
281
// When the TTL found is kMaxTTL and it is not a merge record, then it is assumed not to be
282
// explicitly set. Because it does not override the default table ttl, exp, which was initialized
283
// to the table ttl, is not updated.
284
// Observe that exp updates based on the first record found, while max_overwrite_time updates
285
// based on the first non-merge record found.
286
// This should not be used for leaf nodes. - Why? Looks like it is already used for leaf nodes
287
// also.
288
// Note: it is responsibility of caller to make sure key_bytes_without_ht doesn't have hybrid
289
// time.
290
// TODO: We could also check that the value is kTombStone or kObject type for sanity checking - ?
291
// It could be a simple value as well, not necessarily kTombstone or kObject.
292
Status FindLastWriteTime(
293
    IntentAwareIterator* iter,
294
    const Slice& key_without_ht,
295
    DocHybridTime* max_overwrite_time,
296
    Expiration* exp,
297
247k
    Value* result_value = nullptr) {
298
247k
  Slice value;
299
247k
  DocHybridTime doc_ht = *max_overwrite_time;
300
247k
  RETURN_NOT_OK(iter->FindLatestRecord(key_without_ht, &doc_ht, &value));
301
247k
  if (!iter->valid()) {
302
77.0k
    return Status::OK();
303
77.0k
  }
304
305
170k
  uint64_t merge_flags = 0;
306
170k
  MonoDelta ttl;
307
170k
  ValueType value_type;
308
170k
  RETURN_NOT_OK(Value::DecodePrimitiveValueType(value, &value_type, &merge_flags, &ttl));
309
170k
  if (value_type == ValueType::kInvalid) {
310
3
    return Status::OK();
311
3
  }
312
313
  // We update the expiration if and only if the write time is later than the write time
314
  // currently stored in expiration, and the record is not a regular record with default TTL.
315
  // This is done independently of whether the row is a TTL row.
316
  // In the case that the always_override flag is true, default TTL will not be preserved.
317
170k
  Expiration new_exp = *exp;
318
170k
  if (doc_ht.hybrid_time() >= exp->write_ht) {
319
    // We want to keep the default TTL otherwise.
320
170k
    if (ttl != Value::kMaxTtl || merge_flags == Value::kTtlFlag || exp->always_override) {
321
1.41k
      new_exp.write_ht = doc_ht.hybrid_time();
322
1.41k
      new_exp.ttl = ttl;
323
168k
    } else if (exp->ttl.IsNegative()) {
324
0
      new_exp.ttl = -new_exp.ttl;
325
0
    }
326
170k
  }
327
328
  // If we encounter a TTL row, we assign max_overwrite_time to be the write time of the
329
  // original value/init marker.
330
170k
  if (merge_flags == Value::kTtlFlag) {
331
90
    DocHybridTime new_ht;
332
90
    RETURN_NOT_OK(iter->NextFullValue(&new_ht, &value));
333
334
    // There could be a case where the TTL row exists, but the value has been
335
    // compacted away. Then, it is treated as a Tombstone written at the time
336
    // of the TTL row.
337
90
    if (!iter->valid() && !new_exp.ttl.IsNegative()) {
338
0
      new_exp.ttl = -new_exp.ttl;
339
90
    } else {
340
90
      ValueType value_type;
341
90
      RETURN_NOT_OK(Value::DecodePrimitiveValueType(value, &value_type));
342
      // Because we still do not know whether we are seeking something expired,
343
      // we must take the max_overwrite_time as if the value were not expired.
344
90
      doc_ht = new_ht;
345
90
    }
346
90
  }
347
348
170k
  if ((value_type == ValueType::kTombstone || value_type == ValueType::kInvalid) &&
349
333
      !new_exp.ttl.IsNegative()) {
350
333
    new_exp.ttl = -new_exp.ttl;
351
333
  }
352
170k
  *exp = new_exp;
353
354
170k
  if (doc_ht > *max_overwrite_time) {
355
170k
    *max_overwrite_time = doc_ht;
356
0
    VLOG(4) << "Max overwritten time for " << key_without_ht.ToDebugHexString() << ": "
357
0
            << *max_overwrite_time;
358
170k
  }
359
360
170k
  if (result_value)
361
169k
    RETURN_NOT_OK(result_value->Decode(value));
362
363
170k
  return Status::OK();
364
170k
}
365
366
}  // namespace
367
368
yb::Status GetRedisSubDocument(
369
    const DocDB& doc_db,
370
    const GetRedisSubDocumentData& data,
371
    const rocksdb::QueryId query_id,
372
    const TransactionOperationContext& txn_op_context,
373
    CoarseTimePoint deadline,
374
10.5k
    const ReadHybridTime& read_time) {
375
10.5k
  auto iter = CreateIntentAwareIterator(
376
10.5k
      doc_db, BloomFilterMode::USE_BLOOM_FILTER, data.subdocument_key, query_id,
377
10.5k
      txn_op_context, deadline, read_time);
378
10.5k
  return GetRedisSubDocument(iter.get(), data, nullptr /* projection */, SeekFwdSuffices::kFalse);
379
10.5k
}
380
381
yb::Status GetRedisSubDocument(
382
    IntentAwareIterator *db_iter,
383
    const GetRedisSubDocumentData& data,
384
    const vector<PrimitiveValue>* projection,
385
214k
    const SeekFwdSuffices seek_fwd_suffices) {
386
  // TODO(dtxn) scan through all involved transactions first to cache statuses in a batch,
387
  // so during building subdocument we don't need to request them one by one.
388
  // TODO(dtxn) we need to restart read with scan_ht = commit_ht if some transaction was committed
389
  // at time commit_ht within [scan_ht; read_request_time + max_clock_skew). Also we need
390
  // to wait until time scan_ht = commit_ht passed.
391
  // TODO(dtxn) for each scanned key (and its subkeys) we need to avoid *new* values committed at
392
  // ht <= scan_ht (or just ht < scan_ht?)
393
  // Question: what will break if we allow later commit at ht <= scan_ht ? Need to write down
394
  // detailed example.
395
214k
  *data.doc_found = false;
396
214k
  DOCDB_DEBUG_LOG("GetRedisSubDocument for key $0 @ $1", data.subdocument_key.ToDebugHexString(),
397
214k
                  db_iter->read_time().ToString());
398
399
  // The latest time at which any prefix of the given key was overwritten.
400
214k
  DocHybridTime max_overwrite_ht(DocHybridTime::kMin);
401
0
  VLOG(4) << "GetRedisSubDocument(" << data << ")";
402
403
214k
  SubDocKey found_subdoc_key;
404
214k
  auto dockey_size =
405
214k
      VERIFY_RESULT(DocKey::EncodedSize(data.subdocument_key, DocKeyPart::kWholeDocKey));
406
407
214k
  Slice key_slice(data.subdocument_key.data(), dockey_size);
408
409
  // First, check the descendants of the ID level for TTL or more recent writes.
410
214k
  IntentAwareIteratorPrefixScope prefix_scope(key_slice, db_iter);
411
214k
  if (seek_fwd_suffices) {
412
0
    db_iter->SeekForward(key_slice);
413
214k
  } else {
414
214k
    db_iter->Seek(key_slice);
415
214k
  }
416
214k
  {
417
214k
    auto temp_key = data.subdocument_key;
418
214k
    temp_key.remove_prefix(dockey_size);
419
247k
    for (;;) {
420
247k
      auto decode_result = VERIFY_RESULT(SubDocKey::DecodeSubkey(&temp_key));
421
247k
      if (!decode_result) {
422
214k
        break;
423
214k
      }
424
32.2k
      RETURN_NOT_OK(FindLastWriteTime(db_iter, key_slice, &max_overwrite_ht, &data.exp));
425
32.2k
      key_slice = Slice(key_slice.data(), temp_key.data() - key_slice.data());
426
32.2k
    }
427
214k
  }
428
429
  // By this point, key_slice is the DocKey and all the subkeys of subdocument_key. Check for
430
  // init-marker / tombstones at the top level; update max_overwrite_ht.
431
214k
  Value doc_value = Value(PrimitiveValue(ValueType::kInvalid));
432
214k
  RETURN_NOT_OK(FindLastWriteTime(db_iter, key_slice, &max_overwrite_ht, &data.exp, &doc_value));
433
434
214k
  const ValueType value_type = doc_value.value_type();
435
436
214k
  if (data.return_type_only) {
437
151k
    *data.doc_found = value_type != ValueType::kInvalid &&
438
128k
      !data.exp.ttl.IsNegative();
439
    // Check for expiration.
440
151k
    if (*data.doc_found && max_overwrite_ht != DocHybridTime::kMin) {
441
128k
      *data.doc_found =
442
128k
          !HasExpiredTTL(data.exp.write_ht, data.exp.ttl, db_iter->read_time().read);
443
128k
    }
444
151k
    if (*data.doc_found) {
445
      // Observe that this will have the right type but not necessarily the right value.
446
128k
      *data.result = SubDocument(doc_value.primitive_value());
447
128k
    }
448
151k
    return Status::OK();
449
151k
  }
450
451
63.5k
  if (projection == nullptr) {
452
63.5k
    *data.result = SubDocument(ValueType::kInvalid);
453
63.5k
    int64 num_values_observed = 0;
454
63.5k
    IntentAwareIteratorPrefixScope prefix_scope(key_slice, db_iter);
455
63.5k
    RETURN_NOT_OK(BuildSubDocument(db_iter, data, max_overwrite_ht,
456
63.5k
                                   &num_values_observed));
457
63.5k
    *data.doc_found = data.result->value_type() != ValueType::kInvalid;
458
63.5k
    if (*data.doc_found) {
459
40.2k
      if (value_type == ValueType::kRedisSet) {
460
0
        RETURN_NOT_OK(data.result->ConvertToRedisSet());
461
40.2k
      } else if (value_type == ValueType::kRedisTS) {
462
5.44k
        RETURN_NOT_OK(data.result->ConvertToRedisTS());
463
34.7k
      } else if (value_type == ValueType::kRedisSortedSet) {
464
6
        RETURN_NOT_OK(data.result->ConvertToRedisSortedSet());
465
34.7k
      } else if (value_type == ValueType::kRedisList) {
466
0
        RETURN_NOT_OK(data.result->ConvertToRedisList());
467
0
      }
468
40.2k
    }
469
63.5k
    return Status::OK();
470
0
  }
471
  // Seed key_bytes with the subdocument key. For each subkey in the projection, build subdocument
472
  // and reuse key_bytes while appending the subkey.
473
0
  *data.result = SubDocument();
474
0
  KeyBytes key_bytes;
475
  // Preallocate some extra space to avoid allocation for small subkeys.
476
0
  key_bytes.Reserve(data.subdocument_key.size() + kMaxBytesPerEncodedHybridTime + 32);
477
0
  key_bytes.AppendRawBytes(data.subdocument_key);
478
0
  const size_t subdocument_key_size = key_bytes.size();
479
0
  for (const PrimitiveValue& subkey : *projection) {
480
    // Append subkey to subdocument key. Reserve extra kMaxBytesPerEncodedHybridTime + 1 bytes in
481
    // key_bytes to avoid the internal buffer from getting reallocated and moved by SeekForward()
482
    // appending the hybrid time, thereby invalidating the buffer pointer saved by prefix_scope.
483
0
    subkey.AppendToKey(&key_bytes);
484
0
    key_bytes.Reserve(key_bytes.size() + kMaxBytesPerEncodedHybridTime + 1);
485
    // This seek is to initialize the iterator for BuildSubDocument call.
486
0
    IntentAwareIteratorPrefixScope prefix_scope(key_bytes, db_iter);
487
0
    db_iter->SeekForward(&key_bytes);
488
0
    SubDocument descendant(ValueType::kInvalid);
489
0
    int64 num_values_observed = 0;
490
0
    RETURN_NOT_OK(BuildSubDocument(
491
0
        db_iter, data.Adjusted(key_bytes, &descendant), max_overwrite_ht,
492
0
        &num_values_observed));
493
0
    *data.doc_found = descendant.value_type() != ValueType::kInvalid;
494
0
    data.result->SetChild(subkey, std::move(descendant));
495
496
    // Restore subdocument key by truncating the appended subkey.
497
0
    key_bytes.Truncate(subdocument_key_size);
498
0
  }
499
  // Make sure the iterator is placed outside the whole document in the end.
500
0
  key_bytes.Truncate(dockey_size);
501
0
  db_iter->SeekOutOfSubDoc(&key_bytes);
502
0
  return Status::OK();
503
0
}
504
505
0
std::string GetRedisSubDocumentData::ToString() const {
506
0
  return Format("{ subdocument_key: $0 exp.ttl: $1 exp.write_time: $2 return_type_only: $3 "
507
0
                    "low_subkey: $4 high_subkey: $5 }",
508
0
                SubDocKey::DebugSliceToString(subdocument_key), exp.ttl,
509
0
                exp.write_ht, return_type_only, low_subkey, high_subkey);
510
0
}
511
512
513
}  // namespace docdb
514
}  // namespace yb