Skip to content

Commit 72c8a23

Browse files
committed
fix(serde): address FileScanTask JSON review feedback
- reuse core strict integer parsing (GetJsonIntegerOrDefault) in the REST scan-task reader instead of a local copy - omit 'delete-files' when the task has no delete files, matching Java - look up the partition spec explicitly in ToJson(FileScanTask) instead of relying on std::map::at() after an earlier validation
1 parent 7bc87fb commit 72c8a23

4 files changed

Lines changed: 31 additions & 25 deletions

File tree

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

Lines changed: 2 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818
*/
1919

2020
#include <iterator>
21-
#include <limits>
2221
#include <map>
2322
#include <memory>
2423
#include <optional>
@@ -112,23 +111,6 @@ constexpr std::string_view kResidualFilter = "residual-filter";
112111
constexpr std::string_view kStart = "start";
113112
constexpr std::string_view kLength = "length";
114113

115-
Result<int64_t> GetRangeValueOrDefault(const nlohmann::json& json, std::string_view key,
116-
int64_t default_value) {
117-
if (!json.contains(key) || json.at(key).is_null()) {
118-
return default_value;
119-
}
120-
const auto& value = json.at(key);
121-
if (!value.is_number_integer()) {
122-
return JsonParseError("'{}' must be an integer, but is {}", key, value.type_name());
123-
}
124-
if (value.is_number_unsigned() &&
125-
value.get<uint64_t>() >
126-
static_cast<uint64_t>(std::numeric_limits<int64_t>::max())) {
127-
return JsonParseError("'{}' integer is out of range: {}", key, SafeDumpJson(value));
128-
}
129-
return value.get<int64_t>();
130-
}
131-
132114
Result<nlohmann::json> StorageCredentialToJson(const StorageCredential& credential) {
133115
ICEBERG_RETURN_UNEXPECTED(credential.Validate());
134116
nlohmann::json json;
@@ -188,10 +170,10 @@ Result<std::vector<std::shared_ptr<FileScanTask>>> FileScanTasksFromJson(
188170
ICEBERG_ASSIGN_OR_RAISE(
189171
auto data_file,
190172
iceberg::DataFileFromJson(data_file_json, partition_spec_by_id, schema));
191-
ICEBERG_ASSIGN_OR_RAISE(auto start, GetRangeValueOrDefault(task_json, kStart, 0));
173+
ICEBERG_ASSIGN_OR_RAISE(auto start, GetJsonIntegerOrDefault(task_json, kStart, 0));
192174
ICEBERG_ASSIGN_OR_RAISE(
193175
auto length,
194-
GetRangeValueOrDefault(task_json, kLength, data_file.file_size_in_bytes));
176+
GetJsonIntegerOrDefault(task_json, kLength, data_file.file_size_in_bytes));
195177
ICEBERG_RETURN_UNEXPECTED(
196178
CheckFileScanTaskNotSplit(start, length, data_file.file_size_in_bytes));
197179
// FIXME: REST scan-task DataFile JSON currently carries first-row-id,

‎src/iceberg/json_serde.cc‎

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,14 @@ nlohmann::json ToJson(const SortField& sort_field) {
574574
return json;
575575
}
576576

577+
Result<int64_t> GetJsonIntegerOrDefault(const nlohmann::json& json, std::string_view key,
578+
int64_t default_value) {
579+
if (!json.contains(key) || json.at(key).is_null()) {
580+
return default_value;
581+
}
582+
return IntegerFromJson<int64_t>(json.at(key), std::format("'{}'", key));
583+
}
584+
577585
nlohmann::json ToJson(const SortOrder& sort_order) {
578586
nlohmann::json json;
579587
json[kOrderId] = sort_order.order_id();
@@ -2466,7 +2474,11 @@ Result<nlohmann::json> ToJson(
24662474
ICEBERG_ASSIGN_OR_RAISE(auto data_file_json,
24672475
ToJson(*task.data_file(), partition_specs_by_id, schema));
24682476
const auto spec_id = task.data_file()->partition_spec_id.value();
2469-
json[kSpec] = ToJson(*partition_specs_by_id.at(spec_id));
2477+
const auto spec_it = partition_specs_by_id.find(spec_id);
2478+
if (spec_it == partition_specs_by_id.end() || !spec_it->second) {
2479+
return ValidationFailed("Invalid partition spec: null");
2480+
}
2481+
json[kSpec] = ToJson(*spec_it->second);
24702482
json[kDataFile] = std::move(data_file_json);
24712483
json[kStart] = 0;
24722484
json[kLength] = task.data_file()->file_size_in_bytes;
@@ -2491,7 +2503,9 @@ Result<nlohmann::json> ToJson(
24912503
ToJson(*delete_file, partition_specs_by_id, schema));
24922504
delete_files_json.push_back(std::move(delete_file_json));
24932505
}
2494-
json[kDeleteFiles] = std::move(delete_files_json);
2506+
if (!delete_files_json.empty()) {
2507+
json[kDeleteFiles] = std::move(delete_files_json);
2508+
}
24952509

24962510
if (task.residual_filter()) {
24972511
ICEBERG_ASSIGN_OR_RAISE(auto residual_json, ToJson(*task.residual_filter()));

‎src/iceberg/json_serde_internal.h‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -445,6 +445,15 @@ ICEBERG_EXPORT Result<DataFile> DataFileFromJson(
445445
partition_spec_by_id,
446446
const Schema& schema);
447447

448+
/// \brief Parses an optional signed integer field, returning `default_value` when the
449+
/// key is missing or null.
450+
///
451+
/// The value must be a JSON integer that fits `int64_t`, with the same strictness as
452+
/// the integer parsing used by `DataFile`/`FileScanTask`.
453+
ICEBERG_EXPORT Result<int64_t> GetJsonIntegerOrDefault(const nlohmann::json& json,
454+
std::string_view key,
455+
int64_t default_value);
456+
448457
/// \brief Serializes a `FileScanTask` to a self-contained JSON object.
449458
///
450459
/// Unlike REST scan responses, delete files are inlined under `delete-files` rather
@@ -457,7 +466,8 @@ ICEBERG_EXPORT Result<DataFile> DataFileFromJson(
457466
/// - `data-file` (required): ContentFile JSON
458467
/// - `start` (required): always 0 because split tasks are unsupported
459468
/// - `length` (required): always the data file size
460-
/// - `delete-files` (optional on input; always emitted): array of ContentFile JSON
469+
/// - `delete-files` (optional on input; omitted when the task has no delete files):
470+
/// array of ContentFile JSON
461471
/// - `residual-filter` (optional): Expression JSON
462472
ICEBERG_EXPORT Result<nlohmann::json> ToJson(
463473
const FileScanTask& task,

‎src/iceberg/test/json_serde_test.cc‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1628,11 +1628,11 @@ TEST_F(FileScanTaskJsonTest, ParsesJavaGoldenWithoutExternalSchemaOrSpec) {
16281628
EXPECT_EQ(residual, true);
16291629
}
16301630

1631-
TEST_F(FileScanTaskJsonTest, SerializesEmptyDeleteFilesArray) {
1631+
TEST_F(FileScanTaskJsonTest, OmitsEmptyDeleteFilesLikeJava) {
16321632
FileScanTask task(std::make_shared<DataFile>(DataFileForTask()));
16331633

16341634
ICEBERG_UNWRAP_OR_FAIL(auto json, ToJson(task, specs_, schema_));
1635-
EXPECT_EQ(json["delete-files"], nlohmann::json::array());
1635+
EXPECT_FALSE(json.contains("delete-files"));
16361636
EXPECT_EQ(json["start"], 0);
16371637
EXPECT_EQ(json["length"], 10);
16381638
}

0 commit comments

Comments
 (0)