Skip to content

Commit 0919f96

Browse files
refactor(serde): move DataFile JSON handling from REST to core (#974)
1 parent 44e29e1 commit 0919f96

6 files changed

Lines changed: 535 additions & 484 deletions

File tree

‎src/iceberg/catalog/rest/json_serde.cc‎

Lines changed: 8 additions & 273 deletions
Original file line numberDiff line numberDiff line change
@@ -105,35 +105,9 @@ constexpr std::string_view kEndSnapshotId = "end-snapshot-id";
105105
constexpr std::string_view kStatsFields = "stats-fields";
106106
constexpr std::string_view kMinRowsRequested = "min-rows-requested";
107107
constexpr std::string_view kPlanTask = "plan-task";
108-
constexpr std::string_view kContent = "content";
109-
constexpr std::string_view kContentData = "data";
110-
constexpr std::string_view kContentPositionDeletes = "position-deletes";
111-
constexpr std::string_view kContentEqualityDeletes = "equality-deletes";
112-
constexpr std::string_view kFilePath = "file-path";
113-
constexpr std::string_view kFileFormat = "file-format";
114-
constexpr std::string_view kSpecId = "spec-id";
115-
constexpr std::string_view kPartition = "partition";
116-
constexpr std::string_view kRecordCount = "record-count";
117-
constexpr std::string_view kFileSizeInBytes = "file-size-in-bytes";
118-
constexpr std::string_view kColumnSizes = "column-sizes";
119-
constexpr std::string_view kValueCounts = "value-counts";
120-
constexpr std::string_view kNullValueCounts = "null-value-counts";
121-
constexpr std::string_view kNanValueCounts = "nan-value-counts";
122-
constexpr std::string_view kLowerBounds = "lower-bounds";
123-
constexpr std::string_view kUpperBounds = "upper-bounds";
124-
constexpr std::string_view kKeyMetadata = "key-metadata";
125-
constexpr std::string_view kSplitOffsets = "split-offsets";
126-
constexpr std::string_view kEqualityIds = "equality-ids";
127-
constexpr std::string_view kSortOrderId = "sort-order-id";
128-
constexpr std::string_view kFirstRowId = "first-row-id";
129-
constexpr std::string_view kReferencedDataFile = "referenced-data-file";
130-
constexpr std::string_view kContentOffset = "content-offset";
131-
constexpr std::string_view kContentSizeInBytes = "content-size-in-bytes";
132108
constexpr std::string_view kDataFile = "data-file";
133109
constexpr std::string_view kDeleteFileReferences = "delete-file-references";
134110
constexpr std::string_view kResidualFilter = "residual-filter";
135-
constexpr std::string_view kMapKeys = "keys";
136-
constexpr std::string_view kMapValues = "values";
137111

138112
Result<nlohmann::json> StorageCredentialToJson(const StorageCredential& credential) {
139113
ICEBERG_RETURN_UNEXPECTED(credential.Validate());
@@ -170,157 +144,8 @@ Result<std::vector<StorageCredential>> StorageCredentialsFromJson(
170144
}
171145
return credentials;
172146
}
173-
174-
template <typename Value>
175-
Result<std::map<int32_t, Value>> KeyValueMapFromJson(const nlohmann::json& json,
176-
std::string_view key) {
177-
std::map<int32_t, Value> result;
178-
if (!json.contains(key) || json.at(key).is_null()) {
179-
return result;
180-
}
181-
182-
ICEBERG_ASSIGN_OR_RAISE(auto map_json, GetJsonValue<nlohmann::json>(json, key));
183-
ICEBERG_ASSIGN_OR_RAISE(auto keys,
184-
GetJsonValue<std::vector<int32_t>>(map_json, kMapKeys));
185-
ICEBERG_ASSIGN_OR_RAISE(auto values,
186-
GetJsonValue<std::vector<Value>>(map_json, kMapValues));
187-
if (keys.size() != values.size()) {
188-
return JsonParseError("'{}' map keys and values have different lengths", key);
189-
}
190-
191-
for (size_t i = 0; i < keys.size(); ++i) {
192-
result[keys[i]] = std::move(values[i]);
193-
}
194-
return result;
195-
}
196-
197-
template <typename Value>
198-
void SetKeyValueMap(nlohmann::json& json, std::string_view key,
199-
const std::map<int32_t, Value>& map) {
200-
if (map.empty()) {
201-
return;
202-
}
203-
204-
std::vector<int32_t> keys;
205-
std::vector<Value> values;
206-
keys.reserve(map.size());
207-
values.reserve(map.size());
208-
for (const auto& [field_id, value] : map) {
209-
keys.push_back(field_id);
210-
values.push_back(value);
211-
}
212-
json[key] = {{kMapKeys, std::move(keys)}, {kMapValues, std::move(values)}};
213-
}
214-
215147
} // namespace
216148

