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);