From a3125626789f6378936eb7dd359e58eac8625789 Mon Sep 17 00:00:00 2001 From: Russell Spitzer Date: Tue, 6 Oct 2026 13:38:58 -0500 Subject: [PATCH 01/10] feat(transaction): record the Rust writer in snapshot summaries A commit from iceberg-rust was indistinguishable from any other client that omits writer identity. Stamp engine-name, engine-version, and iceberg-version on every snapshot so mixed-writer tables can attribute the commit. Generated-by: Cursor Grok 4.7 --- crates/iceberg/src/transaction/append.rs | 82 ++++++++++++++++++++++ crates/iceberg/src/transaction/snapshot.rs | 33 ++++++++- 2 files changed, 113 insertions(+), 2 deletions(-) diff --git a/crates/iceberg/src/transaction/append.rs b/crates/iceberg/src/transaction/append.rs index 3260f9ffa2..d0f35db245 100644 --- a/crates/iceberg/src/transaction/append.rs +++ b/crates/iceberg/src/transaction/append.rs @@ -764,6 +764,88 @@ mod tests { ); } + #[tokio::test] + async fn test_snapshot_summary_records_writer_identity() { + let table = make_v2_minimal_table(); + let tx = Transaction::new(&table); + + let data_file = DataFileBuilder::default() + .content(DataContentType::Data) + .file_path("test/1.parquet".to_string()) + .file_format(DataFileFormat::Parquet) + .file_size_in_bytes(100) + .record_count(1) + .partition_spec_id(table.metadata().default_partition_spec_id()) + .partition(Struct::from_iter([Some(Literal::long(300))])) + .build() + .unwrap(); + + let action = tx.fast_append().add_data_files(vec![data_file]); + let mut action_commit = Arc::new(action).commit(&table).await.unwrap(); + let updates = action_commit.take_updates(); + + let new_snapshot = if let TableUpdate::AddSnapshot { snapshot } = &updates[0] { + snapshot + } else { + unreachable!() + }; + let props = &new_snapshot.summary().additional_properties; + let version = env!("CARGO_PKG_VERSION"); + + assert_eq!(props.get("engine-name").unwrap(), "iceberg-rust"); + assert_eq!(props.get("engine-version").unwrap(), version); + assert_eq!( + props.get("iceberg-version").unwrap(), + &format!("Apache Iceberg Rust {version}") + ); + } + + #[tokio::test] + async fn test_snapshot_properties_cannot_override_writer_identity() { + let table = make_v2_minimal_table(); + let tx = Transaction::new(&table); + + let mut snapshot_properties = HashMap::new(); + snapshot_properties.insert("engine-name".to_string(), "other".to_string()); + snapshot_properties.insert("engine-version".to_string(), "0".to_string()); + snapshot_properties.insert("iceberg-version".to_string(), "custom".to_string()); + snapshot_properties.insert("key".to_string(), "val".to_string()); + + let data_file = DataFileBuilder::default() + .content(DataContentType::Data) + .file_path("test/1.parquet".to_string()) + .file_format(DataFileFormat::Parquet) + .file_size_in_bytes(100) + .record_count(1) + .partition_spec_id(table.metadata().default_partition_spec_id()) + .partition(Struct::from_iter([Some(Literal::long(300))])) + .build() + .unwrap(); + + let action = tx + .fast_append() + .set_snapshot_properties(snapshot_properties) + .add_data_files(vec![data_file]); + let mut action_commit = Arc::new(action).commit(&table).await.unwrap(); + let updates = action_commit.take_updates(); + + let new_snapshot = if let TableUpdate::AddSnapshot { snapshot } = &updates[0] { + snapshot + } else { + unreachable!() + }; + let props = &new_snapshot.summary().additional_properties; + let version = env!("CARGO_PKG_VERSION"); + + assert_eq!(props.get("engine-name").unwrap(), "iceberg-rust"); + assert_eq!(props.get("engine-version").unwrap(), version); + assert_eq!( + props.get("iceberg-version").unwrap(), + &format!("Apache Iceberg Rust {version}") + ); + assert_eq!(props.get("key").unwrap(), "val"); + } + #[tokio::test] async fn test_append_snapshot_properties() { let table = make_v2_minimal_table(); diff --git a/crates/iceberg/src/transaction/snapshot.rs b/crates/iceberg/src/transaction/snapshot.rs index a0ce1fba14..89258b00d6 100644 --- a/crates/iceberg/src/transaction/snapshot.rs +++ b/crates/iceberg/src/transaction/snapshot.rs @@ -34,6 +34,27 @@ use crate::table::Table; use crate::transaction::ActionCommit; use crate::{Error, ErrorKind, TableRequirement, TableUpdate}; +const ENGINE_NAME_PROP: &str = "engine-name"; +const ENGINE_VERSION_PROP: &str = "engine-version"; +const ICEBERG_VERSION_PROP: &str = "iceberg-version"; +const ENGINE_NAME: &str = "iceberg-rust"; + +/// Identity fields written on every snapshot this library commits. +/// +/// `engine-name` and `engine-version` match PyIceberg. `iceberg-version` matches +/// iceberg-java's `EnvironmentContext` and iceberg-go, without a git commit id. +fn writer_identity_properties() -> HashMap { + let version = env!("CARGO_PKG_VERSION"); + HashMap::from([ + (ENGINE_NAME_PROP.to_string(), ENGINE_NAME.to_string()), + (ENGINE_VERSION_PROP.to_string(), version.to_string()), + ( + ICEBERG_VERSION_PROP.to_string(), + format!("Apache Iceberg Rust {version}"), + ), + ]) +} + /// A trait that defines how different table operations produce new snapshots. /// /// `SnapshotProduceOperation` is used by [`SnapshotProducer`] to customize snapshot creation @@ -411,11 +432,19 @@ impl<'a> SnapshotProducer<'a> { additional_properties, }; - update_snapshot_summaries( + let mut summary = update_snapshot_summaries( summary, previous_snapshot.map(|s| s.summary()), snapshot_produce_operation.operation() == Operation::Overwrite, - ) + )?; + // Applied last, so these keys override a caller snapshot property with the + // same name. Java copies EnvironmentContext at the end of + // SnapshotProducer.summary; PyIceberg sets its engine fields at the end of + // update_snapshot_summaries. + summary + .additional_properties + .extend(writer_identity_properties()); + Ok(summary) } fn generate_manifest_list_file_path(&self, attempt: i64) -> Result { From bc2db59a9335c4f9b38ecae30707689eed96f936 Mon Sep 17 00:00:00 2001 From: Russell Spitzer Date: Tue, 6 Oct 2026 13:44:07 -0500 Subject: [PATCH 02/10] Remove the writer-identity comments. Generated-by: Cursor Grok 4.7 --- crates/iceberg/src/transaction/snapshot.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/crates/iceberg/src/transaction/snapshot.rs b/crates/iceberg/src/transaction/snapshot.rs index 89258b00d6..abc72aad31 100644 --- a/crates/iceberg/src/transaction/snapshot.rs +++ b/crates/iceberg/src/transaction/snapshot.rs @@ -39,10 +39,6 @@ const ENGINE_VERSION_PROP: &str = "engine-version"; const ICEBERG_VERSION_PROP: &str = "iceberg-version"; const ENGINE_NAME: &str = "iceberg-rust"; -/// Identity fields written on every snapshot this library commits. -/// -/// `engine-name` and `engine-version` match PyIceberg. `iceberg-version` matches -/// iceberg-java's `EnvironmentContext` and iceberg-go, without a git commit id. fn writer_identity_properties() -> HashMap { let version = env!("CARGO_PKG_VERSION"); HashMap::from([ @@ -437,10 +433,6 @@ impl<'a> SnapshotProducer<'a> { previous_snapshot.map(|s| s.summary()), snapshot_produce_operation.operation() == Operation::Overwrite, )?; - // Applied last, so these keys override a caller snapshot property with the - // same name. Java copies EnvironmentContext at the end of - // SnapshotProducer.summary; PyIceberg sets its engine fields at the end of - // update_snapshot_summaries. summary .additional_properties .extend(writer_identity_properties()); From 07425434576329123aaf4ea6392c985bf3a4813a Mon Sep 17 00:00:00 2001 From: Russell Spitzer Date: Tue, 6 Oct 2026 13:55:46 -0500 Subject: [PATCH 03/10] Keep a caller-supplied engine name and version. iceberg-version still identifies this library. engine-name and engine-version are filled only when the caller did not set them, so an embedding engine can record itself through snapshot properties. Generated-by: Cursor Grok 4.7 --- crates/iceberg/src/transaction/append.rs | 6 +++--- crates/iceberg/src/transaction/snapshot.rs | 24 +++++++++++----------- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/crates/iceberg/src/transaction/append.rs b/crates/iceberg/src/transaction/append.rs index d0f35db245..a0f96b3527 100644 --- a/crates/iceberg/src/transaction/append.rs +++ b/crates/iceberg/src/transaction/append.rs @@ -801,7 +801,7 @@ mod tests { } #[tokio::test] - async fn test_snapshot_properties_cannot_override_writer_identity() { + async fn test_caller_engine_identity_is_kept() { let table = make_v2_minimal_table(); let tx = Transaction::new(&table); @@ -837,8 +837,8 @@ mod tests { let props = &new_snapshot.summary().additional_properties; let version = env!("CARGO_PKG_VERSION"); - assert_eq!(props.get("engine-name").unwrap(), "iceberg-rust"); - assert_eq!(props.get("engine-version").unwrap(), version); + assert_eq!(props.get("engine-name").unwrap(), "other"); + assert_eq!(props.get("engine-version").unwrap(), "0"); assert_eq!( props.get("iceberg-version").unwrap(), &format!("Apache Iceberg Rust {version}") diff --git a/crates/iceberg/src/transaction/snapshot.rs b/crates/iceberg/src/transaction/snapshot.rs index abc72aad31..950350fd0a 100644 --- a/crates/iceberg/src/transaction/snapshot.rs +++ b/crates/iceberg/src/transaction/snapshot.rs @@ -39,16 +39,18 @@ const ENGINE_VERSION_PROP: &str = "engine-version"; const ICEBERG_VERSION_PROP: &str = "iceberg-version"; const ENGINE_NAME: &str = "iceberg-rust"; -fn writer_identity_properties() -> HashMap { +fn apply_writer_identity(properties: &mut HashMap) { let version = env!("CARGO_PKG_VERSION"); - HashMap::from([ - (ENGINE_NAME_PROP.to_string(), ENGINE_NAME.to_string()), - (ENGINE_VERSION_PROP.to_string(), version.to_string()), - ( - ICEBERG_VERSION_PROP.to_string(), - format!("Apache Iceberg Rust {version}"), - ), - ]) + properties + .entry(ENGINE_NAME_PROP.to_string()) + .or_insert_with(|| ENGINE_NAME.to_string()); + properties + .entry(ENGINE_VERSION_PROP.to_string()) + .or_insert_with(|| version.to_string()); + properties.insert( + ICEBERG_VERSION_PROP.to_string(), + format!("Apache Iceberg Rust {version}"), + ); } /// A trait that defines how different table operations produce new snapshots. @@ -433,9 +435,7 @@ impl<'a> SnapshotProducer<'a> { previous_snapshot.map(|s| s.summary()), snapshot_produce_operation.operation() == Operation::Overwrite, )?; - summary - .additional_properties - .extend(writer_identity_properties()); + apply_writer_identity(&mut summary.additional_properties); Ok(summary) } From a7dc0ce742e2b52c49b8808398f0ceba7d26c9c4 Mon Sep 17 00:00:00 2001 From: Russell Spitzer Date: Tue, 6 Oct 2026 14:11:02 -0500 Subject: [PATCH 04/10] Keep the writer-identity tests on snapshot properties. A data file and an unrelated property do not affect those keys. Also list the new summary constants alphabetically. Generated-by: Cursor Grok 4.7 --- crates/iceberg/src/transaction/append.rs | 32 ++++------------------ crates/iceberg/src/transaction/snapshot.rs | 2 +- 2 files changed, 7 insertions(+), 27 deletions(-) diff --git a/crates/iceberg/src/transaction/append.rs b/crates/iceberg/src/transaction/append.rs index a0f96b3527..0898edf91b 100644 --- a/crates/iceberg/src/transaction/append.rs +++ b/crates/iceberg/src/transaction/append.rs @@ -769,18 +769,12 @@ mod tests { let table = make_v2_minimal_table(); let tx = Transaction::new(&table); - let data_file = DataFileBuilder::default() - .content(DataContentType::Data) - .file_path("test/1.parquet".to_string()) - .file_format(DataFileFormat::Parquet) - .file_size_in_bytes(100) - .record_count(1) - .partition_spec_id(table.metadata().default_partition_spec_id()) - .partition(Struct::from_iter([Some(Literal::long(300))])) - .build() - .unwrap(); + let mut snapshot_properties = HashMap::new(); + snapshot_properties.insert("key".to_string(), "val".to_string()); - let action = tx.fast_append().add_data_files(vec![data_file]); + let action = tx + .fast_append() + .set_snapshot_properties(snapshot_properties); let mut action_commit = Arc::new(action).commit(&table).await.unwrap(); let updates = action_commit.take_updates(); @@ -809,23 +803,10 @@ mod tests { snapshot_properties.insert("engine-name".to_string(), "other".to_string()); snapshot_properties.insert("engine-version".to_string(), "0".to_string()); snapshot_properties.insert("iceberg-version".to_string(), "custom".to_string()); - snapshot_properties.insert("key".to_string(), "val".to_string()); - - let data_file = DataFileBuilder::default() - .content(DataContentType::Data) - .file_path("test/1.parquet".to_string()) - .file_format(DataFileFormat::Parquet) - .file_size_in_bytes(100) - .record_count(1) - .partition_spec_id(table.metadata().default_partition_spec_id()) - .partition(Struct::from_iter([Some(Literal::long(300))])) - .build() - .unwrap(); let action = tx .fast_append() - .set_snapshot_properties(snapshot_properties) - .add_data_files(vec![data_file]); + .set_snapshot_properties(snapshot_properties); let mut action_commit = Arc::new(action).commit(&table).await.unwrap(); let updates = action_commit.take_updates(); @@ -843,7 +824,6 @@ mod tests { props.get("iceberg-version").unwrap(), &format!("Apache Iceberg Rust {version}") ); - assert_eq!(props.get("key").unwrap(), "val"); } #[tokio::test] diff --git a/crates/iceberg/src/transaction/snapshot.rs b/crates/iceberg/src/transaction/snapshot.rs index 950350fd0a..7ed6cdee27 100644 --- a/crates/iceberg/src/transaction/snapshot.rs +++ b/crates/iceberg/src/transaction/snapshot.rs @@ -34,10 +34,10 @@ use crate::table::Table; use crate::transaction::ActionCommit; use crate::{Error, ErrorKind, TableRequirement, TableUpdate}; +const ENGINE_NAME: &str = "iceberg-rust"; const ENGINE_NAME_PROP: &str = "engine-name"; const ENGINE_VERSION_PROP: &str = "engine-version"; const ICEBERG_VERSION_PROP: &str = "iceberg-version"; -const ENGINE_NAME: &str = "iceberg-rust"; fn apply_writer_identity(properties: &mut HashMap) { let version = env!("CARGO_PKG_VERSION"); From 78a92e88374d4f8e8d5a0da1c9b7c7932a3bb2e5 Mon Sep 17 00:00:00 2001 From: Russell Spitzer Date: Wed, 7 Oct 2026 10:23:09 -0500 Subject: [PATCH 05/10] Write only iceberg-version into snapshot summaries. engine-name and engine-version name the embedding engine, which this library does not know. Callers can still set them. iceberg-version remains the library identity, matching Java and Go. Generated-by: Cursor Grok 4.7 --- crates/iceberg/src/transaction/append.rs | 4 ++-- crates/iceberg/src/transaction/snapshot.rs | 9 --------- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/crates/iceberg/src/transaction/append.rs b/crates/iceberg/src/transaction/append.rs index 0898edf91b..072aa2281d 100644 --- a/crates/iceberg/src/transaction/append.rs +++ b/crates/iceberg/src/transaction/append.rs @@ -786,8 +786,8 @@ mod tests { let props = &new_snapshot.summary().additional_properties; let version = env!("CARGO_PKG_VERSION"); - assert_eq!(props.get("engine-name").unwrap(), "iceberg-rust"); - assert_eq!(props.get("engine-version").unwrap(), version); + assert!(props.get("engine-name").is_none()); + assert!(props.get("engine-version").is_none()); assert_eq!( props.get("iceberg-version").unwrap(), &format!("Apache Iceberg Rust {version}") diff --git a/crates/iceberg/src/transaction/snapshot.rs b/crates/iceberg/src/transaction/snapshot.rs index 7ed6cdee27..661f648221 100644 --- a/crates/iceberg/src/transaction/snapshot.rs +++ b/crates/iceberg/src/transaction/snapshot.rs @@ -34,19 +34,10 @@ use crate::table::Table; use crate::transaction::ActionCommit; use crate::{Error, ErrorKind, TableRequirement, TableUpdate}; -const ENGINE_NAME: &str = "iceberg-rust"; -const ENGINE_NAME_PROP: &str = "engine-name"; -const ENGINE_VERSION_PROP: &str = "engine-version"; const ICEBERG_VERSION_PROP: &str = "iceberg-version"; fn apply_writer_identity(properties: &mut HashMap) { let version = env!("CARGO_PKG_VERSION"); - properties - .entry(ENGINE_NAME_PROP.to_string()) - .or_insert_with(|| ENGINE_NAME.to_string()); - properties - .entry(ENGINE_VERSION_PROP.to_string()) - .or_insert_with(|| version.to_string()); properties.insert( ICEBERG_VERSION_PROP.to_string(), format!("Apache Iceberg Rust {version}"), From ba050289071dbcda2459a64406cf3a01962dd4af Mon Sep 17 00:00:00 2001 From: Russell Spitzer Date: Wed, 7 Oct 2026 10:29:59 -0500 Subject: [PATCH 06/10] Drop the caller engine-identity snapshot test. Those keys are ordinary snapshot properties now, so preserving a caller value is already covered by the existing property test. Generated-by: Cursor Grok 4.7 --- crates/iceberg/src/transaction/append.rs | 32 ------------------------ 1 file changed, 32 deletions(-) diff --git a/crates/iceberg/src/transaction/append.rs b/crates/iceberg/src/transaction/append.rs index 072aa2281d..6c0544cb77 100644 --- a/crates/iceberg/src/transaction/append.rs +++ b/crates/iceberg/src/transaction/append.rs @@ -794,38 +794,6 @@ mod tests { ); } - #[tokio::test] - async fn test_caller_engine_identity_is_kept() { - let table = make_v2_minimal_table(); - let tx = Transaction::new(&table); - - let mut snapshot_properties = HashMap::new(); - snapshot_properties.insert("engine-name".to_string(), "other".to_string()); - snapshot_properties.insert("engine-version".to_string(), "0".to_string()); - snapshot_properties.insert("iceberg-version".to_string(), "custom".to_string()); - - let action = tx - .fast_append() - .set_snapshot_properties(snapshot_properties); - let mut action_commit = Arc::new(action).commit(&table).await.unwrap(); - let updates = action_commit.take_updates(); - - let new_snapshot = if let TableUpdate::AddSnapshot { snapshot } = &updates[0] { - snapshot - } else { - unreachable!() - }; - let props = &new_snapshot.summary().additional_properties; - let version = env!("CARGO_PKG_VERSION"); - - assert_eq!(props.get("engine-name").unwrap(), "other"); - assert_eq!(props.get("engine-version").unwrap(), "0"); - assert_eq!( - props.get("iceberg-version").unwrap(), - &format!("Apache Iceberg Rust {version}") - ); - } - #[tokio::test] async fn test_append_snapshot_properties() { let table = make_v2_minimal_table(); From 3105575ff7f4dd6da9e7f400fc2c3c4ebfac3cea Mon Sep 17 00:00:00 2001 From: Russell Spitzer Date: Wed, 7 Oct 2026 10:31:25 -0500 Subject: [PATCH 07/10] Stop asserting that engine identity keys are absent. The snapshot summary no longer writes those keys, so the test only checks iceberg-version. Generated-by: Cursor Grok 4.7 --- crates/iceberg/src/transaction/append.rs | 2 -- 1 file changed, 2 deletions(-) diff --git a/crates/iceberg/src/transaction/append.rs b/crates/iceberg/src/transaction/append.rs index 6c0544cb77..e0ed80b6dc 100644 --- a/crates/iceberg/src/transaction/append.rs +++ b/crates/iceberg/src/transaction/append.rs @@ -786,8 +786,6 @@ mod tests { let props = &new_snapshot.summary().additional_properties; let version = env!("CARGO_PKG_VERSION"); - assert!(props.get("engine-name").is_none()); - assert!(props.get("engine-version").is_none()); assert_eq!( props.get("iceberg-version").unwrap(), &format!("Apache Iceberg Rust {version}") From dc2086ee41c6d19c3112db7fafc08fc27c10ebb7 Mon Sep 17 00:00:00 2001 From: Russell Spitzer Date: Wed, 7 Oct 2026 11:07:49 -0500 Subject: [PATCH 08/10] Update crates/iceberg/src/transaction/append.rs Co-authored-by: Matt Butrovich --- crates/iceberg/src/transaction/append.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/crates/iceberg/src/transaction/append.rs b/crates/iceberg/src/transaction/append.rs index e0ed80b6dc..7369183938 100644 --- a/crates/iceberg/src/transaction/append.rs +++ b/crates/iceberg/src/transaction/append.rs @@ -769,8 +769,10 @@ mod tests { let table = make_v2_minimal_table(); let tx = Transaction::new(&table); - let mut snapshot_properties = HashMap::new(); - snapshot_properties.insert("key".to_string(), "val".to_string()); + let snapshot_properties = HashMap::from([ + ("key".to_string(), "val".to_string()), + ("iceberg-version".to_string(), "caller".to_string()), + ]); let action = tx .fast_append() @@ -790,6 +792,7 @@ mod tests { props.get("iceberg-version").unwrap(), &format!("Apache Iceberg Rust {version}") ); + assert_eq!(props.get("key").unwrap(), "val"); } #[tokio::test] From 42578eeedb52a8cc19fa54311d5bf9f2eb4551d1 Mon Sep 17 00:00:00 2001 From: Russell Spitzer Date: Wed, 7 Oct 2026 11:10:06 -0500 Subject: [PATCH 09/10] Move the iceberg-version summary key next to the other keys. The transaction path still writes the value. The property name now lives with the rest of the summary keys the library defines. Generated-by: Cursor Grok 4.7 --- crates/iceberg/src/spec/snapshot_summary.rs | 1 + crates/iceberg/src/transaction/snapshot.rs | 11 +++++------ 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/iceberg/src/spec/snapshot_summary.rs b/crates/iceberg/src/spec/snapshot_summary.rs index f8e69c4c86..ba98a1bffd 100644 --- a/crates/iceberg/src/spec/snapshot_summary.rs +++ b/crates/iceberg/src/spec/snapshot_summary.rs @@ -48,6 +48,7 @@ const TOTAL_RECORDS: &str = "total-records"; const TOTAL_FILE_SIZE: &str = "total-files-size"; const CHANGED_PARTITION_COUNT_PROP: &str = "changed-partition-count"; const CHANGED_PARTITION_PREFIX: &str = "partitions."; +pub(crate) const ICEBERG_VERSION_PROP: &str = "iceberg-version"; /// `SnapshotSummaryCollector` collects and aggregates snapshot update metrics. /// It gathers metrics about added or removed data files and manifests, and tracks diff --git a/crates/iceberg/src/transaction/snapshot.rs b/crates/iceberg/src/transaction/snapshot.rs index 661f648221..12e6679db0 100644 --- a/crates/iceberg/src/transaction/snapshot.rs +++ b/crates/iceberg/src/transaction/snapshot.rs @@ -25,17 +25,16 @@ use uuid::Uuid; use crate::error::{Result, invalid_data}; use crate::spec::{ - DataFile, DataFileFormat, FormatVersion, MAIN_BRANCH, ManifestContentType, ManifestEntry, - ManifestFile, ManifestListWriter, ManifestWriter, ManifestWriterBuilder, Operation, Snapshot, - SnapshotReference, SnapshotRetention, SnapshotSummaryCollector, Struct, StructType, Summary, - TableProperties, update_snapshot_summaries, + DataFile, DataFileFormat, FormatVersion, ICEBERG_VERSION_PROP, MAIN_BRANCH, + ManifestContentType, ManifestEntry, ManifestFile, ManifestListWriter, ManifestWriter, + ManifestWriterBuilder, Operation, Snapshot, SnapshotReference, SnapshotRetention, + SnapshotSummaryCollector, Struct, StructType, Summary, TableProperties, + update_snapshot_summaries, }; use crate::table::Table; use crate::transaction::ActionCommit; use crate::{Error, ErrorKind, TableRequirement, TableUpdate}; -const ICEBERG_VERSION_PROP: &str = "iceberg-version"; - fn apply_writer_identity(properties: &mut HashMap) { let version = env!("CARGO_PKG_VERSION"); properties.insert( From 0679bbaa54787b70582ce851398894c955f5bb66 Mon Sep 17 00:00:00 2001 From: Russell Spitzer Date: Wed, 7 Oct 2026 11:14:51 -0500 Subject: [PATCH 10/10] Set iceberg-version from the snapshot summary module. The key stays private next to the other summary keys, and the commit path calls set_iceberg_version after the totals are computed. Generated-by: Cursor Grok 4.7 --- crates/iceberg/src/spec/snapshot_summary.rs | 10 +++++++++- crates/iceberg/src/transaction/snapshot.rs | 20 +++++--------------- 2 files changed, 14 insertions(+), 16 deletions(-) diff --git a/crates/iceberg/src/spec/snapshot_summary.rs b/crates/iceberg/src/spec/snapshot_summary.rs index ba98a1bffd..eaca57fc16 100644 --- a/crates/iceberg/src/spec/snapshot_summary.rs +++ b/crates/iceberg/src/spec/snapshot_summary.rs @@ -48,7 +48,7 @@ const TOTAL_RECORDS: &str = "total-records"; const TOTAL_FILE_SIZE: &str = "total-files-size"; const CHANGED_PARTITION_COUNT_PROP: &str = "changed-partition-count"; const CHANGED_PARTITION_PREFIX: &str = "partitions."; -pub(crate) const ICEBERG_VERSION_PROP: &str = "iceberg-version"; +const ICEBERG_VERSION_PROP: &str = "iceberg-version"; /// `SnapshotSummaryCollector` collects and aggregates snapshot update metrics. /// It gathers metrics about added or removed data files and manifests, and tracks @@ -331,6 +331,14 @@ where T: PartialOrd + Default + ToString { } } +pub(crate) fn set_iceberg_version(properties: &mut HashMap) { + let version = env!("CARGO_PKG_VERSION"); + properties.insert( + ICEBERG_VERSION_PROP.to_string(), + format!("Apache Iceberg Rust {version}"), + ); +} + pub(crate) fn update_snapshot_summaries( summary: Summary, previous_summary: Option<&Summary>, diff --git a/crates/iceberg/src/transaction/snapshot.rs b/crates/iceberg/src/transaction/snapshot.rs index 12e6679db0..656a580ce8 100644 --- a/crates/iceberg/src/transaction/snapshot.rs +++ b/crates/iceberg/src/transaction/snapshot.rs @@ -25,24 +25,14 @@ use uuid::Uuid; use crate::error::{Result, invalid_data}; use crate::spec::{ - DataFile, DataFileFormat, FormatVersion, ICEBERG_VERSION_PROP, MAIN_BRANCH, - ManifestContentType, ManifestEntry, ManifestFile, ManifestListWriter, ManifestWriter, - ManifestWriterBuilder, Operation, Snapshot, SnapshotReference, SnapshotRetention, - SnapshotSummaryCollector, Struct, StructType, Summary, TableProperties, - update_snapshot_summaries, + DataFile, DataFileFormat, FormatVersion, MAIN_BRANCH, ManifestContentType, ManifestEntry, + ManifestFile, ManifestListWriter, ManifestWriter, ManifestWriterBuilder, Operation, Snapshot, + SnapshotReference, SnapshotRetention, SnapshotSummaryCollector, Struct, StructType, Summary, + TableProperties, set_iceberg_version, update_snapshot_summaries, }; use crate::table::Table; use crate::transaction::ActionCommit; use crate::{Error, ErrorKind, TableRequirement, TableUpdate}; - -fn apply_writer_identity(properties: &mut HashMap) { - let version = env!("CARGO_PKG_VERSION"); - properties.insert( - ICEBERG_VERSION_PROP.to_string(), - format!("Apache Iceberg Rust {version}"), - ); -} - /// A trait that defines how different table operations produce new snapshots. /// /// `SnapshotProduceOperation` is used by [`SnapshotProducer`] to customize snapshot creation @@ -425,7 +415,7 @@ impl<'a> SnapshotProducer<'a> { previous_snapshot.map(|s| s.summary()), snapshot_produce_operation.operation() == Operation::Overwrite, )?; - apply_writer_identity(&mut summary.additional_properties); + set_iceberg_version(&mut summary.additional_properties); Ok(summary) }