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");
+    }
+}

Reply via email to