blob: 9e8984df1910e898b5d583ee23f41d7cce04ed43 [file]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
#include <nlohmann/json.hpp>
#include "iceberg/expression/expression.h"
#include "iceberg/expression/json_serde_internal.h"
#include "iceberg/metrics/json_serde_internal.h"
#include "iceberg/util/json_util_internal.h"
#include "iceberg/util/string_util.h"
namespace iceberg {
namespace {
using StringMap = std::unordered_map<std::string, std::string>;
// JSON key constants (kebab-case, matching Iceberg spec)
constexpr std::string_view kTableName = "table-name";
constexpr std::string_view kSnapshotId = "snapshot-id";
constexpr std::string_view kFilter = "filter";
constexpr std::string_view kSchemaId = "schema-id";
constexpr std::string_view kProjectedFieldIds = "projected-field-ids";
constexpr std::string_view kProjectedFieldNames = "projected-field-names";
constexpr std::string_view kMetrics = "metrics";
constexpr std::string_view kMetadata = "metadata";
constexpr std::string_view kSequenceNumber = "sequence-number";
constexpr std::string_view kOperation = "operation";
// CounterResult / TimerResult keys
constexpr std::string_view kUnit = "unit";
constexpr std::string_view kTimeUnit = "time-unit";
constexpr std::string_view kValue = "value";
constexpr std::string_view kCount = "count";
constexpr std::string_view kTotalDuration = "total-duration";
// ScanMetricsResult keys
constexpr std::string_view kTotalPlanningDuration = "total-planning-duration";
constexpr std::string_view kResultDataFiles = "result-data-files";
constexpr std::string_view kResultDeleteFiles = "result-delete-files";
constexpr std::string_view kScannedDataManifests = "scanned-data-manifests";
constexpr std::string_view kScannedDeleteManifests = "scanned-delete-manifests";
constexpr std::string_view kTotalDataManifests = "total-data-manifests";
constexpr std::string_view kTotalDeleteManifests = "total-delete-manifests";
constexpr std::string_view kTotalFileSizeInBytes = "total-file-size-in-bytes";
constexpr std::string_view kTotalDeleteFileSizeInBytes =
"total-delete-file-size-in-bytes";
constexpr std::string_view kSkippedDataManifests = "skipped-data-manifests";
constexpr std::string_view kSkippedDeleteManifests = "skipped-delete-manifests";
constexpr std::string_view kSkippedDataFiles = "skipped-data-files";
constexpr std::string_view kSkippedDeleteFiles = "skipped-delete-files";
constexpr std::string_view kIndexedDeleteFiles = "indexed-delete-files";
constexpr std::string_view kEqualityDeleteFiles = "equality-delete-files";
constexpr std::string_view kPositionalDeleteFiles = "positional-delete-files";
constexpr std::string_view kDvs = "dvs";
// CommitMetricsResult keys
constexpr std::string_view kAttempts = "attempts";
constexpr std::string_view kAddedDataFiles = "added-data-files";
constexpr std::string_view kRemovedDataFiles = "removed-data-files";
constexpr std::string_view kTotalDataFiles = "total-data-files";
constexpr std::string_view kAddedDeleteFiles = "added-delete-files";
constexpr std::string_view kAddedEqualityDeleteFiles = "added-equality-delete-files";
constexpr std::string_view kAddedPositionalDeleteFiles = "added-positional-delete-files";
constexpr std::string_view kAddedDvs = "added-dvs";
constexpr std::string_view kRemovedDeleteFiles = "removed-delete-files";
constexpr std::string_view kRemovedPositionalDeleteFiles =
"removed-positional-delete-files";
constexpr std::string_view kRemovedDvs = "removed-dvs";
constexpr std::string_view kRemovedEqualityDeleteFiles = "removed-equality-delete-files";
constexpr std::string_view kTotalDeleteFiles = "total-delete-files";
constexpr std::string_view kAddedRecords = "added-records";
constexpr std::string_view kRemovedRecords = "removed-records";
constexpr std::string_view kTotalRecords = "total-records";
constexpr std::string_view kAddedFilesSizeBytes = "added-files-size-bytes";
constexpr std::string_view kRemovedFilesSizeBytes = "removed-files-size-bytes";
constexpr std::string_view kTotalFilesSizeBytes = "total-files-size-bytes";
constexpr std::string_view kAddedPositionalDeletes = "added-positional-deletes";
constexpr std::string_view kRemovedPositionalDeletes = "removed-positional-deletes";
constexpr std::string_view kTotalPositionalDeletes = "total-positional-deletes";
constexpr std::string_view kAddedEqualityDeletes = "added-equality-deletes";
constexpr std::string_view kRemovedEqualityDeletes = "removed-equality-deletes";
constexpr std::string_view kTotalEqualityDeletes = "total-equality-deletes";
constexpr std::string_view kKeptManifestCount = "manifests-kept";
constexpr std::string_view kCreatedManifestCount = "manifests-created";
constexpr std::string_view kReplacedManifestCount = "manifests-replaced";
constexpr std::string_view kProcessedManifestEntriesCount = "manifest-entries-processed";
void SetCounterField(nlohmann::json& json, std::string_view key,
const std::optional<CounterResult>& counter) {
if (counter) {
json[key] = ToJson(*counter);
}
}
Result<std::optional<CounterResult>> ParseCounterResult(const nlohmann::json& json,
std::string_view key) {
auto it = json.find(key);
if (it == json.end()) return std::nullopt;
return CounterResultFromJson(*it);
}
Result<std::optional<TimerResult>> ParseTimerResult(const nlohmann::json& json,
std::string_view key) {
auto it = json.find(key);
if (it == json.end()) return std::nullopt;
return TimerResultFromJson(*it);
}
Result<std::chrono::nanoseconds> DurationToNanoseconds(int64_t duration,
std::string_view unit) {
auto normalized = StringUtils::ToLower(unit);
if (normalized == "nanoseconds") return std::chrono::nanoseconds{duration};
if (normalized == "microseconds") return std::chrono::microseconds{duration};
if (normalized == "milliseconds") return std::chrono::milliseconds{duration};
if (normalized == "seconds") return std::chrono::seconds{duration};
if (normalized == "minutes") return std::chrono::minutes{duration};
if (normalized == "hours") return std::chrono::hours{duration};
if (normalized == "days") return std::chrono::days{duration};
return JsonParseError("Invalid time unit: {}", unit);
}
Result<int64_t> NanosecondsToDuration(std::chrono::nanoseconds duration,
std::string_view unit) {
auto normalized = StringUtils::ToLower(unit);
if (normalized == "nanoseconds") return duration.count();
if (normalized == "microseconds") {
return std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
}
if (normalized == "milliseconds") {
return std::chrono::duration_cast<std::chrono::milliseconds>(duration).count();
}
if (normalized == "seconds") {
return std::chrono::duration_cast<std::chrono::seconds>(duration).count();
}
if (normalized == "minutes") {
return std::chrono::duration_cast<std::chrono::minutes>(duration).count();
}
if (normalized == "hours") {
return std::chrono::duration_cast<std::chrono::hours>(duration).count();
}
if (normalized == "days") {
return std::chrono::duration_cast<std::chrono::days>(duration).count();
}
return JsonParseError("Invalid time unit: {}", unit);
}
} // namespace
// ---------------------------------------------------------------------------
// CounterResult
// ---------------------------------------------------------------------------
nlohmann::json ToJson(const CounterResult& counter) {
return {{kUnit, ToString(counter.unit)}, {kValue, counter.value}};
}
Result<CounterResult> CounterResultFromJson(const nlohmann::json& json) {
ICEBERG_ASSIGN_OR_RAISE(auto unit, GetJsonValue<std::string>(json, kUnit));
ICEBERG_ASSIGN_OR_RAISE(auto parsed_unit, CounterUnitFromString(unit));
ICEBERG_ASSIGN_OR_RAISE(auto value, GetJsonValue<int64_t>(json, kValue));
return CounterResult{.unit = parsed_unit, .value = value};
}
// ---------------------------------------------------------------------------
// TimerResult
// ---------------------------------------------------------------------------
Result<nlohmann::json> ToJson(const TimerResult& timer) {
ICEBERG_ASSIGN_OR_RAISE(auto duration,
NanosecondsToDuration(timer.total_duration, timer.unit));
auto unit = StringUtils::ToLower(timer.unit);
nlohmann::json json = {
{kTimeUnit, unit}, {kCount, timer.count}, {kTotalDuration, duration}};
return json;
}
Result<TimerResult> TimerResultFromJson(const nlohmann::json& json) {
ICEBERG_ASSIGN_OR_RAISE(auto count, GetJsonValue<int64_t>(json, kCount));
ICEBERG_ASSIGN_OR_RAISE(auto unit, GetJsonValue<std::string>(json, kTimeUnit));
ICEBERG_ASSIGN_OR_RAISE(auto total, GetJsonValue<int64_t>(json, kTotalDuration));
ICEBERG_ASSIGN_OR_RAISE(auto duration, DurationToNanoseconds(total, unit));
return TimerResult{
.unit = StringUtils::ToLower(unit), .count = count, .total_duration = duration};
}
// ---------------------------------------------------------------------------
// ScanMetricsResult
// ---------------------------------------------------------------------------
Result<nlohmann::json> ToJson(const ScanMetricsResult& m) {
nlohmann::json json = nlohmann::json::object();
if (m.total_planning_duration) {
ICEBERG_ASSIGN_OR_RAISE(auto total_planning_duration,
ToJson(*m.total_planning_duration));
json[std::string(kTotalPlanningDuration)] = std::move(total_planning_duration);
}
SetCounterField(json, kResultDataFiles, m.result_data_files);
SetCounterField(json, kResultDeleteFiles, m.result_delete_files);
SetCounterField(json, kScannedDataManifests, m.scanned_data_manifests);
SetCounterField(json, kScannedDeleteManifests, m.scanned_delete_manifests);
SetCounterField(json, kTotalDataManifests, m.total_data_manifests);
SetCounterField(json, kTotalDeleteManifests, m.total_delete_manifests);
SetCounterField(json, kTotalFileSizeInBytes, m.total_file_size_in_bytes);
SetCounterField(json, kTotalDeleteFileSizeInBytes, m.total_delete_file_size_in_bytes);
SetCounterField(json, kSkippedDataManifests, m.skipped_data_manifests);
SetCounterField(json, kSkippedDeleteManifests, m.skipped_delete_manifests);
SetCounterField(json, kSkippedDataFiles, m.skipped_data_files);
SetCounterField(json, kSkippedDeleteFiles, m.skipped_delete_files);
SetCounterField(json, kIndexedDeleteFiles, m.indexed_delete_files);
SetCounterField(json, kEqualityDeleteFiles, m.equality_delete_files);
SetCounterField(json, kPositionalDeleteFiles, m.positional_delete_files);
SetCounterField(json, kDvs, m.dvs);
return json;
}
Result<ScanMetricsResult> ScanMetricsResultFromJson(const nlohmann::json& json) {
if (!json.is_object()) {
return JsonParseError("Cannot parse scan metrics from non-object: {}",
SafeDumpJson(json));
}
ScanMetricsResult m;
ICEBERG_ASSIGN_OR_RAISE(m.total_planning_duration,
ParseTimerResult(json, kTotalPlanningDuration));
ICEBERG_ASSIGN_OR_RAISE(m.result_data_files,
ParseCounterResult(json, kResultDataFiles));
ICEBERG_ASSIGN_OR_RAISE(m.result_delete_files,
ParseCounterResult(json, kResultDeleteFiles));
ICEBERG_ASSIGN_OR_RAISE(m.scanned_data_manifests,
ParseCounterResult(json, kScannedDataManifests));
ICEBERG_ASSIGN_OR_RAISE(m.scanned_delete_manifests,
ParseCounterResult(json, kScannedDeleteManifests));
ICEBERG_ASSIGN_OR_RAISE(m.total_data_manifests,
ParseCounterResult(json, kTotalDataManifests));
ICEBERG_ASSIGN_OR_RAISE(m.total_delete_manifests,
ParseCounterResult(json, kTotalDeleteManifests));
ICEBERG_ASSIGN_OR_RAISE(m.total_file_size_in_bytes,
ParseCounterResult(json, kTotalFileSizeInBytes));
ICEBERG_ASSIGN_OR_RAISE(m.total_delete_file_size_in_bytes,
ParseCounterResult(json, kTotalDeleteFileSizeInBytes));
ICEBERG_ASSIGN_OR_RAISE(m.skipped_data_manifests,
ParseCounterResult(json, kSkippedDataManifests));
ICEBERG_ASSIGN_OR_RAISE(m.skipped_delete_manifests,
ParseCounterResult(json, kSkippedDeleteManifests));
ICEBERG_ASSIGN_OR_RAISE(m.skipped_data_files,
ParseCounterResult(json, kSkippedDataFiles));
ICEBERG_ASSIGN_OR_RAISE(m.skipped_delete_files,
ParseCounterResult(json, kSkippedDeleteFiles));
ICEBERG_ASSIGN_OR_RAISE(m.indexed_delete_files,
ParseCounterResult(json, kIndexedDeleteFiles));
ICEBERG_ASSIGN_OR_RAISE(m.equality_delete_files,
ParseCounterResult(json, kEqualityDeleteFiles));
ICEBERG_ASSIGN_OR_RAISE(m.positional_delete_files,
ParseCounterResult(json, kPositionalDeleteFiles));
ICEBERG_ASSIGN_OR_RAISE(m.dvs, ParseCounterResult(json, kDvs));
return m;
}
// ---------------------------------------------------------------------------
// CommitMetricsResult
// ---------------------------------------------------------------------------
Result<nlohmann::json> ToJson(const CommitMetricsResult& m) {
nlohmann::json json = nlohmann::json::object();
if (m.total_duration) {
ICEBERG_ASSIGN_OR_RAISE(auto total_duration, ToJson(*m.total_duration));
json[std::string(kTotalDuration)] = std::move(total_duration);
}
SetCounterField(json, kAttempts, m.attempts);
SetCounterField(json, kAddedDataFiles, m.added_data_files);
SetCounterField(json, kRemovedDataFiles, m.removed_data_files);
SetCounterField(json, kTotalDataFiles, m.total_data_files);
SetCounterField(json, kAddedDeleteFiles, m.added_delete_files);
SetCounterField(json, kAddedEqualityDeleteFiles, m.added_equality_delete_files);
SetCounterField(json, kAddedPositionalDeleteFiles, m.added_positional_delete_files);
SetCounterField(json, kAddedDvs, m.added_dvs);
SetCounterField(json, kRemovedDeleteFiles, m.removed_delete_files);
SetCounterField(json, kRemovedPositionalDeleteFiles, m.removed_positional_delete_files);
SetCounterField(json, kRemovedDvs, m.removed_dvs);
SetCounterField(json, kRemovedEqualityDeleteFiles, m.removed_equality_delete_files);
SetCounterField(json, kTotalDeleteFiles, m.total_delete_files);
SetCounterField(json, kAddedRecords, m.added_records);
SetCounterField(json, kRemovedRecords, m.removed_records);
SetCounterField(json, kTotalRecords, m.total_records);
SetCounterField(json, kAddedFilesSizeBytes, m.added_files_size_bytes);
SetCounterField(json, kRemovedFilesSizeBytes, m.removed_files_size_bytes);
SetCounterField(json, kTotalFilesSizeBytes, m.total_files_size_bytes);
SetCounterField(json, kAddedPositionalDeletes, m.added_positional_deletes);
SetCounterField(json, kRemovedPositionalDeletes, m.removed_positional_deletes);
SetCounterField(json, kTotalPositionalDeletes, m.total_positional_deletes);
SetCounterField(json, kAddedEqualityDeletes, m.added_equality_deletes);
SetCounterField(json, kRemovedEqualityDeletes, m.removed_equality_deletes);
SetCounterField(json, kTotalEqualityDeletes, m.total_equality_deletes);
SetCounterField(json, kKeptManifestCount, m.kept_manifest_count);
SetCounterField(json, kCreatedManifestCount, m.created_manifest_count);
SetCounterField(json, kReplacedManifestCount, m.replaced_manifest_count);
SetCounterField(json, kProcessedManifestEntriesCount,
m.processed_manifest_entries_count);
return json;
}
Result<CommitMetricsResult> CommitMetricsResultFromJson(const nlohmann::json& json) {
if (!json.is_object()) {
return JsonParseError("Cannot parse commit metrics from non-object: {}",
SafeDumpJson(json));
}
CommitMetricsResult m;
ICEBERG_ASSIGN_OR_RAISE(m.total_duration, ParseTimerResult(json, kTotalDuration));
ICEBERG_ASSIGN_OR_RAISE(m.attempts, ParseCounterResult(json, kAttempts));
ICEBERG_ASSIGN_OR_RAISE(m.added_data_files, ParseCounterResult(json, kAddedDataFiles));
ICEBERG_ASSIGN_OR_RAISE(m.removed_data_files,
ParseCounterResult(json, kRemovedDataFiles));
ICEBERG_ASSIGN_OR_RAISE(m.total_data_files, ParseCounterResult(json, kTotalDataFiles));
ICEBERG_ASSIGN_OR_RAISE(m.added_delete_files,
ParseCounterResult(json, kAddedDeleteFiles));
ICEBERG_ASSIGN_OR_RAISE(m.added_equality_delete_files,
ParseCounterResult(json, kAddedEqualityDeleteFiles));
ICEBERG_ASSIGN_OR_RAISE(m.added_positional_delete_files,
ParseCounterResult(json, kAddedPositionalDeleteFiles));
ICEBERG_ASSIGN_OR_RAISE(m.added_dvs, ParseCounterResult(json, kAddedDvs));
ICEBERG_ASSIGN_OR_RAISE(m.removed_delete_files,
ParseCounterResult(json, kRemovedDeleteFiles));
ICEBERG_ASSIGN_OR_RAISE(m.removed_positional_delete_files,
ParseCounterResult(json, kRemovedPositionalDeleteFiles));
ICEBERG_ASSIGN_OR_RAISE(m.removed_dvs, ParseCounterResult(json, kRemovedDvs));
ICEBERG_ASSIGN_OR_RAISE(m.removed_equality_delete_files,
ParseCounterResult(json, kRemovedEqualityDeleteFiles));
ICEBERG_ASSIGN_OR_RAISE(m.total_delete_files,
ParseCounterResult(json, kTotalDeleteFiles));
ICEBERG_ASSIGN_OR_RAISE(m.added_records, ParseCounterResult(json, kAddedRecords));
ICEBERG_ASSIGN_OR_RAISE(m.removed_records, ParseCounterResult(json, kRemovedRecords));
ICEBERG_ASSIGN_OR_RAISE(m.total_records, ParseCounterResult(json, kTotalRecords));
ICEBERG_ASSIGN_OR_RAISE(m.added_files_size_bytes,
ParseCounterResult(json, kAddedFilesSizeBytes));
ICEBERG_ASSIGN_OR_RAISE(m.removed_files_size_bytes,
ParseCounterResult(json, kRemovedFilesSizeBytes));
ICEBERG_ASSIGN_OR_RAISE(m.total_files_size_bytes,
ParseCounterResult(json, kTotalFilesSizeBytes));
ICEBERG_ASSIGN_OR_RAISE(m.added_positional_deletes,
ParseCounterResult(json, kAddedPositionalDeletes));
ICEBERG_ASSIGN_OR_RAISE(m.removed_positional_deletes,
ParseCounterResult(json, kRemovedPositionalDeletes));
ICEBERG_ASSIGN_OR_RAISE(m.total_positional_deletes,
ParseCounterResult(json, kTotalPositionalDeletes));
ICEBERG_ASSIGN_OR_RAISE(m.added_equality_deletes,
ParseCounterResult(json, kAddedEqualityDeletes));
ICEBERG_ASSIGN_OR_RAISE(m.removed_equality_deletes,
ParseCounterResult(json, kRemovedEqualityDeletes));
ICEBERG_ASSIGN_OR_RAISE(m.total_equality_deletes,
ParseCounterResult(json, kTotalEqualityDeletes));
ICEBERG_ASSIGN_OR_RAISE(m.kept_manifest_count,
ParseCounterResult(json, kKeptManifestCount));
ICEBERG_ASSIGN_OR_RAISE(m.created_manifest_count,
ParseCounterResult(json, kCreatedManifestCount));
ICEBERG_ASSIGN_OR_RAISE(m.replaced_manifest_count,
ParseCounterResult(json, kReplacedManifestCount));
ICEBERG_ASSIGN_OR_RAISE(m.processed_manifest_entries_count,
ParseCounterResult(json, kProcessedManifestEntriesCount));
return m;
}
// ---------------------------------------------------------------------------
// ScanReport
// ---------------------------------------------------------------------------
Result<nlohmann::json> ToJson(const ScanReport& report) {
nlohmann::json json;
json[kTableName] = report.table_name;
json[kSnapshotId] = report.snapshot_id;
auto filter = report.filter ? report.filter : True::Instance();
ICEBERG_ASSIGN_OR_RAISE(auto filter_json, ToJson(*filter));
json[kFilter] = std::move(filter_json);
json[kSchemaId] = report.schema_id;
json[kProjectedFieldIds] = report.projected_field_ids;
json[kProjectedFieldNames] = report.projected_field_names;
ICEBERG_ASSIGN_OR_RAISE(auto metrics_json, ToJson(report.scan_metrics));
json[kMetrics] = std::move(metrics_json);
if (!report.metadata.empty()) {
json[kMetadata] = report.metadata;
}
return json;
}
Result<ScanReport> ScanReportFromJson(const nlohmann::json& json) {
ScanReport report;
ICEBERG_ASSIGN_OR_RAISE(report.table_name, GetJsonValue<std::string>(json, kTableName));
ICEBERG_ASSIGN_OR_RAISE(report.snapshot_id, GetJsonValue<int64_t>(json, kSnapshotId));
ICEBERG_ASSIGN_OR_RAISE(auto filter_json, GetJsonValue<nlohmann::json>(json, kFilter));
ICEBERG_ASSIGN_OR_RAISE(report.filter, ExpressionFromJson(filter_json));
ICEBERG_ASSIGN_OR_RAISE(report.schema_id, GetJsonValue<int32_t>(json, kSchemaId));
ICEBERG_ASSIGN_OR_RAISE(report.projected_field_ids,
GetJsonValue<std::vector<int32_t>>(json, kProjectedFieldIds));
ICEBERG_ASSIGN_OR_RAISE(
report.projected_field_names,
GetJsonValue<std::vector<std::string>>(json, kProjectedFieldNames));
ICEBERG_ASSIGN_OR_RAISE(auto metrics_json,
GetJsonValue<nlohmann::json>(json, kMetrics));
ICEBERG_ASSIGN_OR_RAISE(report.scan_metrics, ScanMetricsResultFromJson(metrics_json));
if (json.contains(kMetadata)) {
ICEBERG_ASSIGN_OR_RAISE(report.metadata, GetJsonValue<StringMap>(json, kMetadata));
}
return report;
}
// ---------------------------------------------------------------------------
// CommitReport
// ---------------------------------------------------------------------------
Result<nlohmann::json> ToJson(const CommitReport& report) {
nlohmann::json json;
json[kTableName] = report.table_name;
json[kSnapshotId] = report.snapshot_id;
json[kSequenceNumber] = report.sequence_number;
json[kOperation] = report.operation;
ICEBERG_ASSIGN_OR_RAISE(auto metrics_json, ToJson(report.commit_metrics));
json[kMetrics] = std::move(metrics_json);
if (!report.metadata.empty()) {
json[kMetadata] = report.metadata;
}
return json;
}
Result<CommitReport> CommitReportFromJson(const nlohmann::json& json) {
CommitReport report;
ICEBERG_ASSIGN_OR_RAISE(report.table_name, GetJsonValue<std::string>(json, kTableName));
ICEBERG_ASSIGN_OR_RAISE(report.snapshot_id, GetJsonValue<int64_t>(json, kSnapshotId));
ICEBERG_ASSIGN_OR_RAISE(report.sequence_number,
GetJsonValue<int64_t>(json, kSequenceNumber));
ICEBERG_ASSIGN_OR_RAISE(report.operation, GetJsonValue<std::string>(json, kOperation));
ICEBERG_ASSIGN_OR_RAISE(auto metrics_json,
GetJsonValue<nlohmann::json>(json, kMetrics));
ICEBERG_ASSIGN_OR_RAISE(report.commit_metrics,
CommitMetricsResultFromJson(metrics_json));
if (json.contains(kMetadata)) {
ICEBERG_ASSIGN_OR_RAISE(report.metadata, GetJsonValue<StringMap>(json, kMetadata));
}
return report;
}
} // namespace iceberg