/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 |