4343#include " iceberg/table_update.h"
4444#include " iceberg/transform.h"
4545#include " iceberg/type.h"
46+ #include " iceberg/util/base64.h"
4647#include " iceberg/util/checked_cast.h"
4748#include " iceberg/util/formatter.h" // IWYU pragma: keep
4849#include " iceberg/util/json_util_internal.h"
@@ -90,6 +91,8 @@ constexpr std::string_view kValueId = "value-id";
9091constexpr std::string_view kRequired = " required" ;
9192constexpr std::string_view kElementRequired = " element-required" ;
9293constexpr std::string_view kValueRequired = " value-required" ;
94+ constexpr std::string_view kEncryptedKeyMetadata = " encrypted-key-metadata" ;
95+ constexpr std::string_view kEncryptedById = " encrypted-by-id" ;
9396
9497// Snapshot constants
9598constexpr std::string_view kSpecId = " spec-id" ;
@@ -166,6 +169,7 @@ constexpr std::string_view kRefs = "refs";
166169constexpr std::string_view kStatistics = " statistics" ;
167170constexpr std::string_view kPartitionStatistics = " partition-statistics" ;
168171constexpr std::string_view kNextRowId = " next-row-id" ;
172+ constexpr std::string_view kEncryptionKeys = " encryption-keys" ;
169173constexpr std::string_view kMetadataFile = " metadata-file" ;
170174constexpr std::string_view kStatisticsPath = " statistics-path" ;
171175constexpr std::string_view kFileSizeInBytes = " file-size-in-bytes" ;
@@ -196,6 +200,8 @@ constexpr std::string_view kActionRemoveStatistics = "remove-statistics";
196200constexpr std::string_view kActionSetPartitionStatistics = " set-partition-statistics" ;
197201constexpr std::string_view kActionRemovePartitionStatistics =
198202 " remove-partition-statistics" ;
203+ constexpr std::string_view kActionAddEncryptionKey = " add-encryption-key" ;
204+ constexpr std::string_view kActionRemoveEncryptionKey = " remove-encryption-key" ;
199205
200206// TableUpdate field constants
201207constexpr std::string_view kUUID = " uuid" ;
@@ -210,6 +216,9 @@ constexpr std::string_view kRefName = "ref-name";
210216constexpr std::string_view kRef = " ref" ;
211217constexpr std::string_view kUpdates = " updates" ;
212218constexpr std::string_view kRemovals = " removals" ;
219+ constexpr std::string_view kUpdated = " updated" ;
220+ constexpr std::string_view kRemoved = " removed" ;
221+ constexpr std::string_view kEncryptionKey = " encryption-key" ;
213222
214223// TableRequirement type constants
215224constexpr std::string_view kRequirementAssertDoesNotExist = " assert-create" ;
@@ -851,6 +860,41 @@ Result<MetadataLogEntry> MetadataLogEntryFromJson(const nlohmann::json& json) {
851860 return metadata_log_entry;
852861}
853862
863+ nlohmann::json ToJson (const EncryptedKey& encrypted_key) {
864+ nlohmann::json json;
865+ json[kKeyId ] = encrypted_key.key_id ;
866+ json[kEncryptedKeyMetadata ] = Base64::Encode (encrypted_key.encrypted_key_metadata );
867+ SetOptionalField (json, kEncryptedById , encrypted_key.encrypted_by_id );
868+ if (!encrypted_key.properties .empty ()) {
869+ json[kProperties ] = encrypted_key.properties ;
870+ }
871+ return json;
872+ }
873+
874+ Result<EncryptedKey> EncryptedKeyFromJson (const nlohmann::json& json) {
875+ using StringMap = std::unordered_map<std::string, std::string>;
876+
877+ if (!json.is_object ()) {
878+ return JsonParseError (" Invalid encryption key, must be non-null object: {}" ,
879+ SafeDumpJson (json));
880+ }
881+
882+ ICEBERG_ASSIGN_OR_RAISE (auto key_id, GetJsonValue<std::string>(json, kKeyId ));
883+ ICEBERG_ASSIGN_OR_RAISE (auto encoded_metadata,
884+ GetJsonValue<std::string>(json, kEncryptedKeyMetadata ));
885+ ICEBERG_ASSIGN_OR_RAISE (auto encrypted_key_metadata, Base64::Decode (encoded_metadata));
886+ ICEBERG_ASSIGN_OR_RAISE (auto encrypted_by_id,
887+ GetJsonValueOptional<std::string>(json, kEncryptedById ));
888+ ICEBERG_ASSIGN_OR_RAISE (auto properties,
889+ GetJsonValueOrDefault<StringMap>(json, kProperties ));
890+ return EncryptedKey{
891+ .key_id = std::move (key_id),
892+ .encrypted_key_metadata = std::move (encrypted_key_metadata),
893+ .encrypted_by_id = std::move (encrypted_by_id),
894+ .properties = std::move (properties),
895+ };
896+ }
897+
854898nlohmann::json ToJson (const TableMetadata& table_metadata) {
855899 nlohmann::json json;
856900
@@ -917,6 +961,9 @@ nlohmann::json ToJson(const TableMetadata& table_metadata) {
917961 json[kSnapshots ] = ToJsonList (table_metadata.snapshots );
918962 json[kStatistics ] = ToJsonList (table_metadata.statistics );
919963 json[kPartitionStatistics ] = ToJsonList (table_metadata.partition_statistics );
964+ if (!table_metadata.encryption_keys .empty ()) {
965+ json[kEncryptionKeys ] = ToJsonList (table_metadata.encryption_keys );
966+ }
920967 json[kSnapshotLog ] = ToJsonList (table_metadata.snapshot_log );
921968 json[kMetadataLog ] = ToJsonList (table_metadata.metadata_log );
922969
@@ -1182,6 +1229,9 @@ Result<std::unique_ptr<TableMetadata>> TableMetadataFromJson(const nlohmann::jso
11821229 table_metadata->partition_statistics ,
11831230 FromJsonList<PartitionStatisticsFile>(json, kPartitionStatistics ,
11841231 PartitionStatisticsFileFromJson));
1232+ ICEBERG_ASSIGN_OR_RAISE (
1233+ table_metadata->encryption_keys ,
1234+ FromJsonList<EncryptedKey>(json, kEncryptionKeys , EncryptedKeyFromJson));
11851235 ICEBERG_ASSIGN_OR_RAISE (
11861236 table_metadata->snapshot_log ,
11871237 FromJsonList<SnapshotLogEntry>(json, kSnapshotLog , SnapshotLogEntryFromJson));
@@ -1486,6 +1536,18 @@ nlohmann::json ToJson(const TableUpdate& update) {
14861536 json[kSnapshotId ] = u.snapshot_id ();
14871537 break ;
14881538 }
1539+ case TableUpdate::Kind::kAddEncryptionKey : {
1540+ const auto & u = internal::checked_cast<const table::AddEncryptionKey&>(update);
1541+ json[kAction ] = kActionAddEncryptionKey ;
1542+ json[kEncryptionKey ] = ToJson (u.key ());
1543+ break ;
1544+ }
1545+ case TableUpdate::Kind::kRemoveEncryptionKey : {
1546+ const auto & u = internal::checked_cast<const table::RemoveEncryptionKey&>(update);
1547+ json[kAction ] = kActionRemoveEncryptionKey ;
1548+ json[kKeyId ] = u.key_id ();
1549+ break ;
1550+ }
14891551 }
14901552 return json;
14911553}
@@ -1506,7 +1568,6 @@ nlohmann::json ToJson(const TableRequirement& requirement) {
15061568 const auto & r =
15071569 internal::checked_cast<const table::AssertRefSnapshotID&>(requirement);
15081570 json[kType ] = kRequirementAssertRefSnapshotID ;
1509- // REST spec names this field "ref", not "ref-name".
15101571 json[kRef ] = r.ref_name ();
15111572 if (r.snapshot_id ().has_value ()) {
15121573 json[kSnapshotId ] = r.snapshot_id ().value ();
@@ -1570,8 +1631,10 @@ Result<std::unique_ptr<TableUpdate>> TableUpdateFromJson(const nlohmann::json& j
15701631 ICEBERG_ASSIGN_OR_RAISE (auto schema_json,
15711632 GetJsonValue<nlohmann::json>(json, kSchema ));
15721633 ICEBERG_ASSIGN_OR_RAISE (auto parsed_schema, SchemaFromJson (schema_json));
1573- ICEBERG_ASSIGN_OR_RAISE (auto last_column_id,
1574- GetJsonValue<int32_t >(json, kLastColumnId ));
1634+ ICEBERG_ASSIGN_OR_RAISE (auto highest_field_id, parsed_schema->HighestFieldId ());
1635+ ICEBERG_ASSIGN_OR_RAISE (
1636+ auto last_column_id,
1637+ GetJsonValueOrDefault<int32_t >(json, kLastColumnId , highest_field_id));
15751638 return std::make_unique<table::AddSchema>(std::move (parsed_schema), last_column_id);
15761639 }
15771640 if (action == kActionSetCurrentSchema ) {
@@ -1650,12 +1713,17 @@ Result<std::unique_ptr<TableUpdate>> TableUpdateFromJson(const nlohmann::json& j
16501713 }
16511714 if (action == kActionSetProperties ) {
16521715 using StringMap = std::unordered_map<std::string, std::string>;
1653- ICEBERG_ASSIGN_OR_RAISE (auto updates, GetJsonValue<StringMap>(json, kUpdates ));
1716+ ICEBERG_ASSIGN_OR_RAISE (auto updates,
1717+ json.contains (kUpdates ) || !json.contains (kUpdated )
1718+ ? GetJsonValue<StringMap>(json, kUpdates )
1719+ : GetJsonValue<StringMap>(json, kUpdated ));
16541720 return std::make_unique<table::SetProperties>(std::move (updates));
16551721 }
16561722 if (action == kActionRemoveProperties ) {
16571723 ICEBERG_ASSIGN_OR_RAISE (auto removals_vec,
1658- GetJsonValue<std::vector<std::string>>(json, kRemovals ));
1724+ json.contains (kRemovals ) || !json.contains (kRemoved )
1725+ ? GetJsonValue<std::vector<std::string>>(json, kRemovals )
1726+ : GetJsonValue<std::vector<std::string>>(json, kRemoved ));
16591727 std::unordered_set<std::string> removals (
16601728 std::make_move_iterator (removals_vec.begin ()),
16611729 std::make_move_iterator (removals_vec.end ()));
@@ -1688,6 +1756,17 @@ Result<std::unique_ptr<TableUpdate>> TableUpdateFromJson(const nlohmann::json& j
16881756 ICEBERG_ASSIGN_OR_RAISE (auto snapshot_id, GetJsonValue<int64_t >(json, kSnapshotId ));
16891757 return std::make_unique<table::RemovePartitionStatistics>(snapshot_id);
16901758 }
1759+ if (action == kActionAddEncryptionKey ) {
1760+ if (!json.contains (kEncryptionKey )) {
1761+ return JsonParseError (" Invalid encryption key, must be non-null object: null" );
1762+ }
1763+ ICEBERG_ASSIGN_OR_RAISE (auto key, EncryptedKeyFromJson (json.at (kEncryptionKey )));
1764+ return std::make_unique<table::AddEncryptionKey>(std::move (key));
1765+ }
1766+ if (action == kActionRemoveEncryptionKey ) {
1767+ ICEBERG_ASSIGN_OR_RAISE (auto key_id, GetJsonValue<std::string>(json, kKeyId ));
1768+ return std::make_unique<table::RemoveEncryptionKey>(std::move (key_id));
1769+ }
16911770
16921771 return JsonParseError (" Unknown table update action: {}" , action);
16931772}
0 commit comments