You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@iceberg.apache.org by si...@apache.org on 2026/05/21 03:43:10 UTC

(iceberg) branch main updated: Encrypting IO as a `DelegateFileIO` (#14876)

This is an automated email from the ASF dual-hosted git repository.

singhpk234 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/iceberg.git


The following commit(s) were added to refs/heads/main by this push:
     new 8a90699dcf Encrypting IO as a `DelegateFileIO` (#14876)
8a90699dcf is described below

commit 8a90699dcf692a3c5f517baae352c4648bb77c30
Author: Sreesh Maheshwar <ma...@gmail.com>
AuthorDate: Thu May 21 04:43:04 2026 +0100

    Encrypting IO as a `DelegateFileIO` (#14876)
    
    * Encrypting IO as a `DelegateFileIO`
    
    * Test closing of underlying file IO
---
 .../iceberg/encryption/EncryptingFileIO.java       | 30 +++++++-
 .../iceberg/encryption/TestEncryptingFileIO.java   | 88 +++++++++++++++++++++-
 2 files changed, 115 insertions(+), 3 deletions(-)

diff --git a/api/src/main/java/org/apache/iceberg/encryption/EncryptingFileIO.java b/api/src/main/java/org/apache/iceberg/encryption/EncryptingFileIO.java
index c19da0f01e..575171f852 100644
--- a/api/src/main/java/org/apache/iceberg/encryption/EncryptingFileIO.java
+++ b/api/src/main/java/org/apache/iceberg/encryption/EncryptingFileIO.java
@@ -29,6 +29,8 @@ import org.apache.iceberg.DataFile;
 import org.apache.iceberg.DeleteFile;
 import org.apache.iceberg.ManifestFile;
 import org.apache.iceberg.ManifestListFile;
+import org.apache.iceberg.io.BulkDeletionFailureException;
+import org.apache.iceberg.io.DelegateFileIO;
 import org.apache.iceberg.io.FileIO;
 import org.apache.iceberg.io.FileInfo;
 import org.apache.iceberg.io.InputFile;
@@ -48,7 +50,9 @@ public class EncryptingFileIO implements FileIO, Serializable {
       return combine(encryptingIO.io, em);
     }
 
-    if (io instanceof SupportsPrefixOperations) {
+    if (io instanceof DelegateFileIO) {
+      return new WithDelegateFileIO((DelegateFileIO) io, em);
+    } else if (io instanceof SupportsPrefixOperations) {
       return new WithSupportsPrefixOperations((SupportsPrefixOperations) io, em);
     } else {
       return new EncryptingFileIO(io, em);
@@ -238,4 +242,28 @@ public class EncryptingFileIO implements FileIO, Serializable {
       prefixIo.deletePrefix(prefix);
     }
   }
+
+  static class WithDelegateFileIO extends EncryptingFileIO implements DelegateFileIO {
+    private final DelegateFileIO delegateFileIO;
+
+    WithDelegateFileIO(DelegateFileIO io, EncryptionManager em) {
+      super(io, em);
+      this.delegateFileIO = io;
+    }
+
+    @Override
+    public void deleteFiles(Iterable<String> pathsToDelete) throws BulkDeletionFailureException {
+      delegateFileIO.deleteFiles(pathsToDelete);
+    }
+
+    @Override
+    public Iterable<FileInfo> listPrefix(String prefix) {
+      return delegateFileIO.listPrefix(prefix);
+    }
+
+    @Override
+    public void deletePrefix(String prefix) {
+      delegateFileIO.deletePrefix(prefix);
+    }
+  }
 }
diff --git a/api/src/test/java/org/apache/iceberg/encryption/TestEncryptingFileIO.java b/api/src/test/java/org/apache/iceberg/encryption/TestEncryptingFileIO.java
index 983203e2db..bbae92434a 100644
--- a/api/src/test/java/org/apache/iceberg/encryption/TestEncryptingFileIO.java
+++ b/api/src/test/java/org/apache/iceberg/encryption/TestEncryptingFileIO.java
@@ -24,9 +24,13 @@ import static org.mockito.Mockito.verify;
 import static org.mockito.Mockito.when;
 import static org.mockito.Mockito.withSettings;
 
+import java.io.Closeable;
+import java.util.List;
 import java.util.Map;
+import org.apache.iceberg.io.DelegateFileIO;
 import org.apache.iceberg.io.FileIO;
 import org.apache.iceberg.io.FileInfo;
+import org.apache.iceberg.io.SupportsBulkOperations;
 import org.apache.iceberg.io.SupportsPrefixOperations;
 import org.junit.jupiter.api.Test;
 
@@ -39,15 +43,25 @@ public class TestEncryptingFileIO {
     FileIO fileIONoMixins = mock(FileIO.class);
     assertThat(EncryptingFileIO.combine(fileIONoMixins, em))
         .isInstanceOf(EncryptingFileIO.class)
+        .isNotInstanceOf(EncryptingFileIO.WithSupportsPrefixOperations.class)
+        .isNotInstanceOf(EncryptingFileIO.WithDelegateFileIO.class)
         .extracting(EncryptingFileIO::encryptionManager)
         .isEqualTo(em);
 
-    FileIO fileIOWithMixins =
+    FileIO fileIOWithPrefixOps =
         mock(FileIO.class, withSettings().extraInterfaces(SupportsPrefixOperations.class));
-    assertThat(EncryptingFileIO.combine(fileIOWithMixins, em))
+    assertThat(EncryptingFileIO.combine(fileIOWithPrefixOps, em))
         .isInstanceOf(EncryptingFileIO.WithSupportsPrefixOperations.class)
         .extracting(EncryptingFileIO::encryptionManager)
         .isEqualTo(em);
+
+    DelegateFileIO delegateFileIO = mock(DelegateFileIO.class);
+    assertThat(EncryptingFileIO.combine(delegateFileIO, em))
+        .isInstanceOf(EncryptingFileIO.WithDelegateFileIO.class)
+        .isInstanceOf(DelegateFileIO.class)
+        .isInstanceOf(SupportsBulkOperations.class)
+        .extracting(EncryptingFileIO::encryptionManager)
+        .isEqualTo(em);
   }
 
   @Test
@@ -67,6 +81,76 @@ public class TestEncryptingFileIO {
     verify(delegate).deletePrefix(prefix);
   }
 
+  @Test
+  public void reWrappingDelegateFileIOPreservesType() {
+    EncryptionManager em1 = mock(EncryptionManager.class);
+    EncryptionManager em2 = mock(EncryptionManager.class);
+    DelegateFileIO delegate = mock(DelegateFileIO.class);
+
+    EncryptingFileIO first = EncryptingFileIO.combine(delegate, em1);
+    EncryptingFileIO second = EncryptingFileIO.combine(first, em2);
+    assertThat(second)
+        .isInstanceOf(EncryptingFileIO.WithDelegateFileIO.class)
+        .extracting(EncryptingFileIO::encryptionManager)
+        .isEqualTo(em2);
+  }
+
+  @Test
+  public void delegateFileIODelegation() {
+    EncryptionManager em = mock(EncryptionManager.class);
+    DelegateFileIO delegate = mock(DelegateFileIO.class);
+
+    EncryptingFileIO.WithDelegateFileIO fileIO =
+        (EncryptingFileIO.WithDelegateFileIO) EncryptingFileIO.combine(delegate, em);
+
+    String prefix = "prefix";
+    Iterable<FileInfo> fileInfos = mock(Iterable.class);
+    when(delegate.listPrefix(prefix)).thenReturn(fileInfos);
+    assertThat(fileIO.listPrefix(prefix)).isEqualTo(fileInfos);
+
+    fileIO.deletePrefix(prefix);
+    verify(delegate).deletePrefix(prefix);
+
+    List<String> pathsToDelete = List.of("path1", "path2");
+    fileIO.deleteFiles(pathsToDelete);
+    verify(delegate).deleteFiles(pathsToDelete);
+  }
+
+  @Test
+  public void closeClosesUnderlyingFileIO() {
+    EncryptionManager em = mock(EncryptionManager.class);
+
+    FileIO fileIONoMixins = mock(FileIO.class);
+    EncryptingFileIO.combine(fileIONoMixins, em).close();
+    verify(fileIONoMixins).close();
+
+    SupportsPrefixOperations prefixIO = mock(SupportsPrefixOperations.class);
+    EncryptingFileIO prefixEncryptingIO = EncryptingFileIO.combine(prefixIO, em);
+    assertThat(prefixEncryptingIO)
+        .isInstanceOf(EncryptingFileIO.WithSupportsPrefixOperations.class);
+    prefixEncryptingIO.close();
+    verify(prefixIO).close();
+
+    DelegateFileIO delegate = mock(DelegateFileIO.class);
+    EncryptingFileIO delegateEncryptingIO = EncryptingFileIO.combine(delegate, em);
+    assertThat(delegateEncryptingIO).isInstanceOf(EncryptingFileIO.WithDelegateFileIO.class);
+    delegateEncryptingIO.close();
+    verify(delegate).close();
+  }
+
+  @Test
+  public void closeClosesEncryptionManagerWhenCloseable() throws Exception {
+    EncryptionManager em =
+        mock(EncryptionManager.class, withSettings().extraInterfaces(Closeable.class));
+    DelegateFileIO delegate = mock(DelegateFileIO.class);
+
+    EncryptingFileIO fileIO = EncryptingFileIO.combine(delegate, em);
+    fileIO.close();
+
+    verify(delegate).close();
+    verify((Closeable) em).close();
+  }
+
   @Test
   public void properties() {
     EncryptionManager em = mock(EncryptionManager.class);