1818 */
1919
2020#include < algorithm>
21+ #include < concepts>
2122#include < cstdint>
2223#include < format>
24+ #include < limits>
2325#include < map>
2426#include < regex>
27+ #include < string>
2528#include < unordered_set>
2629#include < utility>
2730
@@ -269,7 +272,58 @@ constexpr std::string_view kContentSizeInBytes = "content-size-in-bytes";
269272constexpr std::string_view kMapKeys = " keys" ;
270273constexpr std::string_view kMapValues = " values" ;
271274
272- template <typename Value>
275+ template <std::signed_integral Integer>
276+ Result<Integer> IntegerFromJson (const nlohmann::json& json,
277+ std::string_view description) {
278+ if (!json.is_number_integer ()) {
279+ return JsonParseError (" {} must be an integer, but is {}" , description,
280+ json.type_name ());
281+ }
282+ if (json.is_number_unsigned ()) {
283+ const auto value = json.get <uint64_t >();
284+ if (value > static_cast <uint64_t >(std::numeric_limits<Integer>::max ())) {
285+ return JsonParseError (" {} integer is out of range: {}" , description,
286+ SafeDumpJson (json));
287+ }
288+ return static_cast <Integer>(value);
289+ }
290+
291+ const auto value = json.get <int64_t >();
292+ if (value < static_cast <int64_t >(std::numeric_limits<Integer>::min ()) ||
293+ value > static_cast <int64_t >(std::numeric_limits<Integer>::max ())) {
294+ return JsonParseError (" {} integer is out of range: {}" , description,
295+ SafeDumpJson (json));
296+ }
297+ return static_cast <Integer>(value);
298+ }
299+
300+ template <std::signed_integral Integer>
301+ Result<Integer> GetJsonInteger (const nlohmann::json& json, std::string_view key) {
302+ ICEBERG_ASSIGN_OR_RAISE (auto value, GetJsonValue<nlohmann::json>(json, key));
303+ return IntegerFromJson<Integer>(value, std::format (" '{}'" , key));
304+ }
305+
306+ template <std::signed_integral Integer>
307+ Result<std::vector<Integer>> IntegerVectorFromJson (const nlohmann::json& json,
308+ std::string_view key) {
309+ ICEBERG_ASSIGN_OR_RAISE (auto values_json, GetJsonValue<nlohmann::json>(json, key));
310+ if (!values_json.is_array ()) {
311+ return JsonParseError (" '{}' must be an array, but is {}" , key,
312+ values_json.type_name ());
313+ }
314+
315+ std::vector<Integer> values;
316+ values.reserve (values_json.size ());
317+ for (const auto & value_json : values_json) {
318+ ICEBERG_ASSIGN_OR_RAISE (
319+ auto value,
320+ IntegerFromJson<Integer>(value_json, std::format (" '{}' element" , key)));
321+ values.push_back (value);
322+ }
323+ return values;
324+ }
325+
326+ template <std::signed_integral Value>
273327Result<std::map<int32_t , Value>> KeyValueMapFromJson (const nlohmann::json& json,
274328 std::string_view key) {
275329 std::map<int32_t , Value> result;
@@ -278,16 +332,27 @@ Result<std::map<int32_t, Value>> KeyValueMapFromJson(const nlohmann::json& json,
278332 }
279333
280334 ICEBERG_ASSIGN_OR_RAISE (auto map_json, GetJsonValue<nlohmann::json>(json, key));
281- ICEBERG_ASSIGN_OR_RAISE (auto keys,
282- GetJsonValue<std::vector<int32_t >>(map_json, kMapKeys ));
283- ICEBERG_ASSIGN_OR_RAISE (auto values,
284- GetJsonValue<std::vector<Value>>(map_json, kMapValues ));
285- if (keys.size () != values.size ()) {
335+ ICEBERG_ASSIGN_OR_RAISE (auto keys_json,
336+ GetJsonValue<nlohmann::json>(map_json, kMapKeys ));
337+ ICEBERG_ASSIGN_OR_RAISE (auto values_json,
338+ GetJsonValue<nlohmann::json>(map_json, kMapValues ));
339+ if (!keys_json.is_array () || !values_json.is_array ()) {
340+ return JsonParseError (" '{}' map keys and values must be arrays" , key);
341+ }
342+ if (keys_json.size () != values_json.size ()) {
286343 return JsonParseError (" '{}' map keys and values have different lengths" , key);
287344 }
288345
289- for (size_t i = 0 ; i < keys.size (); ++i) {
290- result[keys[i]] = std::move (values[i]);
346+ for (size_t i = 0 ; i < keys_json.size (); ++i) {
347+ ICEBERG_ASSIGN_OR_RAISE (
348+ auto field_id,
349+ IntegerFromJson<int32_t >(keys_json[i], std::format (" '{}' map key" , key)));
350+ ICEBERG_ASSIGN_OR_RAISE (
351+ auto value,
352+ IntegerFromJson<Value>(values_json[i], std::format (" '{}' map value" , key)));
353+ if (!result.emplace (field_id, value).second ) {
354+ return JsonParseError (" '{}' map contains duplicate key {}" , key, field_id);
355+ }
291356 }
292357 return result;
293358}
@@ -310,6 +375,118 @@ void SetKeyValueMap(nlohmann::json& json, std::string_view key,
310375 json[key] = {{kMapKeys , std::move (keys)}, {kMapValues , std::move (values)}};
311376}
312377
378+ std::string BytesToHex (const std::vector<uint8_t >& bytes) {
379+ std::string hex;
380+ hex.reserve (bytes.size () * 2 );
381+ for (uint8_t byte : bytes) {
382+ hex += std::format (" {:02X}" , byte);
383+ }
384+ return hex;
385+ }
386+
387+ Result<std::map<int32_t , std::vector<uint8_t >>> BytesMapFromJson (
388+ const nlohmann::json& json, std::string_view key) {
389+ std::map<int32_t , std::vector<uint8_t >> result;
390+ if (!json.contains (key) || json.at (key).is_null ()) {
391+ return result;
392+ }
393+
394+ ICEBERG_ASSIGN_OR_RAISE (auto map_json, GetJsonValue<nlohmann::json>(json, key));
395+ ICEBERG_ASSIGN_OR_RAISE (auto keys_json,
396+ GetJsonValue<nlohmann::json>(map_json, kMapKeys ));
397+ ICEBERG_ASSIGN_OR_RAISE (auto values_json,
398+ GetJsonValue<nlohmann::json>(map_json, kMapValues ));
399+ if (!keys_json.is_array () || !values_json.is_array ()) {
400+ return JsonParseError (" '{}' map keys and values must be arrays" , key);
401+ }
402+ if (keys_json.size () != values_json.size ()) {
403+ return JsonParseError (" '{}' map keys and values have different lengths" , key);
404+ }
405+
406+ for (size_t i = 0 ; i < keys_json.size (); ++i) {
407+ ICEBERG_ASSIGN_OR_RAISE (
408+ auto field_id,
409+ IntegerFromJson<int32_t >(keys_json[i], std::format (" '{}' map key" , key)));
410+ if (!values_json[i].is_string ()) {
411+ return JsonParseError (" '{}' map value must be a string, but is {}" , key,
412+ values_json[i].type_name ());
413+ }
414+ ICEBERG_ASSIGN_OR_RAISE (
415+ auto bytes, StringUtils::HexStringToBytes (values_json[i].get <std::string>()));
416+ if (!result.emplace (field_id, std::move (bytes)).second ) {
417+ return JsonParseError (" '{}' map contains duplicate key {}" , key, field_id);
418+ }
419+ }
420+ return result;
421+ }
422+
423+ void SetBytesMap (nlohmann::json& json, std::string_view key,
424+ const std::map<int32_t , std::vector<uint8_t >>& map) {
425+ if (map.empty ()) {
426+ return ;
427+ }
428+
429+ std::vector<int32_t > keys;
430+ std::vector<std::string> values;
431+ keys.reserve (map.size ());
432+ values.reserve (map.size ());
433+ for (const auto & [field_id, value] : map) {
434+ keys.push_back (field_id);
435+ values.push_back (BytesToHex (value));
436+ }
437+ json[key] = {{kMapKeys , std::move (keys)}, {kMapValues , std::move (values)}};
438+ }
439+
440+ Result<Literal> PartitionLiteralFromJson (const nlohmann::json* value,
441+ const SchemaField& field) {
442+ if (!field.type () || !field.type ()->is_primitive ()) {
443+ return InvalidSchema (" Partition field {} must have a primitive type" ,
444+ field.field_id ());
445+ }
446+
447+ auto primitive_type = internal::checked_pointer_cast<PrimitiveType>(field.type ());
448+ if (value == nullptr || value->is_null ()) {
449+ return Literal::Null (std::move (primitive_type));
450+ }
451+ return LiteralFromJson (*value, primitive_type.get ());
452+ }
453+
454+ Result<PartitionValues> PartitionValuesFromJson (const nlohmann::json& json,
455+ const StructType& partition_type) {
456+ const auto & fields = partition_type.fields ();
457+ std::vector<Literal> values;
458+ values.reserve (fields.size ());
459+
460+ if (json.is_array ()) {
461+ if (json.size () != fields.size ()) {
462+ return JsonParseError (" Invalid partition data size: expected = {}, actual = {}" ,
463+ fields.size (), json.size ());
464+ }
465+ for (size_t pos = 0 ; pos < fields.size (); ++pos) {
466+ ICEBERG_ASSIGN_OR_RAISE (auto value,
467+ PartitionLiteralFromJson (&json[pos], fields[pos]));
468+ values.push_back (std::move (value));
469+ }
470+ } else if (json.is_object ()) {
471+ if (json.size () > fields.size ()) {
472+ return JsonParseError (" Invalid partition data size: expected <= {}, actual = {}" ,
473+ fields.size (), json.size ());
474+ }
475+ for (const auto & field : fields) {
476+ const auto it = json.find (std::to_string (field.field_id ()));
477+ const nlohmann::json* value = it == json.end () ? nullptr : &it.value ();
478+ ICEBERG_ASSIGN_OR_RAISE (auto literal, PartitionLiteralFromJson (value, field));
479+ values.push_back (std::move (literal));
480+ }
481+ } else {
482+ return JsonParseError (
483+ " Invalid partition data for content file: expected array or object ({})" ,
484+ SafeDumpJson (json));
485+ }
486+
487+ return PartitionValues (std::move (values));
488+ }
489+
313490Result<nlohmann::json> DataFileToJsonUnchecked (const DataFile& data_file) {
314491 nlohmann::json json;
315492 switch (data_file.content ) {
@@ -327,7 +504,7 @@ Result<nlohmann::json> DataFileToJsonUnchecked(const DataFile& data_file) {
327504 json[kFileFormat ] = ToString (data_file.file_format );
328505
329506 if (!data_file.partition_spec_id .has_value ()) {
330- return ValidationFailed (" Cannot serialize REST content file without 'spec-id'" );
507+ return ValidationFailed (" Cannot serialize content file without 'spec-id'" );
331508 }
332509 json[kSpecId ] = data_file.partition_spec_id .value ();
333510
@@ -345,11 +522,11 @@ Result<nlohmann::json> DataFileToJsonUnchecked(const DataFile& data_file) {
345522 SetKeyValueMap (json, kValueCounts , data_file.value_counts );
346523 SetKeyValueMap (json, kNullValueCounts , data_file.null_value_counts );
347524 SetKeyValueMap (json, kNanValueCounts , data_file.nan_value_counts );
348- SetKeyValueMap (json, kLowerBounds , data_file.lower_bounds );
349- SetKeyValueMap (json, kUpperBounds , data_file.upper_bounds );
525+ SetBytesMap (json, kLowerBounds , data_file.lower_bounds );
526+ SetBytesMap (json, kUpperBounds , data_file.upper_bounds );
350527
351528 if (!data_file.key_metadata .empty ()) {
352- json[kKeyMetadata ] = data_file.key_metadata ;
529+ json[kKeyMetadata ] = BytesToHex ( data_file.key_metadata ) ;
353530 }
354531 if (!data_file.split_offsets .empty ()) {
355532 json[kSplitOffsets ] = data_file.split_offsets ;
@@ -2134,11 +2311,13 @@ Result<DataFile> DataFileFromJson(
21342311 DataFile data_file;
21352312
21362313 ICEBERG_ASSIGN_OR_RAISE (auto content_str, GetJsonValue<std::string>(json, kContent ));
2137- if (content_str == kContentData ) {
2314+ if (content_str == kContentData || content_str == " DATA " ) {
21382315 data_file.content = DataFile::Content::kData ;
2139- } else if (content_str == kContentPositionDeletes ) {
2316+ } else if (content_str == kContentPositionDeletes ||
2317+ content_str == " POSITION_DELETES" ) {
21402318 data_file.content = DataFile::Content::kPositionDeletes ;
2141- } else if (content_str == kContentEqualityDeletes ) {
2319+ } else if (content_str == kContentEqualityDeletes ||
2320+ content_str == " EQUALITY_DELETES" ) {
21422321 data_file.content = DataFile::Content::kEqualityDeletes ;
21432322 } else {
21442323 return JsonParseError (" Unknown data file content: {}" , content_str);
@@ -2149,37 +2328,25 @@ Result<DataFile> DataFileFromJson(
21492328 ICEBERG_ASSIGN_OR_RAISE (auto format_str, GetJsonValue<std::string>(json, kFileFormat ));
21502329 ICEBERG_ASSIGN_OR_RAISE (data_file.file_format , FileFormatTypeFromString (format_str));
21512330
2152- ICEBERG_ASSIGN_OR_RAISE (auto spec_id, GetJsonValue <int32_t >(json, kSpecId ));
2331+ ICEBERG_ASSIGN_OR_RAISE (auto spec_id, GetJsonInteger <int32_t >(json, kSpecId ));
21532332 data_file.partition_spec_id = spec_id;
21542333
2155- ICEBERG_ASSIGN_OR_RAISE (auto partition_vals,
2156- GetJsonValue<nlohmann::json>(json, kPartition ));
2157- if (!partition_vals.is_array ()) {
2158- return JsonParseError (" PartitionValues must be a JSON array: {}" ,
2159- SafeDumpJson (partition_vals));
2160- }
2161- std::vector<Literal> literals;
21622334 auto it = partition_spec_by_id.find (spec_id);
2163- if (it == partition_spec_by_id.end ()) {
2335+ if (it == partition_spec_by_id.end () || !it-> second ) {
21642336 return JsonParseError (" Invalid partition spec id: {}" , spec_id);
21652337 }
2166- ICEBERG_ASSIGN_OR_RAISE (auto struct_type, it->second ->PartitionType (schema));
2167- auto fields = struct_type->fields ();
2168- if (partition_vals.size () != fields.size ()) {
2169- return JsonParseError (" Invalid partition data size: expected = {}, actual = {}" ,
2170- fields.size (), partition_vals.size ());
2171- }
2172- for (size_t pos = 0 ; pos < fields.size (); ++pos) {
2173- ICEBERG_ASSIGN_OR_RAISE (
2174- auto literal, LiteralFromJson (partition_vals[pos], fields[pos].type ().get ()));
2175- literals.push_back (std::move (literal));
2338+ if (json.contains (kPartition )) {
2339+ ICEBERG_ASSIGN_OR_RAISE (auto partition_vals,
2340+ GetJsonValue<nlohmann::json>(json, kPartition ));
2341+ ICEBERG_ASSIGN_OR_RAISE (auto struct_type, it->second ->PartitionType (schema));
2342+ ICEBERG_ASSIGN_OR_RAISE (data_file.partition ,
2343+ PartitionValuesFromJson (partition_vals, *struct_type));
21762344 }
2177- data_file.partition = PartitionValues (std::move (literals));
21782345
21792346 ICEBERG_ASSIGN_OR_RAISE (data_file.record_count ,
2180- GetJsonValue <int64_t >(json, kRecordCount ));
2347+ GetJsonInteger <int64_t >(json, kRecordCount ));
21812348 ICEBERG_ASSIGN_OR_RAISE (data_file.file_size_in_bytes ,
2182- GetJsonValue <int64_t >(json, kFileSizeInBytes ));
2349+ GetJsonInteger <int64_t >(json, kFileSizeInBytes ));
21832350
21842351 ICEBERG_ASSIGN_OR_RAISE (data_file.column_sizes ,
21852352 KeyValueMapFromJson<int64_t >(json, kColumnSizes ));
@@ -2189,42 +2356,42 @@ Result<DataFile> DataFileFromJson(
21892356 KeyValueMapFromJson<int64_t >(json, kNullValueCounts ));
21902357 ICEBERG_ASSIGN_OR_RAISE (data_file.nan_value_counts ,
21912358 KeyValueMapFromJson<int64_t >(json, kNanValueCounts ));
2192- ICEBERG_ASSIGN_OR_RAISE (data_file.lower_bounds ,
2193- KeyValueMapFromJson<std::vector<uint8_t >>(json, kLowerBounds ));
2194- ICEBERG_ASSIGN_OR_RAISE (data_file.upper_bounds ,
2195- KeyValueMapFromJson<std::vector<uint8_t >>(json, kUpperBounds ));
2359+ ICEBERG_ASSIGN_OR_RAISE (data_file.lower_bounds , BytesMapFromJson (json, kLowerBounds ));
2360+ ICEBERG_ASSIGN_OR_RAISE (data_file.upper_bounds , BytesMapFromJson (json, kUpperBounds ));
21962361
21972362 if (json.contains (kKeyMetadata ) && !json.at (kKeyMetadata ).is_null ()) {
2363+ ICEBERG_ASSIGN_OR_RAISE (auto key_metadata,
2364+ GetJsonValue<std::string>(json, kKeyMetadata ));
21982365 ICEBERG_ASSIGN_OR_RAISE (data_file.key_metadata ,
2199- GetJsonValue<std::vector< uint8_t >>(json, kKeyMetadata ));
2366+ StringUtils::HexStringToBytes (key_metadata ));
22002367 }
22012368 if (json.contains (kSplitOffsets ) && !json.at (kSplitOffsets ).is_null ()) {
22022369 ICEBERG_ASSIGN_OR_RAISE (data_file.split_offsets ,
2203- GetJsonValue<std::vector< int64_t > >(json, kSplitOffsets ));
2370+ IntegerVectorFromJson< int64_t >(json, kSplitOffsets ));
22042371 }
22052372 if (json.contains (kEqualityIds ) && !json.at (kEqualityIds ).is_null ()) {
22062373 ICEBERG_ASSIGN_OR_RAISE (data_file.equality_ids ,
2207- GetJsonValue<std::vector< int32_t > >(json, kEqualityIds ));
2374+ IntegerVectorFromJson< int32_t >(json, kEqualityIds ));
22082375 }
22092376 if (json.contains (kSortOrderId ) && !json.at (kSortOrderId ).is_null ()) {
22102377 ICEBERG_ASSIGN_OR_RAISE (data_file.sort_order_id ,
2211- GetJsonValue <int32_t >(json, kSortOrderId ));
2378+ GetJsonInteger <int32_t >(json, kSortOrderId ));
22122379 }
22132380 if (json.contains (kFirstRowId ) && !json.at (kFirstRowId ).is_null ()) {
22142381 ICEBERG_ASSIGN_OR_RAISE (data_file.first_row_id ,
2215- GetJsonValue <int64_t >(json, kFirstRowId ));
2382+ GetJsonInteger <int64_t >(json, kFirstRowId ));
22162383 }
22172384 if (json.contains (kReferencedDataFile ) && !json.at (kReferencedDataFile ).is_null ()) {
22182385 ICEBERG_ASSIGN_OR_RAISE (data_file.referenced_data_file ,
22192386 GetJsonValue<std::string>(json, kReferencedDataFile ));
22202387 }
22212388 if (json.contains (kContentOffset ) && !json.at (kContentOffset ).is_null ()) {
22222389 ICEBERG_ASSIGN_OR_RAISE (data_file.content_offset ,
2223- GetJsonValue <int64_t >(json, kContentOffset ));
2390+ GetJsonInteger <int64_t >(json, kContentOffset ));
22242391 }
22252392 if (json.contains (kContentSizeInBytes ) && !json.at (kContentSizeInBytes ).is_null ()) {
22262393 ICEBERG_ASSIGN_OR_RAISE (data_file.content_size_in_bytes ,
2227- GetJsonValue <int64_t >(json, kContentSizeInBytes ));
2394+ GetJsonInteger <int64_t >(json, kContentSizeInBytes ));
22282395 }
22292396
22302397 return data_file;
@@ -2254,6 +2421,16 @@ Result<nlohmann::json> ToJson(
22542421 partition_type->fields ().empty () ? " unpartitioned" : " partitioned" ,
22552422 data_file.partition .num_fields () == 0 ? " unpartitioned" : " partitioned" );
22562423 }
2424+ for (size_t pos = 0 ; pos < partition_type->fields ().size (); ++pos) {
2425+ const auto & literal = data_file.partition .values ()[pos];
2426+ const auto & expected_type = partition_type->fields ()[pos].type ();
2427+ if (!literal.IsNull () && (!literal.type () || *literal.type () != *expected_type)) {
2428+ return ValidationFailed (
2429+ " Invalid partition value type at position {}: expected = {}, actual = {}" , pos,
2430+ expected_type->ToString (),
2431+ literal.type () ? literal.type ()->ToString () : " unknown" );
2432+ }
2433+ }
22572434 return DataFileToJsonUnchecked (data_file);
22582435}
22592436
0 commit comments