Coverage Report

Created: 2026-11-12 08:06

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/rocksdb/db/wide/wide_column_serialization.cc
Line
Count
Source
1
//  Copyright (c) Meta Platforms, Inc. and affiliates.
2
//  This source code is licensed under both the GPLv2 (found in the
3
//  COPYING file in the root directory) and Apache 2.0 License
4
//  (found in the LICENSE.Apache file in the root directory).
5
6
#include "db/wide/wide_column_serialization.h"
7
8
#include <cassert>
9
#include <cstring>
10
11
#include "db/blob/blob_fetcher.h"
12
#include "db/blob/blob_index.h"
13
#include "db/blob/prefetch_buffer_collection.h"
14
#include "db/wide/wide_columns_helper.h"
15
#include "rocksdb/slice.h"
16
#include "util/autovector.h"
17
#include "util/cast_util.h"
18
#include "util/coding.h"
19
20
namespace ROCKSDB_NAMESPACE {
21
22
Status WideColumnSerialization::BuildBlobIndexMap(
23
    size_t num_columns,
24
    const std::vector<std::pair<size_t, BlobIndex>>& blob_columns,
25
0
    std::vector<const BlobIndex*>& blob_index_map) {
26
0
  if (Status s = ValidateWideColumnLimit(num_columns, "Too many wide columns");
27
0
      !s.ok()) {
28
0
    return s;
29
0
  }
30
31
0
  blob_index_map.assign(num_columns, nullptr);
32
0
  for (const auto& blob_col : blob_columns) {
33
0
    if (blob_col.first >= blob_index_map.size()) {
34
0
      return Status::InvalidArgument("Blob column index out of range");
35
0
    }
36
0
    blob_index_map[blob_col.first] = &blob_col.second;
37
0
  }
38
39
0
  return Status::OK();
40
0
}
41
42
bool WideColumnSerialization::ContainsBlobType(const char* type_bytes,
43
0
                                               uint32_t num_columns) {
44
0
  for (uint32_t i = 0; i < num_columns; ++i) {
45
0
    if (static_cast<uint8_t>(type_bytes[i]) == kTypeBlobIndex) {
46
0
      return true;
47
0
    }
48
0
  }
49
0
  return false;
50
0
}
51
52
Status WideColumnSerialization::Serialize(const WideColumns& columns,
53
0
                                          std::string& output) {
54
0
  const size_t num_columns = columns.size();
55
56
0
  if (Status sv = ValidateWideColumnLimit(num_columns, "Too many wide columns");
57
0
      !sv.ok()) {
58
0
    return sv;
59
0
  }
60
61
0
  PutVarint32(&output, kVersion1);
62
63
0
  PutVarint32(&output, static_cast<uint32_t>(num_columns));
64
65
0
  const Slice* prev_name = nullptr;
66
67
0
  for (size_t i = 0; i < columns.size(); ++i) {
68
0
    const WideColumn& column = columns[i];
69
70
0
    const Slice& name = column.name();
71
0
    if (Status s_name =
72
0
            ValidateWideColumnLimit(name.size(), "Wide column name too long");
73
0
        !s_name.ok()) {
74
0
      return s_name;
75
0
    }
76
77
0
    if (prev_name) {
78
0
      if (Status so = ValidateColumnOrder(*prev_name, name); !so.ok()) {
79
0
        return so;
80
0
      }
81
0
    }
82
83
0
    const Slice& value = column.value();
84
0
    if (Status s_val =
85
0
            ValidateWideColumnLimit(value.size(), "Wide column value too long");
86
0
        !s_val.ok()) {
87
0
      return s_val;
88
0
    }
89
90
0
    PutLengthPrefixedSlice(&output, name);
91
0
    PutVarint32(&output, static_cast<uint32_t>(value.size()));
92
93
0
    prev_name = &name;
94
0
  }
95
96
0
  for (const auto& column : columns) {
97
0
    const Slice& value = column.value();
98
99
0
    output.append(value.data(), value.size());
100
0
  }
101
102
0
  return Status::OK();
103
0
}
104
105
0
size_t WideColumnSerialization::SerializedSizeV1(const WideColumns& columns) {
106
0
  size_t size = VarintLength(kVersion1) +
107
0
                VarintLength(static_cast<uint32_t>(columns.size()));
108
109
0
  for (const auto& column : columns) {
110
0
    const size_t name_size = column.name().size();
111
0
    const size_t value_size = column.value().size();
112
0
    size += VarintLength(name_size) + name_size + VarintLength(value_size) +
113
0
            value_size;
114
0
  }
115
116
0
  return size;
117
0
}
118
119
template <typename GetName, typename GetValue>
120
Status WideColumnSerialization::SerializeV2Impl(
121
    size_t num_columns,
122
    const std::vector<std::pair<size_t, BlobIndex>>& blob_columns,
123
0
    std::string& output, GetName get_name, GetValue get_value) {
124
0
  std::vector<const BlobIndex*> blob_index_map;
125
0
  if (Status s = BuildBlobIndexMap(num_columns, blob_columns, blob_index_map);
126
0
      !s.ok()) {
127
0
    return s;
128
0
  }
129
0
  assert(blob_index_map.size() == num_columns);
130
131
  // First pass: validate column ordering, compute sizes, serialize blob
132
  // indices, and build column types.
133
  // Only allocate serialized blob index strings for actual blob columns
134
  // (typically few) rather than all num_columns.
135
0
  std::vector<std::string> serialized_blob_indices(blob_columns.size());
136
0
  autovector<uint32_t, 16> name_sizes;
137
0
  autovector<uint32_t, 16> value_sizes;
138
0
  name_sizes.resize(num_columns);
139
0
  value_sizes.resize(num_columns);
140
0
  std::string column_types;
141
0
  column_types.reserve(num_columns);
142
143
0
  Slice prev_name_storage;
144
0
  bool has_prev = false;
145
0
  uint64_t name_sizes_bytes = 0;
146
0
  uint64_t names_bytes = 0;
147
0
  uint64_t total_value_sizes_bytes = 0;
148
0
  uint64_t total_values_bytes = 0;
149
0
  size_t blob_ser_idx = 0;
150
151
0
  for (size_t i = 0; i < num_columns; ++i) {
152
0
    const Slice name = get_name(i);
153
0
    const Slice value = get_value(i);
154
155
0
    if (Status sn =
156
0
            ValidateWideColumnLimit(name.size(), "Wide column name too long");
157
0
        !sn.ok()) {
158
0
      return sn;
159
0
    }
160
161
0
    if (has_prev) {
162
0
      if (Status so = ValidateColumnOrder(prev_name_storage, name); !so.ok()) {
163
0
        return so;
164
0
      }
165
0
    }
166
167
0
    name_sizes[i] = static_cast<uint32_t>(name.size());
168
0
    name_sizes_bytes += VarintLength(name_sizes[i]);
169
0
    names_bytes += name_sizes[i];
170
171
0
    if (blob_index_map[i] != nullptr) {
172
0
      const BlobIndex* blob_idx = blob_index_map[i];
173
0
      blob_idx->EncodeTo(&serialized_blob_indices[blob_ser_idx]);
174
0
      value_sizes[i] =
175
0
          static_cast<uint32_t>(serialized_blob_indices[blob_ser_idx].size());
176
0
      column_types.push_back(static_cast<char>(kTypeBlobIndex));
177
0
      ++blob_ser_idx;
178
0
    } else {
179
0
      if (Status svl = ValidateWideColumnLimit(value.size(),
180
0
                                               "Wide column value too long");
181
0
          !svl.ok()) {
182
0
        return svl;
183
0
      }
184
0
      value_sizes[i] = static_cast<uint32_t>(value.size());
185
0
      column_types.push_back(static_cast<char>(kTypeValue));
186
0
    }
187
188
0
    total_value_sizes_bytes += VarintLength(value_sizes[i]);
189
0
    total_values_bytes += value_sizes[i];
190
191
0
    prev_name_storage = name;
192
0
    has_prev = true;
193
0
  }
194
195
0
  const uint64_t kMaxWideColumnComponent =
196
0
      static_cast<uint64_t>(std::numeric_limits<uint32_t>::max());
197
0
  if (name_sizes_bytes > kMaxWideColumnComponent ||
198
0
      total_value_sizes_bytes > kMaxWideColumnComponent) {
199
0
    return Status::InvalidArgument("Wide column metadata too large");
200
0
  }
201
0
  if (names_bytes > kMaxWideColumnComponent) {
202
0
    return Status::InvalidArgument("Wide column names too large");
203
0
  }
204
0
  if (total_values_bytes > kMaxWideColumnComponent) {
205
0
    return Status::InvalidArgument("Wide column values too large");
206
0
  }
207
208
0
  const auto name_sizes_bytes32 = static_cast<uint32_t>(name_sizes_bytes);
209
0
  const auto names_bytes32 = static_cast<uint32_t>(names_bytes);
210
0
  const auto total_value_sizes_bytes32 =
211
0
      static_cast<uint32_t>(total_value_sizes_bytes);
212
0
  const auto total_values_bytes32 = static_cast<uint32_t>(total_values_bytes);
213
214
  // Second pass: write all V2 sections to output.
215
  // Pre-allocate output string.
216
0
  const size_t total_size = VarintLength(kVersion2) +
217
0
                            VarintLength(static_cast<uint32_t>(num_columns)) +
218
0
                            num_columns +  // column types
219
0
                            VarintLength(name_sizes_bytes32) +
220
0
                            VarintLength(total_value_sizes_bytes32) +
221
0
                            VarintLength(names_bytes32) + name_sizes_bytes32 +
222
0
                            total_value_sizes_bytes32 + names_bytes32 +
223
0
                            total_values_bytes32;
224
225
0
  const size_t base_offset = output.size();
226
0
  output.reserve(base_offset + total_size);
227
228
  // Sections 1-3: header, skip info, column types
229
0
  PutVarint32(&output, kVersion2);
230
0
  PutVarint32(&output, static_cast<uint32_t>(num_columns));
231
0
  PutVarint32(&output, name_sizes_bytes32);
232
0
  PutVarint32(&output, total_value_sizes_bytes32);
233
0
  PutVarint32(&output, names_bytes32);
234
0
  output.append(column_types);
235
236
  // Sections 4-7: resize to final size, then write all 4 sections in a
237
  // single loop using independent pointers. Each section's start offset is
238
  // known from the sizes computed in the first pass.
239
0
  if (num_columns == 0) {
240
0
    return Status::OK();
241
0
  }
242
243
0
  const size_t sec4_offset = output.size();
244
0
  output.resize(base_offset + total_size);
245
246
0
  char* s4 = &output[sec4_offset];            // section 4: name sizes
247
0
  char* s5 = s4 + name_sizes_bytes32;         // section 5: value sizes
248
0
  char* s6 = s5 + total_value_sizes_bytes32;  // section 6: names
249
0
  char* s7 = s6 + names_bytes32;              // section 7: values
250
251
0
  size_t blob_write_idx = 0;
252
0
  for (size_t i = 0; i < num_columns; ++i) {
253
0
    s4 = EncodeVarint32(s4, name_sizes[i]);
254
0
    s5 = EncodeVarint32(s5, value_sizes[i]);
255
256
0
    memcpy(s6, get_name(i).data(), name_sizes[i]);
257
0
    s6 += name_sizes[i];
258
259
0
    if (blob_index_map[i] != nullptr) {
260
0
      memcpy(s7, serialized_blob_indices[blob_write_idx].data(),
261
0
             value_sizes[i]);
262
0
      ++blob_write_idx;
263
0
    } else {
264
0
      memcpy(s7, get_value(i).data(), value_sizes[i]);
265
0
    }
266
0
    s7 += value_sizes[i];
267
0
  }
268
269
0
  return Status::OK();
270
0
}
Unexecuted instantiation: wide_column_serialization.cc:rocksdb::Status rocksdb::WideColumnSerialization::SerializeV2Impl<rocksdb::WideColumnSerialization::SerializeV2(std::__1::vector<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > const&, std::__1::vector<std::__1::pair<unsigned long, rocksdb::BlobIndex>, std::__1::allocator<std::__1::pair<unsigned long, rocksdb::BlobIndex> > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::$_0, rocksdb::WideColumnSerialization::SerializeV2(std::__1::vector<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > const&, std::__1::vector<std::__1::pair<unsigned long, rocksdb::BlobIndex>, std::__1::allocator<std::__1::pair<unsigned long, rocksdb::BlobIndex> > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::$_1>(unsigned long, std::__1::vector<std::__1::pair<unsigned long, rocksdb::BlobIndex>, std::__1::allocator<std::__1::pair<unsigned long, rocksdb::BlobIndex> > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&, rocksdb::WideColumnSerialization::SerializeV2(std::__1::vector<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > const&, std::__1::vector<std::__1::pair<unsigned long, rocksdb::BlobIndex>, std::__1::allocator<std::__1::pair<unsigned long, rocksdb::BlobIndex> > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::$_0, rocksdb::WideColumnSerialization::SerializeV2(std::__1::vector<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > >, std::__1::allocator<std::__1::pair<std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> > > > > const&, std::__1::vector<std::__1::pair<unsigned long, rocksdb::BlobIndex>, std::__1::allocator<std::__1::pair<unsigned long, rocksdb::BlobIndex> > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::$_1)
Unexecuted instantiation: wide_column_serialization.cc:rocksdb::Status rocksdb::WideColumnSerialization::SerializeV2Impl<rocksdb::WideColumnSerialization::SerializeV2(std::__1::vector<rocksdb::WideColumn, std::__1::allocator<rocksdb::WideColumn> > const&, std::__1::vector<std::__1::pair<unsigned long, rocksdb::BlobIndex>, std::__1::allocator<std::__1::pair<unsigned long, rocksdb::BlobIndex> > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::$_0, rocksdb::WideColumnSerialization::SerializeV2(std::__1::vector<rocksdb::WideColumn, std::__1::allocator<rocksdb::WideColumn> > const&, std::__1::vector<std::__1::pair<unsigned long, rocksdb::BlobIndex>, std::__1::allocator<std::__1::pair<unsigned long, rocksdb::BlobIndex> > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::$_1>(unsigned long, std::__1::vector<std::__1::pair<unsigned long, rocksdb::BlobIndex>, std::__1::allocator<std::__1::pair<unsigned long, rocksdb::BlobIndex> > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&, rocksdb::WideColumnSerialization::SerializeV2(std::__1::vector<rocksdb::WideColumn, std::__1::allocator<rocksdb::WideColumn> > const&, std::__1::vector<std::__1::pair<unsigned long, rocksdb::BlobIndex>, std::__1::allocator<std::__1::pair<unsigned long, rocksdb::BlobIndex> > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::$_0, rocksdb::WideColumnSerialization::SerializeV2(std::__1::vector<rocksdb::WideColumn, std::__1::allocator<rocksdb::WideColumn> > const&, std::__1::vector<std::__1::pair<unsigned long, rocksdb::BlobIndex>, std::__1::allocator<std::__1::pair<unsigned long, rocksdb::BlobIndex> > > const&, std::__1::basic_string<char, std::__1::char_traits<char>, std::__1::allocator<char> >&)::$_1)
271
272
Status WideColumnSerialization::SerializeV2(
273
    const std::vector<std::pair<std::string, std::string>>& columns,
274
    const std::vector<std::pair<size_t, BlobIndex>>& blob_columns,
275
0
    std::string& output) {
276
0
  return SerializeV2Impl(
277
0
      columns.size(), blob_columns, output,
278
0
      [&](size_t i) { return Slice(columns[i].first); },
279
0
      [&](size_t i) { return Slice(columns[i].second); });
280
0
}
281
282
Status WideColumnSerialization::SerializeV2(
283
    const WideColumns& columns,
284
    const std::vector<std::pair<size_t, BlobIndex>>& blob_columns,
285
0
    std::string& output) {
286
0
  return SerializeV2Impl(
287
0
      columns.size(), blob_columns, output,
288
0
      [&](size_t i) { return columns[i].name(); },
289
0
      [&](size_t i) { return columns[i].value(); });
290
0
}
291
292
Status WideColumnSerialization::DeserializeV1(
293
0
    Slice& input, uint32_t num_columns, std::vector<WideColumn>& columns) {
294
0
  columns.reserve(num_columns);
295
296
0
  autovector<uint32_t, 16> column_value_sizes;
297
0
  column_value_sizes.reserve(num_columns);
298
299
0
  for (uint32_t i = 0; i < num_columns; ++i) {
300
0
    Slice name;
301
0
    if (!GetLengthPrefixedSlice(&input, &name)) {
302
0
      return Status::Corruption("Error decoding wide column name");
303
0
    }
304
305
0
    if (!columns.empty()) {
306
0
      if (Status so = ValidateColumnOrder(columns.back().name(), name);
307
0
          !so.ok()) {
308
0
        return so;
309
0
      }
310
0
    }
311
312
0
    columns.emplace_back(name, Slice());
313
314
0
    uint32_t value_size = 0;
315
0
    if (!GetVarint32(&input, &value_size)) {
316
0
      return Status::Corruption("Error decoding wide column value size");
317
0
    }
318
319
0
    column_value_sizes.emplace_back(value_size);
320
0
  }
321
322
0
  const Slice data(input);
323
0
  size_t pos = 0;
324
325
0
  for (uint32_t i = 0; i < num_columns; ++i) {
326
0
    const uint32_t value_size = column_value_sizes[i];
327
328
0
    if (pos + value_size > data.size()) {
329
0
      return Status::Corruption("Error decoding wide column value payload");
330
0
    }
331
332
0
    columns[i].value() = Slice(data.data() + pos, value_size);
333
334
0
    pos += value_size;
335
0
  }
336
337
  // Reject any trailing bytes after the last value: a serialized entity must be
338
  // exactly the whole input (V2 already enforces this via its section-size
339
  // check; this keeps V1 consistent).
340
0
  if (pos != data.size()) {
341
0
    return Status::Corruption(
342
0
        "Unexpected trailing data after wide column entity");
343
0
  }
344
345
0
  return Status::OK();
346
0
}
347
348
Status WideColumnSerialization::DeserializeV2Impl(
349
    Slice& input, uint32_t num_columns, std::vector<WideColumn>& columns,
350
0
    std::vector<ValueType>& column_types) {
351
  // Section 2: SKIP INFO (3 varints)
352
0
  uint32_t name_sizes_bytes = 0;
353
0
  uint32_t value_sizes_bytes = 0;
354
0
  uint32_t names_bytes = 0;
355
0
  if (!GetVarint32(&input, &name_sizes_bytes)) {
356
0
    return Status::Corruption("Error decoding wide column name sizes bytes");
357
0
  }
358
0
  if (!GetVarint32(&input, &value_sizes_bytes)) {
359
0
    return Status::Corruption("Error decoding wide column value sizes bytes");
360
0
  }
361
0
  if (!GetVarint32(&input, &names_bytes)) {
362
0
    return Status::Corruption("Error decoding wide column names bytes");
363
0
  }
364
365
  // Section 3: COLUMN TYPES (N bytes, each is a ValueType)
366
0
  if (input.size() < num_columns) {
367
0
    return Status::Corruption("Error decoding wide column types");
368
0
  }
369
0
  column_types.resize(num_columns);
370
0
  for (uint32_t i = 0; i < num_columns; ++i) {
371
0
    column_types[i] = static_cast<ValueType>(input[i]);
372
0
    if (!IsValidColumnValueType(column_types[i])) {
373
0
      return Status::Corruption("Unsupported wide column ValueType");
374
0
    }
375
0
  }
376
0
  input.remove_prefix(num_columns);
377
378
  // Validate that sections 4-6 fit in the remaining input
379
0
  const size_t metadata_size =
380
0
      name_sizes_bytes + value_sizes_bytes + names_bytes;
381
0
  if (input.size() < metadata_size) {
382
0
    return Status::Corruption("Error decoding wide column sections");
383
0
  }
384
385
  // Set up 4 pointers into sections 4-7 for single-loop parsing.
386
  // Skip info gives us exact boundaries for each section.
387
0
  const char* s4 = input.data();  // section 4: name sizes
388
0
  const char* s4_limit = s4 + name_sizes_bytes;
389
0
  const char* s5 = s4_limit;  // section 5: value sizes
390
0
  const char* s5_limit = s5 + value_sizes_bytes;
391
0
  const char* s6 = s5_limit;          // section 6: names
392
0
  const char* s7 = s6 + names_bytes;  // section 7: values
393
0
  const char* input_end = input.data() + input.size();
394
395
0
  columns.reserve(num_columns);
396
0
  size_t name_pos = 0;
397
0
  size_t value_pos = 0;
398
399
0
  for (uint32_t i = 0; i < num_columns; ++i) {
400
    // Decode name size from section 4
401
0
    uint32_t ns = 0;
402
0
    const char* s4_next = GetVarint32Ptr(s4, s4_limit, &ns);
403
0
    if (s4_next == nullptr) {
404
0
      return Status::Corruption("Error decoding wide column name size");
405
0
    }
406
0
    s4 = s4_next;
407
408
    // Decode value size from section 5
409
0
    uint32_t vs = 0;
410
0
    const char* s5_next = GetVarint32Ptr(s5, s5_limit, &vs);
411
0
    if (s5_next == nullptr) {
412
0
      return Status::Corruption("Error decoding wide column value size");
413
0
    }
414
0
    s5 = s5_next;
415
416
    // Read name from section 6
417
0
    if (name_pos + ns > names_bytes) {
418
0
      return Status::Corruption("Error decoding wide column name");
419
0
    }
420
0
    Slice name(s6 + name_pos, ns);
421
422
0
    if (!columns.empty()) {
423
0
      if (Status so = ValidateColumnOrder(columns.back().name(), name);
424
0
          !so.ok()) {
425
0
        return so;
426
0
      }
427
0
    }
428
429
    // Read value from section 7
430
0
    if (s7 + value_pos + vs > input_end) {
431
0
      return Status::Corruption("Error decoding wide column value payload");
432
0
    }
433
434
0
    columns.emplace_back(name, Slice(s7 + value_pos, vs));
435
0
    name_pos += ns;
436
0
    value_pos += vs;
437
0
  }
438
439
  // Validate that the consumed bytes match the declared section sizes. Because
440
  // a V2 entity's values section is defined to extend to the end of `input`,
441
  // this also rejects any trailing bytes after the entity.
442
0
  const size_t total_values_bytes = static_cast<size_t>(input_end - s7);
443
0
  if (s4 != s4_limit || s5 != s5_limit || name_pos != names_bytes ||
444
0
      value_pos != total_values_bytes) {
445
0
    return Status::Corruption("Wide column section size mismatch");
446
0
  }
447
448
0
  return Status::OK();
449
0
}
450
451
Status WideColumnSerialization::Deserialize(
452
    const Slice& entity, WideColumns& columns,
453
0
    std::vector<std::pair<size_t, BlobIndex>>* blob_columns) {
454
0
  assert(columns.empty());
455
0
  assert(!blob_columns || blob_columns->empty());
456
457
0
  Slice input = entity;
458
0
  uint32_t version = 0;
459
0
  if (!GetVarint32(&input, &version)) {
460
0
    return Status::Corruption("Error decoding wide column version");
461
0
  }
462
463
  // A version newer than kVersion2 is reported as Corruption, consistent with
464
  // how the rest of the codebase classifies an unrecognized serialized
465
  // version/type (BlobIndex, blob log header, table footer).
466
0
  if (version > kVersion2) {
467
0
    return Status::Corruption("Corrupt or unsupported wide column version");
468
0
  }
469
470
0
  uint32_t num_columns = 0;
471
0
  if (!GetVarint32(&input, &num_columns)) {
472
0
    return Status::Corruption("Error decoding number of wide columns");
473
0
  }
474
475
  // A zero-column entity is still dispatched to the version-specific parser
476
  // below (rather than returning OK here) so that trailing bytes after an empty
477
  // entity are rejected as Corruption too -- both parsers handle num_columns==0
478
  // and validate that the input is exactly one serialized entity.
479
0
  if (version < kVersion2) {
480
0
    return DeserializeV1(input, num_columns, columns);
481
0
  }
482
483
  // V2 layout: parse columns and extract any blob column info.
484
0
  std::vector<ValueType> column_types;
485
0
  if (Status s = DeserializeV2Impl(input, num_columns, columns, column_types);
486
0
      !s.ok()) {
487
0
    return s;
488
0
  }
489
0
  assert(column_types.size() == num_columns);
490
0
  assert(columns.size() == num_columns);
491
492
0
  for (uint32_t i = 0; i < num_columns; ++i) {
493
0
    if (column_types[i] != kTypeBlobIndex) {
494
0
      continue;
495
0
    }
496
497
0
    if (!blob_columns) {
498
      // The caller promised a fully resolved entity; a blob reference here is
499
      // unexpected and may indicate corruption (or a logic error)
500
0
      return Status::Corruption(
501
0
          "Wide column blob reference in a context where it is not expected.");
502
0
    }
503
504
0
    BlobIndex blob_idx;
505
0
    Slice blob_slice = columns[i].value();
506
0
    if (blob_slice.empty()) {
507
0
      return Status::Corruption("Empty blob index in wide column");
508
0
    }
509
0
    if (Status bs = blob_idx.DecodeFrom(blob_slice); !bs.ok()) {
510
0
      return Status::Corruption("Error decoding blob index in wide column");
511
0
    }
512
0
    blob_columns->emplace_back(i, blob_idx);
513
0
  }
514
515
0
  return Status::OK();
516
0
}
517
518
Status WideColumnSerialization::HasBlobColumns(const Slice& input,
519
0
                                               bool& has_blob_columns) {
520
0
  has_blob_columns = false;
521
522
0
  Slice input_ref = input;
523
524
0
  uint32_t version = 0;
525
0
  if (!GetVarint32(&input_ref, &version)) {
526
0
    return Status::Corruption("Error decoding wide column version");
527
0
  }
528
529
  // Version 1 never has blob columns
530
0
  if (version < kVersion2) {
531
0
    return Status::OK();
532
0
  }
533
0
  if (version > kVersion2) {
534
0
    return Status::Corruption("Corrupt or unsupported wide column version");
535
0
  }
536
537
0
  uint32_t num_columns = 0;
538
0
  if (!GetVarint32(&input_ref, &num_columns)) {
539
0
    return Status::Corruption("Error decoding number of wide columns");
540
0
  }
541
542
0
  if (!num_columns) {
543
0
    return Status::OK();
544
0
  }
545
546
  // V2: Skip over SKIP INFO (3 varints) to reach COLUMN TYPES section.
547
0
  uint32_t unused_name_sizes_bytes = 0;
548
0
  uint32_t unused_value_sizes_bytes = 0;
549
0
  uint32_t unused_names_bytes = 0;
550
0
  if (!GetVarint32(&input_ref, &unused_name_sizes_bytes) ||
551
0
      !GetVarint32(&input_ref, &unused_value_sizes_bytes) ||
552
0
      !GetVarint32(&input_ref, &unused_names_bytes)) {
553
0
    return Status::Corruption("Error decoding wide column skip info");
554
0
  }
555
0
  if (input_ref.size() < num_columns) {
556
0
    return Status::Corruption("Error decoding wide column types");
557
0
  }
558
0
  for (uint32_t i = 0; i < num_columns; ++i) {
559
0
    if (!IsValidColumnValueType(static_cast<ValueType>(input_ref[i]))) {
560
0
      return Status::Corruption("Unsupported wide column ValueType");
561
0
    }
562
0
  }
563
0
  has_blob_columns = ContainsBlobType(input_ref.data(), num_columns);
564
565
0
  return Status::OK();
566
0
}
567
568
Status WideColumnSerialization::ForEachBlobFileNumber(
569
    const Slice& input,
570
0
    const std::function<Status(const BlobIndex&)>& callback) {
571
0
  Slice input_ref = input;
572
573
0
  uint32_t version = 0;
574
0
  if (!GetVarint32(&input_ref, &version)) {
575
0
    return Status::Corruption("Error decoding wide column version");
576
0
  }
577
578
0
  if (version < kVersion2) {
579
0
    return Status::OK();
580
0
  }
581
0
  if (version > kVersion2) {
582
0
    return Status::Corruption("Corrupt or unsupported wide column version");
583
0
  }
584
585
0
  uint32_t num_columns = 0;
586
0
  if (!GetVarint32(&input_ref, &num_columns)) {
587
0
    return Status::Corruption("Error decoding number of wide columns");
588
0
  }
589
590
0
  if (!num_columns) {
591
0
    return Status::OK();
592
0
  }
593
594
  // Read SKIP INFO
595
0
  uint32_t name_sizes_bytes = 0;
596
0
  uint32_t value_sizes_bytes = 0;
597
0
  uint32_t names_bytes = 0;
598
0
  if (!GetVarint32(&input_ref, &name_sizes_bytes) ||
599
0
      !GetVarint32(&input_ref, &value_sizes_bytes) ||
600
0
      !GetVarint32(&input_ref, &names_bytes)) {
601
0
    return Status::Corruption("Error decoding wide column skip info");
602
0
  }
603
604
  // Read COLUMN TYPES
605
0
  if (input_ref.size() < num_columns) {
606
0
    return Status::Corruption("Error decoding wide column types");
607
0
  }
608
609
  // Validate every column type, and note whether any blob columns are present.
610
  // An unrecognized/unsupported type byte is Corruption (consistent with
611
  // Deserialize() and HasBlobColumns()) rather than silently treated as a
612
  // non-blob column.
613
0
  bool has_any_blob = false;
614
0
  for (uint32_t i = 0; i < num_columns; ++i) {
615
0
    const auto type = lossless_cast<ValueType>(input_ref[i]);
616
0
    if (!IsValidColumnValueType(type)) {
617
0
      return Status::Corruption("Unsupported wide column ValueType");
618
0
    }
619
0
    if (type == kTypeBlobIndex) {
620
0
      has_any_blob = true;
621
0
    }
622
0
  }
623
624
0
  if (!has_any_blob) {
625
0
    return Status::OK();
626
0
  }
627
628
  // Need to skip to value sizes to find blob column value offsets,
629
  // then skip to the values section to decode blob indices.
630
0
  const char* type_bytes = input_ref.data();
631
0
  input_ref.remove_prefix(num_columns);
632
633
  // Skip NAME SIZES section
634
0
  if (input_ref.size() < name_sizes_bytes) {
635
0
    return Status::Corruption("Error decoding wide column name sizes");
636
0
  }
637
0
  input_ref.remove_prefix(name_sizes_bytes);
638
639
  // Read VALUE SIZES section to find blob column value offsets
640
0
  if (input_ref.size() < value_sizes_bytes) {
641
0
    return Status::Corruption("Error decoding wide column value sizes");
642
0
  }
643
0
  const char* vs_ptr = input_ref.data();
644
0
  const char* vs_limit = vs_ptr + value_sizes_bytes;
645
0
  input_ref.remove_prefix(value_sizes_bytes);
646
647
  // Skip NAMES section
648
0
  if (input_ref.size() < names_bytes) {
649
0
    return Status::Corruption("Error decoding wide column names");
650
0
  }
651
0
  input_ref.remove_prefix(names_bytes);
652
653
  // Now input_ref points to VALUES section. Walk through value sizes
654
  // and decode only blob column values.
655
0
  size_t value_pos = 0;
656
0
  for (uint32_t i = 0; i < num_columns; ++i) {
657
0
    uint32_t vs = 0;
658
0
    const char* vs_next = GetVarint32Ptr(vs_ptr, vs_limit, &vs);
659
0
    if (vs_next == nullptr) {
660
0
      return Status::Corruption("Error decoding wide column value size");
661
0
    }
662
0
    vs_ptr = vs_next;
663
664
0
    if (static_cast<uint8_t>(type_bytes[i]) == kTypeBlobIndex) {
665
0
      if (value_pos + vs > input_ref.size()) {
666
0
        return Status::Corruption("Error decoding wide column blob index");
667
0
      }
668
0
      Slice blob_slice(input_ref.data() + value_pos, vs);
669
0
      if (blob_slice.empty()) {
670
0
        return Status::Corruption("Empty blob index in wide column");
671
0
      }
672
0
      BlobIndex blob_idx;
673
0
      Status s = blob_idx.DecodeFrom(blob_slice);
674
0
      if (!s.ok()) {
675
0
        return Status::Corruption("Error decoding blob index in wide column");
676
0
      }
677
0
      Status cb_s = callback(blob_idx);
678
0
      if (!cb_s.ok()) {
679
0
        return cb_s;
680
0
      }
681
0
    }
682
0
    value_pos += vs;
683
0
  }
684
685
0
  return Status::OK();
686
0
}
687
688
Status WideColumnSerialization::GetVersion(const Slice& input,
689
0
                                           uint32_t& version) {
690
0
  Slice input_ref = input;
691
692
0
  version = 0;
693
0
  if (!GetVarint32(&input_ref, &version)) {
694
0
    return Status::Corruption("Error decoding wide column version");
695
0
  }
696
697
0
  return Status::OK();
698
0
}
699
700
Status WideColumnSerialization::GetValueOfDefaultColumn(
701
0
    const Slice& input, Slice& value, bool& is_blob_reference) {
702
0
  is_blob_reference = false;
703
704
0
  Slice input_ref = input;
705
706
0
  uint32_t version = 0;
707
0
  if (!GetVarint32(&input_ref, &version)) {
708
0
    return Status::Corruption("Error decoding wide column version");
709
0
  }
710
711
  // See Deserialize(): a too-new version is Corruption, not NotSupported.
712
0
  if (version > kVersion2) {
713
0
    return Status::Corruption("Corrupt or unsupported wide column version");
714
0
  }
715
716
0
  uint32_t num_columns = 0;
717
0
  if (!GetVarint32(&input_ref, &num_columns)) {
718
0
    return Status::Corruption("Error decoding number of wide columns");
719
0
  }
720
721
0
  if (!num_columns) {
722
0
    value.clear();
723
0
    return Status::OK();
724
0
  }
725
726
0
  if (version >= kVersion2) {
727
    // V2 fast path: use skip info to jump directly to values without
728
    // scanning through variable-length sections.
729
730
    // Read SKIP INFO (3 varints, immediately after header)
731
0
    uint32_t name_sizes_bytes = 0;
732
0
    uint32_t value_sizes_bytes = 0;
733
0
    uint32_t names_bytes = 0;
734
0
    if (!GetVarint32(&input_ref, &name_sizes_bytes)) {
735
0
      return Status::Corruption("Error decoding wide column name sizes bytes");
736
0
    }
737
0
    if (!GetVarint32(&input_ref, &value_sizes_bytes)) {
738
0
      return Status::Corruption("Error decoding wide column value sizes bytes");
739
0
    }
740
0
    if (!GetVarint32(&input_ref, &names_bytes)) {
741
0
      return Status::Corruption("Error decoding wide column names bytes");
742
0
    }
743
744
    // Read COLUMN TYPES (N bytes). We only need column 0's type here (it is the
745
    // default column's type only if column 0 is actually the default column,
746
    // checked via its name size below), but validate it is a recognized type so
747
    // a corrupt/unsupported type byte is reported as Corruption rather than
748
    // silently treated as an inline value.
749
0
    if (input_ref.size() < num_columns) {
750
0
      return Status::Corruption("Error decoding wide column types");
751
0
    }
752
0
    const auto column0_type = lossless_cast<ValueType>(input_ref[0]);
753
0
    if (!IsValidColumnValueType(column0_type)) {
754
0
      return Status::Corruption("Unsupported wide column ValueType");
755
0
    }
756
0
    const bool column0_is_blob = column0_type == kTypeBlobIndex;
757
0
    input_ref.remove_prefix(num_columns);
758
759
    // Peek first name size from NAME SIZES section
760
0
    if (input_ref.size() < name_sizes_bytes) {
761
0
      return Status::Corruption("Error decoding wide column name sizes");
762
0
    }
763
0
    Slice name_sizes_section(input_ref.data(), name_sizes_bytes);
764
0
    uint32_t first_name_size = 0;
765
0
    if (!GetVarint32(&name_sizes_section, &first_name_size)) {
766
0
      return Status::Corruption("Error decoding wide column name size");
767
0
    }
768
0
    input_ref.remove_prefix(name_sizes_bytes);
769
770
    // Peek first value size from VALUE SIZES section
771
0
    if (input_ref.size() < value_sizes_bytes) {
772
0
      return Status::Corruption("Error decoding wide column value sizes");
773
0
    }
774
0
    Slice value_sizes_section(input_ref.data(), value_sizes_bytes);
775
0
    uint32_t first_value_size = 0;
776
0
    if (!GetVarint32(&value_sizes_section, &first_value_size)) {
777
0
      return Status::Corruption("Error decoding wide column value size");
778
0
    }
779
    // Skip entire VALUE SIZES section using value_sizes_bytes
780
0
    input_ref.remove_prefix(value_sizes_bytes);
781
782
    // Check if the first column is the default column (empty name)
783
0
    if (first_name_size != 0) {
784
0
      value.clear();
785
0
      return Status::OK();
786
0
    }
787
788
    // Skip NAMES section
789
0
    if (input_ref.size() < names_bytes) {
790
0
      return Status::Corruption("Error decoding wide column names");
791
0
    }
792
0
    input_ref.remove_prefix(names_bytes);
793
794
    // Read the first value from VALUES section. For a blob-referenced default
795
    // column these are the raw serialized BlobIndex bytes.
796
0
    if (input_ref.size() < first_value_size) {
797
0
      return Status::Corruption("Error decoding wide column value payload");
798
0
    }
799
0
    value = Slice(input_ref.data(), first_value_size);
800
0
    is_blob_reference = column0_is_blob;
801
0
    return Status::OK();
802
0
  }
803
804
  // V1 fallback: full deserialization. V1 has no blob references.
805
0
  WideColumns columns;
806
807
0
  if (Status s = DeserializeSimple(input, columns); !s.ok()) {
808
0
    return s;
809
0
  }
810
811
0
  if (!WideColumnsHelper::HasDefaultColumn(columns)) {
812
0
    value.clear();
813
0
    return Status::OK();
814
0
  }
815
816
0
  value = WideColumnsHelper::GetDefaultColumn(columns);
817
818
0
  return Status::OK();
819
0
}
820
821
Status WideColumnSerialization::ResolveEntityBlobColumns(
822
    const Slice& entity_value, const Slice& user_key,
823
    const BlobFetcher* blob_fetcher, PrefetchBufferCollection* prefetch_buffers,
824
    std::string& resolved_entity, bool& resolved, uint64_t* total_bytes_read,
825
0
    uint64_t* num_blobs_resolved) {
826
  // Resolve into zero-copy columns backed by fetched blob buffers, then
827
  // serialize that result as a V1 (all-inline) entity.
828
0
  WideColumns resolved_columns;
829
0
  std::forward_list<PinnableSlice> extra_buffers;
830
0
  Status s = ResolveEntityBlobColumnsMultiBuffer(
831
0
      entity_value, user_key, blob_fetcher, prefetch_buffers, resolved_columns,
832
0
      extra_buffers, resolved, total_bytes_read, num_blobs_resolved);
833
0
  if (s.ok() && resolved) {
834
0
    s = Serialize(resolved_columns, resolved_entity);
835
0
  }
836
0
  return s;
837
0
}
838
839
Status WideColumnSerialization::ResolveEntityBlobColumnsMultiBuffer(
840
    const Slice& entity_value, const Slice& user_key,
841
    const BlobFetcher* blob_fetcher, PrefetchBufferCollection* prefetch_buffers,
842
    WideColumns& resolved_columns,
843
    std::forward_list<PinnableSlice>& extra_buffers, bool& resolved,
844
0
    uint64_t* total_bytes_read, uint64_t* num_blobs_resolved) {
845
0
  resolved = false;
846
847
0
  std::vector<WideColumn> columns;
848
0
  std::vector<std::pair<size_t, BlobIndex>> blob_columns;
849
850
0
  if (Status s = Deserialize(entity_value, columns, &blob_columns); !s.ok()) {
851
0
    return s;
852
0
  }
853
854
0
  if (blob_columns.empty()) {
855
0
    return Status::OK();
856
0
  }
857
858
0
  resolved = true;
859
860
0
  for (const auto& blob_col : blob_columns) {
861
0
    const size_t column_idx = blob_col.first;
862
0
    const BlobIndex& blob_idx = blob_col.second;
863
864
0
    if (blob_idx.IsInlined()) {
865
      // The inlined value bytes live in `entity_value`; point the column value
866
      // at them directly (zero copy). The caller keeps `entity_value` alive.
867
0
      columns[column_idx].value() = blob_idx.value();
868
0
      continue;
869
0
    }
870
871
0
    if (blob_fetcher == nullptr) {
872
      // A blob-backed entity reached a read context with no blob fetcher (a
873
      // reader without blob support, e.g. SstFileReader, or corrupt/legacy
874
      // data). This is the single place entity blob resolution reports that
875
      // condition; callers no longer pre-check the fetcher.
876
0
      return Status::Corruption(
877
0
          "Cannot resolve blob columns in entity without a blob fetcher");
878
0
    }
879
880
0
    FilePrefetchBuffer* prefetch_buffer =
881
0
        prefetch_buffers ? prefetch_buffers->GetOrCreatePrefetchBuffer(
882
0
                               blob_idx.file_number())
883
0
                         : nullptr;
884
885
0
    uint64_t bytes_read = 0;
886
887
    // Fetch the blob value into a fresh, address-stable backing node.
888
0
    extra_buffers.emplace_front();
889
0
    PinnableSlice& blob_value = extra_buffers.front();
890
0
    const Status fetch_s = blob_fetcher->FetchBlob(
891
0
        user_key, blob_idx, prefetch_buffer, &blob_value, &bytes_read);
892
0
    if (!fetch_s.ok()) {
893
0
      return fetch_s;
894
0
    }
895
896
    // Zero-copy Slice into the node we just added.
897
0
    columns[column_idx].value() = blob_value;
898
899
0
    if (total_bytes_read) {
900
0
      *total_bytes_read += bytes_read;
901
0
    }
902
0
  }
903
904
0
  if (num_blobs_resolved) {
905
0
    *num_blobs_resolved += blob_columns.size();
906
0
  }
907
908
0
  resolved_columns = std::move(columns);
909
910
0
  return Status::OK();
911
0
}
912
913
Status WideColumnSerialization::ResolveDefaultColumnBlobReference(
914
    const Slice& blob_index, const Slice& user_key,
915
0
    const BlobFetcher* blob_fetcher, PinnableSlice& value) {
916
0
  if (blob_index.empty()) {
917
0
    return Status::Corruption("Empty blob index in wide column default value");
918
0
  }
919
920
0
  BlobIndex blob_idx;
921
0
  if (Status s = blob_idx.DecodeFrom(blob_index); !s.ok()) {
922
0
    return Status::Corruption(
923
0
        "Error decoding blob index in wide column default value");
924
0
  }
925
926
0
  if (blob_idx.IsInlined()) {
927
0
    value.PinSelf(blob_idx.value());
928
0
    return Status::OK();
929
0
  }
930
931
0
  if (blob_fetcher == nullptr) {
932
    // A blob-backed default column reached a read context with no blob fetcher
933
    // (a reader without blob support, e.g. SstFileReader, or corrupt/legacy
934
    // data). This is the single place default-column blob resolution reports
935
    // that condition; callers no longer pre-check the fetcher.
936
0
    return Status::Corruption(
937
0
        "Cannot resolve blob-backed default column without a blob fetcher");
938
0
  }
939
940
0
  return blob_fetcher->FetchBlob(user_key, blob_idx,
941
0
                                 nullptr /* prefetch_buffer */, &value,
942
0
                                 nullptr /* bytes_read */);
943
0
}
944
945
Status WideColumnSerialization::ResolveEntityForMerge(
946
    const Slice& entity_value, const Slice& user_key,
947
    const BlobFetcher* blob_fetcher, PrefetchBufferCollection* prefetch_buffers,
948
0
    std::string& resolved_entity, Slice& effective_entity) {
949
0
  bool has_blob_columns = false;
950
0
  Status status = HasBlobColumns(entity_value, has_blob_columns);
951
0
  if (status.ok()) {
952
0
    if (!has_blob_columns) {
953
0
      effective_entity = entity_value;
954
0
    } else {
955
      // A null blob_fetcher with blob columns present is reported as Corruption
956
      // inside ResolveEntityBlobColumns -> ResolveEntityBlobColumnsMultiBuffer.
957
0
      bool resolved = false;
958
0
      status = ResolveEntityBlobColumns(
959
0
          entity_value, user_key, blob_fetcher, prefetch_buffers,
960
0
          resolved_entity, resolved, nullptr /* total_bytes_read */,
961
0
          nullptr /* num_blobs_resolved */);
962
0
      if (status.ok()) {
963
0
        effective_entity = resolved ? Slice(resolved_entity) : entity_value;
964
0
      }
965
0
    }
966
0
  }
967
0
  return status;
968
0
}
969
970
}  // namespace ROCKSDB_NAMESPACE