This is an automated email from the ASF dual-hosted git repository. github-merge-queue[bot] pushed a commit to branch gh-readonly-queue/main/pr-11733-e0908622a9e5a6247d16314f1cae2ffab84d315e in repository https://gitbox.apache.org/repos/asf/arrow-rs.git
commit 09bb64ac5c5221a927601d43c4e93e8e6e5c4eff Author: autoCAT <[email protected]> AuthorDate: Sun Oct 25 08:52:46 2026 +0000 feat(parquet): expose encryption key metadata by column name (#11733) # Which issue does this PR close? Closes #11753. # Rationale for this change `FileEncryptionProperties::column_keys()` returns names and keys for every configured column, but its metadata vector contains only columns with metadata. When some column keys have metadata and others do not, callers cannot safely associate metadata with column names by vector position. This matters when converting encryption properties to another configuration representation: a key identifier can become associated with a different column. # What changes are included in this PR? Add `column_key_metadata(&str) -> Option<&[u8]>` to retrieve metadata by column name without cloning it. The accessor distinguishes explicitly empty metadata from absent metadata, and does not substitute footer-key metadata for an unconfigured column. The existing `column_keys()` API and behavior are unchanged. # Are these changes tested? Unit tests cover mixed metadata with both insertion orders, multiple distinct column identifiers, unknown columns, absent versus explicitly empty metadata, and the boundary between column metadata and footer metadata during uniform encryption. # Are there any significant user-facing changes? This is an additive public API; existing signatures and encryption behavior remain unchanged. --- parquet/src/encryption/encrypt.rs | 68 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) diff --git a/parquet/src/encryption/encrypt.rs b/parquet/src/encryption/encrypt.rs index 591eac802d..b870f73155 100644 --- a/parquet/src/encryption/encrypt.rs +++ b/parquet/src/encryption/encrypt.rs @@ -141,6 +141,15 @@ impl FileEncryptionProperties { (column_names, keys, meta) } + /// Returns the retrieval metadata for the named column's encryption key. + /// + /// Returns `None` if the column has no explicitly configured encryption key + /// or its key has no metadata. Metadata is not inherited from the footer key. + /// Explicitly configured empty metadata is returned as `Some(&[])`. + pub fn column_key_metadata(&self, column_name: &str) -> Option<&[u8]> { + self.column_keys.get(column_name)?.key_metadata.as_deref() + } + /// AAD prefix string uniquely identifies the file and prevents file swapping pub fn aad_prefix(&self) -> Option<&Vec<u8>> { self.aad_prefix.as_ref() @@ -442,3 +451,62 @@ pub(crate) fn get_column_crypto_metadata( }) } } + +#[cfg(test)] +mod tests { + use super::FileEncryptionProperties; + + #[test] + fn test_column_key_metadata_preserves_associations() { + let columns = [ + ("first", Some(b"first-key-id".as_slice())), + ("second", None), + ("third", Some(b"third-key-id".as_slice())), + ]; + for indices in [[0, 1, 2], [2, 1, 0]] { + let mut builder = FileEncryptionProperties::builder(b"0123456789012345".to_vec()); + for index in indices { + let (name, metadata) = columns[index]; + let key = vec![index as u8; 16]; + builder = match metadata { + Some(metadata) => { + builder.with_column_key_and_metadata(name, key, metadata.to_vec()) + } + None => builder.with_column_key(name, key), + }; + } + let properties = builder.build().unwrap(); + + for (name, metadata) in columns { + assert_eq!(properties.column_key_metadata(name), metadata); + } + assert_eq!(properties.column_key_metadata("missing"), None); + } + } + + #[test] + fn test_column_key_metadata_preserves_empty_metadata() { + let properties = FileEncryptionProperties::builder(b"0123456789012345".to_vec()) + .with_footer_key_metadata(b"footer-key-id".to_vec()) + .with_column_key_and_metadata("empty", b"1234567890123450".to_vec(), Vec::new()) + .with_column_key("absent", b"1234567890123451".to_vec()) + .build() + .unwrap(); + + assert_eq!(properties.column_key_metadata("empty"), Some([].as_slice())); + assert_eq!(properties.column_key_metadata("absent"), None); + assert_eq!(properties.column_key_metadata("missing"), None); + assert_eq!(properties.footer_key_metadata().unwrap(), b"footer-key-id"); + } + + #[test] + fn test_column_key_metadata_does_not_inherit_footer_metadata() { + let properties = FileEncryptionProperties::builder(b"0123456789012345".to_vec()) + .with_footer_key_metadata(b"footer-key-id".to_vec()) + .build() + .unwrap(); + + assert_eq!(properties.column_key_metadata("column"), None); + assert_eq!(properties.footer_key_metadata().unwrap(), b"footer-key-id"); + } +}