217-
Result<DataFile> DataFileFromJson(
218-
const nlohmann::json& json,
219-
const std::unordered_map<int32_t, std::shared_ptr<PartitionSpec>>&
220-
partition_spec_by_id,
221-
const Schema& schema) {
222-
if (!json.is_object()) {
223-
return JsonParseError("DataFile must be a JSON object: {}", SafeDumpJson(json));
224-
}
225-
DataFile data_file;
226-
227-
ICEBERG_ASSIGN_OR_RAISE(auto content_str, GetJsonValue<std::string>(json, kContent));
228-
if (content_str == kContentData) {
229-
data_file.content = DataFile::Content::kData;
230-
} else if (content_str == kContentPositionDeletes) {
231-
data_file.content = DataFile::Content::kPositionDeletes;
232-
} else if (content_str == kContentEqualityDeletes) {
233-
data_file.content = DataFile::Content::kEqualityDeletes;
234-
} else {
235-
return JsonParseError("Unknown data file content: {}", content_str);
236-
}
237-
238-
ICEBERG_ASSIGN_OR_RAISE(data_file.file_path,
239-
GetJsonValue<std::string>(json, kFilePath));
240-
ICEBERG_ASSIGN_OR_RAISE(auto format_str, GetJsonValue<std::string>(json, kFileFormat));
241-
ICEBERG_ASSIGN_OR_RAISE(data_file.file_format, FileFormatTypeFromString(format_str));
242-
243-
ICEBERG_ASSIGN_OR_RAISE(auto spec_id, GetJsonValue<int32_t>(json, kSpecId));
244-
data_file.partition_spec_id = spec_id;
245-
246-
ICEBERG_ASSIGN_OR_RAISE(auto partition_vals,
247-
GetJsonValue<nlohmann::json>(json, kPartition));
248-
if (!partition_vals.is_array()) {
249-
return JsonParseError("PartitionValues must be a JSON array: {}",
250-
SafeDumpJson(partition_vals));
251-
}
252-
std::vector<Literal> literals;
253-
auto it = partition_spec_by_id.find(spec_id);
254-
if (it == partition_spec_by_id.end()) {
255-
return JsonParseError("Invalid partition spec id: {}", spec_id);
256-
}
257-
ICEBERG_ASSIGN_OR_RAISE(auto struct_type, it->second->PartitionType(schema));
258-
auto fields = struct_type->fields();
259-
if (partition_vals.size() != fields.size()) {
260-
return JsonParseError("Invalid partition data size: expected = {}, actual = {}",
261-
fields.size(), partition_vals.size());
262-
}
263-
for (size_t pos = 0; pos < fields.size(); ++pos) {
264-
ICEBERG_ASSIGN_OR_RAISE(
265-
auto literal, LiteralFromJson(partition_vals[pos], fields[pos].type().get()));
266-
literals.push_back(std::move(literal));
267-
}
268-
data_file.partition = PartitionValues(std::move(literals));
269-
270-
ICEBERG_ASSIGN_OR_RAISE(data_file.record_count,
271-
GetJsonValue<int64_t>(json, kRecordCount));
272-
ICEBERG_ASSIGN_OR_RAISE(data_file.file_size_in_bytes,
273-
GetJsonValue<int64_t>(json, kFileSizeInBytes));
274-
275-
ICEBERG_ASSIGN_OR_RAISE(data_file.column_sizes,
276-
KeyValueMapFromJson<int64_t>(json, kColumnSizes));
277-
ICEBERG_ASSIGN_OR_RAISE(data_file.value_counts,
278-
KeyValueMapFromJson<int64_t>(json, kValueCounts));
279-
ICEBERG_ASSIGN_OR_RAISE(data_file.null_value_counts,
280-
KeyValueMapFromJson<int64_t>(json, kNullValueCounts));
281-
ICEBERG_ASSIGN_OR_RAISE(data_file.nan_value_counts,
282-
KeyValueMapFromJson<int64_t>(json, kNanValueCounts));
283-
ICEBERG_ASSIGN_OR_RAISE(data_file.lower_bounds,
284-
KeyValueMapFromJson<std::vector<uint8_t>>(json, kLowerBounds));
285-
ICEBERG_ASSIGN_OR_RAISE(data_file.upper_bounds,
286-
KeyValueMapFromJson<std::vector<uint8_t>>(json, kUpperBounds));
287-
288-
if (json.contains(kKeyMetadata) && !json.at(kKeyMetadata).is_null()) {
289-
ICEBERG_ASSIGN_OR_RAISE(data_file.key_metadata,
290-
GetJsonValue<std::vector<uint8_t>>(json, kKeyMetadata));
291-
}
292-
if (json.contains(kSplitOffsets) && !json.at(kSplitOffsets).is_null()) {
293-
ICEBERG_ASSIGN_OR_RAISE(data_file.split_offsets,
294-
GetJsonValue<std::vector<int64_t>>(json, kSplitOffsets));
295-
}
296-
if (json.contains(kEqualityIds) && !json.at(kEqualityIds).is_null()) {
297-
ICEBERG_ASSIGN_OR_RAISE(data_file.equality_ids,
298-
GetJsonValue<std::vector<int32_t>>(json, kEqualityIds));
299-
}
300-
if (json.contains(kSortOrderId) && !json.at(kSortOrderId).is_null()) {
301-
ICEBERG_ASSIGN_OR_RAISE(data_file.sort_order_id,
302-
GetJsonValue<int32_t>(json, kSortOrderId));
303-
}
304-
if (json.contains(kFirstRowId) && !json.at(kFirstRowId).is_null()) {
305-
ICEBERG_ASSIGN_OR_RAISE(data_file.first_row_id,
306-
GetJsonValue<int64_t>(json, kFirstRowId));
307-
}
308-
if (json.contains(kReferencedDataFile) && !json.at(kReferencedDataFile).is_null()) {
309-
ICEBERG_ASSIGN_OR_RAISE(data_file.referenced_data_file,
310-
GetJsonValue<std::string>(json, kReferencedDataFile));
311-
}
312-
if (json.contains(kContentOffset) && !json.at(kContentOffset).is_null()) {
313-
ICEBERG_ASSIGN_OR_RAISE(data_file.content_offset,
314-
GetJsonValue<int64_t>(json, kContentOffset));
315-
}
316-
if (json.contains(kContentSizeInBytes) && !json.at(kContentSizeInBytes).is_null()) {
317-
ICEBERG_ASSIGN_OR_RAISE(data_file.content_size_in_bytes,
318-
GetJsonValue<int64_t>(json, kContentSizeInBytes));
319-
}
320-
321-
return data_file;
322-
}
323-
324149
Result<std::vector<std::shared_ptr<FileScanTask>>> FileScanTasksFromJson(
325150
const nlohmann::json& json,
326151
const std::vector<std::shared_ptr<DataFile>>& delete_files,
@@ -341,7 +166,8 @@ Result<std::vector<std::shared_ptr<FileScanTask>>> FileScanTasksFromJson(
341166
ICEBERG_ASSIGN_OR_RAISE(auto data_file_json,
342167
GetJsonValue<nlohmann::json>(task_json, kDataFile));
343168
ICEBERG_ASSIGN_OR_RAISE(
344-
auto data_file, DataFileFromJson(data_file_json, partition_spec_by_id, schema));
169+
auto data_file,
170+
iceberg::DataFileFromJson(data_file_json, partition_spec_by_id, schema));
345171
// FIXME: REST scan-task DataFile JSON currently carries first-row-id,
346172
// but not the manifest-entry data sequence number. Until the REST API exposes
347173
// it, REST-planned tasks cannot inherit _last_updated_sequence_number.
@@ -376,99 +202,6 @@ Result<std::vector<std::shared_ptr<FileScanTask>>> FileScanTasksFromJson(
376202
return file_scan_tasks;
377203
}
378204

379-
Result<nlohmann::json> DataFileToJsonUnchecked(const DataFile& data_file) {
380-
nlohmann::json json;
381-
switch (data_file.content) {
382-
case DataFile::Content::kData:
383-
json[kContent] = kContentData;
384-
break;
385-
case DataFile::Content::kPositionDeletes:
386-
json[kContent] = kContentPositionDeletes;
387-
break;
388-
case DataFile::Content::kEqualityDeletes:
389-
json[kContent] = kContentEqualityDeletes;
390-
break;
391-
}
392-
json[kFilePath] = data_file.file_path;
393-
json[kFileFormat] = ToString(data_file.file_format);
394-
395-
if (!data_file.partition_spec_id.has_value()) {
396-
return ValidationFailed("Cannot serialize REST content file without 'spec-id'");
397-
}
398-
json[kSpecId] = data_file.partition_spec_id.value();
399-
400-
nlohmann::json partition_json = nlohmann::json::array();
401-
for (const auto& literal : data_file.partition.values()) {
402-
ICEBERG_ASSIGN_OR_RAISE(auto lit_json, iceberg::ToJson(literal));
403-
partition_json.push_back(std::move(lit_json));
404-
}
405-
json[kPartition] = std::move(partition_json);
406-
407-
json[kRecordCount] = data_file.record_count;
408-
json[kFileSizeInBytes] = data_file.file_size_in_bytes;
409-
410-
SetKeyValueMap(json, kColumnSizes, data_file.column_sizes);
411-
SetKeyValueMap(json, kValueCounts, data_file.value_counts);
412-
SetKeyValueMap(json, kNullValueCounts, data_file.null_value_counts);
413-
SetKeyValueMap(json, kNanValueCounts, data_file.nan_value_counts);
414-
SetKeyValueMap(json, kLowerBounds, data_file.lower_bounds);
415-
SetKeyValueMap(json, kUpperBounds, data_file.upper_bounds);
416-
417-
if (!data_file.key_metadata.empty()) {
418-
json[kKeyMetadata] = data_file.key_metadata;
419-
}
420-
if (!data_file.split_offsets.empty()) {
421-
json[kSplitOffsets] = data_file.split_offsets;
422-
}
423-
if (!data_file.equality_ids.empty()) {
424-
json[kEqualityIds] = data_file.equality_ids;
425-
}
426-
if (data_file.sort_order_id.has_value()) {
427-
json[kSortOrderId] = data_file.sort_order_id.value();
428-
}
429-
if (data_file.first_row_id.has_value()) {
430-
json[kFirstRowId] = data_file.first_row_id.value();
431-
}
432-
if (data_file.referenced_data_file.has_value()) {
433-
json[kReferencedDataFile] = data_file.referenced_data_file.value();
434-
}
435-
if (data_file.content_offset.has_value()) {
436-
json[kContentOffset] = data_file.content_offset.value();
437-
}
438-
if (data_file.content_size_in_bytes.has_value()) {
439-
json[kContentSizeInBytes] = data_file.content_size_in_bytes.value();
440-
}
441-
442-
return json;
443-
}
444-
445-
Result<nlohmann::json> ToJson(
446-
const DataFile& data_file,
447-
const std::unordered_map<int32_t, std::shared_ptr<PartitionSpec>>&
448-
partition_specs_by_id,
449-
const Schema& schema) {
450-
if (!data_file.partition_spec_id.has_value()) {
451-
return ValidationFailed("Invalid partition spec id from content file: null");
452-
}
453-
auto it = partition_specs_by_id.find(data_file.partition_spec_id.value());
454-
if (it == partition_specs_by_id.end() || !it->second) {
455-
return ValidationFailed("Invalid partition spec: null");
456-
}
457-
if (data_file.partition_spec_id.value() != it->second->spec_id()) {
458-
return ValidationFailed(
459-
"Invalid partition spec id from content file: expected = {}, actual = {}",
460-
it->second->spec_id(), data_file.partition_spec_id.value());
461-
}
462-
ICEBERG_ASSIGN_OR_RAISE(auto partition_type, it->second->PartitionType(schema));
463-
if (data_file.partition.num_fields() != partition_type->fields().size()) {
464-
return ValidationFailed(
465-
"Invalid partition data from content file: expected = {}, actual = {}",
466-
partition_type->fields().empty() ? "unpartitioned" : "partitioned",
467-
data_file.partition.num_fields() == 0 ? "unpartitioned" : "partitioned");
468-
}
469-
return DataFileToJsonUnchecked(data_file);
470-
}
471-
472205
namespace {
473206

474207
template <typename Response>
@@ -514,7 +247,7 @@ Result<nlohmann::json> ScanTaskFieldsToJson(
514247
if (task->data_file()) {
515248
ICEBERG_ASSIGN_OR_RAISE(
516249
auto data_file_json,
517-
ToJson(*task->data_file(), partition_specs_by_id, schema));
250+
iceberg::ToJson(*task->data_file(), partition_specs_by_id, schema));
518251
task_json[kDataFile] = std::move(data_file_json);
519252
}
520253
if (!task->delete_files().empty()) {
@@ -540,7 +273,8 @@ Result<nlohmann::json> ScanTaskFieldsToJson(
540273
}
541274
nlohmann::json delete_files_json = nlohmann::json::array();
542275
for (const auto& file : delete_files) {
543-
ICEBERG_ASSIGN_OR_RAISE(auto df_json, ToJson(*file, partition_specs_by_id, schema));
276+
ICEBERG_ASSIGN_OR_RAISE(auto df_json,
277+
iceberg::ToJson(*file, partition_specs_by_id, schema));
544278
delete_files_json.push_back(std::move(df_json));
545279
}
546280
if (!delete_files_json.empty()) {
@@ -576,8 +310,9 @@ Status ScanTaskFieldsFromJson(
576310
SafeDumpJson(delete_files_json));
577311
}
578312
for (const auto& entry_json : delete_files_json) {
579-
ICEBERG_ASSIGN_OR_RAISE(auto delete_file,
580-
DataFileFromJson(entry_json, partition_specs_by_id, schema));
313+
ICEBERG_ASSIGN_OR_RAISE(
314+
auto delete_file,
315+
iceberg::DataFileFromJson(entry_json, partition_specs_by_id, schema));
581316
response.delete_files.push_back(std::make_shared<DataFile>(std::move(delete_file)));
582317
}
583318

‎src/iceberg/catalog/rest/json_serde_internal.h‎

Lines changed: 0 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -139,18 +139,6 @@ template <>
139139
ICEBERG_REST_EXPORT Result<FetchScanTasksRequest> FromJson(const nlohmann::json& json);
140140
ICEBERG_REST_EXPORT nlohmann::json ToJson(const FetchScanTasksRequest& request);
141141

142-
ICEBERG_REST_EXPORT Result<nlohmann::json> ToJson(
143-
const DataFile& df,
144-
const std::unordered_map<int32_t, std::shared_ptr<PartitionSpec>>&
145-
partition_specs_by_id,
146-
const Schema& schema);
147-
148-
ICEBERG_REST_EXPORT Result<DataFile> DataFileFromJson(
149-
const nlohmann::json& json,
150-
const std::unordered_map<int32_t, std::shared_ptr<PartitionSpec>>&
151-
partition_spec_by_id,
152-
const Schema& schema);
153-
154142
ICEBERG_REST_EXPORT Result<std::vector<std::shared_ptr<FileScanTask>>>
155143
FileScanTasksFromJson(const nlohmann::json& json,
156144
const std::vector<std::shared_ptr<DataFile>>& delete_files,

0 commit comments

Comments
 (0)