This is an automated email from the ASF dual-hosted git repository.
wgtmac pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg-cpp.git
The following commit(s) were added to refs/heads/main by this push:
new ce4544b1 feat: add snapshot encryption key-id (#990)
ce4544b1 is described below
commit ce4544b1af0b570773a10b89e0e81716ed1200fb
Author: Xinli Shang <[email protected]>
AuthorDate: Thu Oct 15 14:32:39 2026 +0900
feat: add snapshot encryption key-id (#990)
Spec v3 snapshots of encrypted tables carry `key-id`, the ID of the
entry in `encryption-keys` that holds the encrypted key metadata of the
snapshot's manifest list. Add it to Snapshot, read and write it in the
JSON serde, and include it in snapshot equality.
Co-authored-by: Claude Opus 5.5 <[email protected]>
---
src/iceberg/json_serde.cc | 5 ++++-
src/iceberg/snapshot.cc | 2 +-
src/iceberg/snapshot.h | 3 +++
src/iceberg/test/json_serde_test.cc | 22 ++++++++++++++++++++++
4 files changed, 30 insertions(+), 2 deletions(-)
diff --git a/src/iceberg/json_serde.cc b/src/iceberg/json_serde.cc
index c604a2e0..83559df9 100644
--- a/src/iceberg/json_serde.cc
+++ b/src/iceberg/json_serde.cc
@@ -748,6 +748,7 @@ nlohmann::json ToJson(const Snapshot& snapshot) {
if (snapshot.first_row_id.has_value()) {
SetOptionalField(json, kAddedRows, snapshot.added_rows);
}
+ SetOptionalField(json, kKeyId, snapshot.key_id);
return json;
}
@@ -1170,11 +1171,13 @@ Result<std::unique_ptr<Snapshot>>
SnapshotFromJson(const nlohmann::json& json) {
}
ICEBERG_ASSIGN_OR_RAISE(auto schema_id, GetJsonValueOptional<int32_t>(json,
kSchemaId));
+ ICEBERG_ASSIGN_OR_RAISE(auto key_id, GetJsonValueOptional<std::string>(json,
kKeyId));
return std::make_unique<Snapshot>(
snapshot_id, parent_snapshot_id,
sequence_number.value_or(TableMetadata::kInitialSequenceNumber),
timestamp_ms,
- manifest_list, std::move(summary), schema_id, first_row_id, added_rows);
+ manifest_list, std::move(summary), schema_id, first_row_id, added_rows,
+ std::move(key_id));
}
nlohmann::json ToJson(const BlobMetadata& blob_metadata) {
diff --git a/src/iceberg/snapshot.cc b/src/iceberg/snapshot.cc
index 51affaa1..f791c372 100644
--- a/src/iceberg/snapshot.cc
+++ b/src/iceberg/snapshot.cc
@@ -173,7 +173,7 @@ bool Snapshot::Equals(const Snapshot& other) const {
parent_snapshot_id == other.parent_snapshot_id &&
sequence_number == other.sequence_number && timestamp_ms ==
other.timestamp_ms &&
schema_id == other.schema_id && first_row_id == other.first_row_id &&
- added_rows == other.added_rows;
+ added_rows == other.added_rows && key_id == other.key_id;
}
Result<std::unique_ptr<Snapshot>> Snapshot::Make(
diff --git a/src/iceberg/snapshot.h b/src/iceberg/snapshot.h
index 3f6009b2..54c6fea2 100644
--- a/src/iceberg/snapshot.h
+++ b/src/iceberg/snapshot.h
@@ -413,6 +413,9 @@ struct ICEBERG_EXPORT Snapshot {
std::optional<int64_t> first_row_id;
/// The upper bound of rows with assigned row IDs in this snapshot.
std::optional<int64_t> added_rows;
+ /// ID of the encryption key (in table metadata `encryption-keys`) that
holds the
+ /// encrypted key metadata of the manifest list. Set only for encrypted
tables.
+ std::optional<std::string> key_id;
/// \brief Create a new Snapshot instance with validation on the inputs.
static Result<std::unique_ptr<Snapshot>> Make(
diff --git a/src/iceberg/test/json_serde_test.cc
b/src/iceberg/test/json_serde_test.cc
index fc7a5ec7..4cea7563 100644
--- a/src/iceberg/test/json_serde_test.cc
+++ b/src/iceberg/test/json_serde_test.cc
@@ -567,6 +567,28 @@ TEST(JsonInternalTest,
SnapshotFromJsonReadsTopLevelRowLineageFields) {
EXPECT_EQ(json["summary"]["added-rows"], "26");
}
+TEST(JsonInternalTest, SnapshotEncryptionKeyId) {
+ nlohmann::json snapshot_json =
+ R"({"snapshot-id":1234567890,
+ "sequence-number":1,
+ "timestamp-ms":1234567890123,
+ "manifest-list":"/path/to/manifest_list",
+ "summary":{"operation":"append"},
+ "key-id":"C9PJpnsZpsbZarERjyOitg=="})"_json;
+
+ ICEBERG_UNWRAP_OR_FAIL(auto snapshot, SnapshotFromJson(snapshot_json));
+ ASSERT_TRUE(snapshot->key_id.has_value());
+ EXPECT_EQ(snapshot->key_id.value(), "C9PJpnsZpsbZarERjyOitg==");
+ EXPECT_EQ(ToJson(*snapshot)["key-id"], "C9PJpnsZpsbZarERjyOitg==");
+
+ // Snapshots of unencrypted tables have no key-id
+ snapshot_json.erase("key-id");
+ ICEBERG_UNWRAP_OR_FAIL(auto plain, SnapshotFromJson(snapshot_json));
+ EXPECT_FALSE(plain->key_id.has_value());
+ EXPECT_FALSE(ToJson(*plain).contains("key-id"));
+ EXPECT_NE(*plain, *snapshot);
+}
+
// FIXME: disable it for now since Iceberg Spark plugin generates
// custom summary keys.
TEST(JsonInternalTest, DISABLED_SnapshotFromJsonWithInvalidSummary) {