YugabyteDB (2.13.0.0-b42, bfc6a6643e7399ac8a0e81d06a3ee6d6571b33ab)

Coverage Report

Created: 2022-03-09 17:30

/Users/deen/code/yugabyte-db/src/yb/docdb/in_mem_docdb.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/in_mem_docdb.h"
15
16
#include <sstream>
17
18
#include "yb/common/hybrid_time.h"
19
20
#include "yb/docdb/doc_key.h"
21
#include "yb/docdb/doc_reader.h"
22
#include "yb/docdb/docdb.h"
23
#include "yb/docdb/docdb_rocksdb_util.h"
24
#include "yb/docdb/docdb_test_util.h"
25
26
#include "yb/gutil/strings/substitute.h"
27
28
#include "yb/rocksdb/db.h"
29
30
#include "yb/util/status_format.h"
31
#include "yb/util/status_log.h"
32
#include "yb/util/test_macros.h"
33
34
using std::endl;
35
using std::string;
36
using std::stringstream;
37
using strings::Substitute;
38
39
namespace yb {
40
namespace docdb {
41
42
0
Status InMemDocDbState::SetPrimitive(const DocPath& doc_path, const PrimitiveValue& value) {
43
0
  VLOG(2) << __func__ << ": doc_path=" << doc_path.ToString() << ", value=" << value.ToString();
44
0
  const PrimitiveValue encoded_doc_key_as_primitive(doc_path.encoded_doc_key().AsSlice());
45
0
  const bool is_deletion = value.value_type() == ValueType::kTombstone;
46
0
  if (doc_path.num_subkeys() == 0) {
47
0
    if (is_deletion) {
48
0
      root_.DeleteChild(encoded_doc_key_as_primitive);
49
0
    } else {
50
0
      root_.SetChildPrimitive(encoded_doc_key_as_primitive, value);
51
0
    }
52
53
0
    return Status::OK();
54
0
  }
55
0
  SubDocument* current_subdoc = nullptr;
56
57
0
  if (is_deletion) {
58
0
    current_subdoc = root_.GetChild(encoded_doc_key_as_primitive);
59
0
    if (current_subdoc == nullptr) {
60
      // The subdocument we're trying to delete does not exist, nothing to do.
61
0
      return Status::OK();
62
0
    }
63
0
  } else {
64
0
    current_subdoc = root_.GetOrAddChild(encoded_doc_key_as_primitive).first;
65
0
  }
66
67
0
  const auto num_subkeys = doc_path.num_subkeys();
68
0
  for (size_t subkey_index = 0; subkey_index < num_subkeys - 1; ++subkey_index) {
69
0
    const PrimitiveValue& subkey = doc_path.subkey(subkey_index);
70
0
    if (subkey.value_type() == ValueType::kArrayIndex) {
71
0
      return STATUS(NotSupported, "Setting values at a given array index is not supported yet.");
72
0
    }
73
74
0
    if (current_subdoc->value_type() != ValueType::kObject) {
75
0
      return STATUS_FORMAT(IllegalState,
76
0
          "Cannot set or delete values inside a subdocument of type $0",
77
0
          current_subdoc->value_type());
78
0
    }
79
80
0
    if (is_deletion) {
81
0
      current_subdoc = current_subdoc->GetChild(subkey);
82
0
      if (current_subdoc == nullptr) {
83
        // Document does not exist, nothing to do.
84
0
        return Status::OK();
85
0
      }
86
0
    } else {
87
0
      current_subdoc = current_subdoc->GetOrAddChild(subkey).first;
88
0
    }
89
0
  }
90
91
0
  if (is_deletion) {
92
0
    current_subdoc->DeleteChild(doc_path.last_subkey());
93
0
  } else {
94
0
    current_subdoc->SetChildPrimitive(doc_path.last_subkey(), value);
95
0
  }
96
97
0
  return Status::OK();
98
0
}
99
100
0
Status InMemDocDbState::DeleteSubDoc(const DocPath &doc_path) {
101
0
  return SetPrimitive(doc_path, PrimitiveValue::kTombstone);
102
0
}
103
104
0
void InMemDocDbState::SetDocument(const KeyBytes& encoded_doc_key, SubDocument&& doc) {
105
0
  root_.SetChild(PrimitiveValue(encoded_doc_key.AsSlice()), std::move(doc));
106
0
}
107
108
0
const SubDocument* InMemDocDbState::GetSubDocument(const SubDocKey& subdoc_key) const {
109
0
  const SubDocument* current =
110
0
      root_.GetChild(PrimitiveValue(subdoc_key.doc_key().Encode().AsSlice()));
111
0
  for (const auto& subkey : subdoc_key.subkeys()) {
112
0
    if (current == nullptr) {
113
0
      return nullptr;
114
0
    }
115
0
    current = current->GetChild(subkey);
116
0
  }
117
0
  return current;
118
0
}
119
120
void InMemDocDbState::CaptureAt(const DocDB& doc_db, HybridTime hybrid_time,
121
0
                                rocksdb::QueryId query_id) {
122
  // Clear the internal state.
123
0
  root_ = SubDocument();
124
125
0
  auto rocksdb_iter = CreateRocksDBIterator(
126
0
      doc_db.regular, doc_db.key_bounds, BloomFilterMode::DONT_USE_BLOOM_FILTER,
127
0
      boost::none /* user_key_for_filter */, query_id);
128
0
  rocksdb_iter.SeekToFirst();
129
0
  KeyBytes prev_key;
130
0
  while (rocksdb_iter.Valid()) {
131
0
    const auto key = rocksdb_iter.key();
132
0
    CHECK_NE(0, prev_key.CompareTo(key)) << "Infinite loop detected on key " << prev_key.ToString();
133
0
    prev_key = KeyBytes(key);
134
135
0
    SubDocKey subdoc_key;
136
0
    CHECK_OK(subdoc_key.FullyDecodeFrom(key));
137
0
    CHECK_EQ(0, subdoc_key.num_subkeys())
138
0
        << "Expected to be positioned at the first key of a new document with no subkeys, "
139
0
        << "but found " << subdoc_key.num_subkeys() << " subkeys: " << subdoc_key.ToString();
140
0
    subdoc_key.remove_hybrid_time();
141
142
    // TODO: It would be good to be able to refer to a slice of the original key whenever we need
143
    //       to extract document key out of a subdocument key.
144
0
    auto encoded_doc_key = subdoc_key.doc_key().Encode();
145
    // TODO(dtxn) Pass real TransactionOperationContext when we need to support cross-shard
146
    // transactions write intents resolution during DocDbState capturing.
147
    // For now passing kNonTransactionalOperationContext in order to fail if there are any intents,
148
    // since this is not supported.
149
0
    auto encoded_subdoc_key = subdoc_key.EncodeWithoutHt();
150
0
    auto doc_from_rocksdb_opt = ASSERT_RESULT(yb::docdb::TEST_GetSubDocument(
151
0
        encoded_subdoc_key, doc_db, query_id, kNonTransactionalOperationContext,
152
0
        CoarseTimePoint::max() /* deadline */, ReadHybridTime::SingleTime(hybrid_time)));
153
    // doc_found can be false for deleted documents, and that is perfectly valid.
154
0
    if (doc_from_rocksdb_opt) {
155
0
      SetDocument(encoded_doc_key, std::move(*doc_from_rocksdb_opt));
156
0
    }
157
    // Go to the next top-level document key.
158
0
    ROCKSDB_SEEK(&rocksdb_iter, subdoc_key.AdvanceOutOfSubDoc().AsSlice());
159
160
0
    VLOG(4) << "After performing a seek: IsValid=" << rocksdb_iter.Valid();
161
0
    if (VLOG_IS_ON(4) && rocksdb_iter.Valid()) {
162
0
      VLOG(4) << "Next key: " << FormatSliceAsStr(rocksdb_iter.key());
163
0
      SubDocKey tmp_subdoc_key;
164
0
      CHECK_OK(tmp_subdoc_key.FullyDecodeFrom(rocksdb_iter.key()));
165
0
      VLOG(4) << "Parsed as SubDocKey: " << tmp_subdoc_key.ToString();
166
0
    }
167
0
  }
168
169
  // Initialize the "captured at" hybrid_time now, even though we expect it to be overwritten in
170
  // many cases. One common usage pattern is that this will be called with HybridTime::kMax, but
171
  // we'll later call SetCaptureHybridTime and set the hybrid_time to the last known hybrid_time of
172
  // an operation performed on DocDB.
173
0
  captured_at_ = hybrid_time;
174
175
  // Ensure we don't get any funny value types in the root node (had a test failure like this).
176
0
  CHECK_EQ(root_.value_type(), ValueType::kObject);
177
0
}
178
179
0
void InMemDocDbState::SetCaptureHybridTime(HybridTime hybrid_time) {
180
0
  CHECK(hybrid_time.is_valid());
181
0
  captured_at_ = hybrid_time;
182
0
}
183
184
0
bool InMemDocDbState::EqualsAndLogDiff(const InMemDocDbState &expected, bool log_diff) {
185
0
  bool matches = true;
186
0
  if (num_docs() != expected.num_docs()) {
187
0
    if (log_diff) {
188
0
      LOG(WARNING) << "Found " << num_docs() << " documents but expected to find "
189
0
                   << expected.num_docs();
190
0
    }
191
0
    matches = false;
192
0
  }
193
194
  // As an optimization, a SubDocument won't even maintain a map if it is an empty object that no
195
  // operations have been performed on. As we are using a SubDocument to represent the top-level
196
  // mapping of encoded document keys to SubDocuments here, we need to check for that situation.
197
0
  if (expected.root_.has_valid_object_container()) {
198
0
    for (const auto& expected_kv : expected.root_.object_container()) {
199
0
      const KeyBytes encoded_doc_key(expected_kv.first.GetString());
200
0
      const SubDocument& expected_doc = expected_kv.second;
201
0
      DocKey doc_key;
202
0
      CHECK_OK(doc_key.FullyDecodeFrom(encoded_doc_key.AsSlice()));
203
0
      const SubDocument* child_from_this = GetDocument(doc_key);
204
0
      if (child_from_this == nullptr) {
205
0
        if (log_diff) {
206
0
          LOG(WARNING) << "Document with key " << doc_key.ToString() << " is missing but is "
207
0
                       << "expected to be " << expected_doc.ToString();
208
0
        }
209
0
        matches = false;
210
0
        continue;
211
0
      }
212
0
      if (*child_from_this != expected_kv.second) {
213
0
        if (log_diff) {
214
0
          LOG(WARNING) << "Expected document with key " << doc_key.ToString() << " to be "
215
0
                       << expected_doc.ToString() << " but found " << *child_from_this;
216
0
        }
217
0
        matches = false;
218
0
      }
219
0
    }
220
0
  }
221
222
  // Also report all document keys that are present in this ("actual") database but are absent from
223
  // the other ("expected") database.
224
0
  if (root_.has_valid_object_container()) {
225
0
    for (const auto& actual_kv : root_.object_container()) {
226
0
      const KeyBytes encoded_doc_key(actual_kv.first.GetString());
227
0
      DocKey doc_key;
228
0
      CHECK_OK(doc_key.FullyDecodeFrom(encoded_doc_key.AsSlice()));
229
0
      const SubDocument* child_from_expected = GetDocument(doc_key);
230
0
      if (child_from_expected == nullptr) {
231
0
        DocKey doc_key;
232
0
        CHECK_OK(doc_key.FullyDecodeFrom(encoded_doc_key.AsSlice()));
233
0
        if (log_diff) {
234
0
          LOG(WARNING) << "Unexpected document found with key " << doc_key.ToString() << ":"
235
0
                       << actual_kv.second.ToString();
236
0
        }
237
0
        matches = false;
238
0
      }
239
0
    }
240
0
  }
241
242
  // A brute-force way to check that the comparison logic above is correct.
243
  // TODO: disable this if it makes tests much slower.
244
0
  CHECK_EQ(matches, ToDebugString() == expected.ToDebugString());
245
0
  return matches;
246
0
}
247
248
0
string InMemDocDbState::ToDebugString() const {
249
0
  stringstream ss;
250
0
  if (root_.has_valid_object_container()) {
251
0
    int i = 1;
252
0
    for (const auto& kv : root_.object_container()) {
253
0
      DocKey doc_key;
254
0
      CHECK_OK(doc_key.FullyDecodeFrom(rocksdb::Slice(kv.first.GetString())));
255
0
      ss << i << ". " << doc_key.ToString() << " => " << kv.second.ToString() << endl;
256
0
      ++i;
257
0
    }
258
0
  }
259
0
  string dump_str = ss.str();
260
0
  return dump_str.empty() ? "<Empty>" : dump_str;
261
0
}
262
263
0
HybridTime InMemDocDbState::captured_at() const {
264
0
  CHECK(captured_at_.is_valid());
265
0
  return captured_at_;
266
0
}
267
268
0
void InMemDocDbState::SanityCheck() const {
269
0
  CHECK_EQ(root_.value_type(), ValueType::kObject);
270
0
}
271
272
0
const SubDocument* InMemDocDbState::GetDocument(const DocKey& doc_key) const {
273
0
  return GetSubDocument(SubDocKey(doc_key));
274
0
}
275
276
}  // namespace docdb
277
}  // namespace yb