diff --git a/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/CommitRangeNotFoundException.java b/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/CommitRangeNotFoundException.java new file mode 100644 index 00000000000..1e31a04ce99 --- /dev/null +++ b/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/CommitRangeNotFoundException.java @@ -0,0 +1,62 @@ +/* + * Copyright (2025) The Delta Lake Project Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.delta.kernel.exceptions; + +import io.delta.kernel.annotation.Evolving; +import java.util.Optional; + +/** + * Exception thrown when Kernel cannot find any commit files in the requested version range. This + * can happen when the requested versions don't exist in the table. + * + * @since 4.1.0 + */ +@Evolving +public class CommitRangeNotFoundException extends KernelException { + + private final String tablePath; + private final long startVersion; + private final Optional endVersion; + + public CommitRangeNotFoundException( + String tablePath, long startVersion, Optional endVersion) { + super( + String.format( + "%s: Requested table changes between [%s, %s] but no log files found in the requested" + + " version range.", + tablePath, startVersion, endVersion)); + this.tablePath = tablePath; + this.startVersion = startVersion; + this.endVersion = endVersion; + } + + /** @return the table path where the commit range was not found */ + public String getTablePath() { + return tablePath; + } + + /** @return the start version of the requested commit range */ + public long getStartVersion() { + return startVersion; + } + + /** + * @return the end version of the requested commit range, or empty if no end version was specified + */ + public Optional getEndVersion() { + return endVersion; + } +} diff --git a/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/UnsupportedProtocolVersionException.java b/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/UnsupportedProtocolVersionException.java new file mode 100644 index 00000000000..9579543afa3 --- /dev/null +++ b/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/UnsupportedProtocolVersionException.java @@ -0,0 +1,69 @@ +/* + * Copyright (2025) The Delta Lake Project Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.delta.kernel.exceptions; + +import io.delta.kernel.annotation.Evolving; + +/** + * Exception thrown when Kernel encounters unsupported protocol versions. + * + * @since 4.1.0 + */ +@Evolving +public class UnsupportedProtocolVersionException extends KernelException { + + /** Enum representing the type of Delta protocol version. */ + public enum ProtocolVersionType { + /** Reader protocol version */ + READER, + /** Writer protocol version */ + WRITER + } + + private final String tablePath; + private final int version; + private final ProtocolVersionType versionType; + + public UnsupportedProtocolVersionException( + String tablePath, int version, ProtocolVersionType versionType) { + super( + String.format( + "Unsupported Delta protocol %s version: table `%s` requires %s version %s " + + "which is unsupported by this version of Delta Kernel.", + versionType.name().toLowerCase(), + tablePath, + versionType.name().toLowerCase(), + version)); + this.tablePath = tablePath; + this.version = version; + this.versionType = versionType; + } + + /** @return the table path where the unsupported protocol was encountered */ + public String getTablePath() { + return tablePath; + } + + /** @return the unsupported protocol version */ + public int getVersion() { + return version; + } + + /** @return the type of protocol version (READER or WRITER) */ + public ProtocolVersionType getVersionType() { + return versionType; + } +} diff --git a/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/UnsupportedReaderFeatureException.java b/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/UnsupportedReaderFeatureException.java new file mode 100644 index 00000000000..3fec340bf8b --- /dev/null +++ b/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/UnsupportedReaderFeatureException.java @@ -0,0 +1,38 @@ +/* + * Copyright (2025) The Delta Lake Project Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.delta.kernel.exceptions; + +import io.delta.kernel.annotation.Evolving; +import java.util.Set; + +/** + * Exception thrown when Kernel encounters unsupported reader table features. + * + * @since 4.1.0 + */ +@Evolving +public class UnsupportedReaderFeatureException extends UnsupportedTableFeatureException { + + public UnsupportedReaderFeatureException(String tablePath, Set readerFeatures) { + super( + tablePath, + readerFeatures, + String.format( + "Unsupported Delta reader features: table `%s` requires reader table features [%s] " + + "which is unsupported by this version of Delta Kernel.", + tablePath, String.join(", ", readerFeatures))); + } +} diff --git a/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/UnsupportedTableFeatureException.java b/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/UnsupportedTableFeatureException.java new file mode 100644 index 00000000000..fff1e658f15 --- /dev/null +++ b/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/UnsupportedTableFeatureException.java @@ -0,0 +1,60 @@ +/* + * Copyright (2025) The Delta Lake Project Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.delta.kernel.exceptions; + +import io.delta.kernel.annotation.Evolving; +import java.util.Collections; +import java.util.Set; + +/** + * Base exception thrown when Kernel encounters unsupported table features. + * + * @since 4.1.0 + */ +@Evolving +public class UnsupportedTableFeatureException extends KernelException { + + private final String tablePath; + private final Set unsupportedFeatures; + + public UnsupportedTableFeatureException( + String tablePath, Set unsupportedFeatures, String message) { + super(message); + this.tablePath = tablePath; + this.unsupportedFeatures = + unsupportedFeatures != null + ? Collections.unmodifiableSet(unsupportedFeatures) + : Collections.emptySet(); + } + + public UnsupportedTableFeatureException( + String tablePath, String unsupportedFeature, String message) { + this(tablePath, Collections.singleton(unsupportedFeature), message); + } + + /** + * @return the table path where the unsupported features were encountered, or null if not + * applicable + */ + public String getTablePath() { + return tablePath; + } + + /** @return an unmodifiable set of unsupported feature names */ + public Set getUnsupportedFeatures() { + return unsupportedFeatures; + } +} diff --git a/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/UnsupportedWriterFeatureException.java b/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/UnsupportedWriterFeatureException.java new file mode 100644 index 00000000000..e58b886ed14 --- /dev/null +++ b/kernel/kernel-api/src/main/java/io/delta/kernel/exceptions/UnsupportedWriterFeatureException.java @@ -0,0 +1,38 @@ +/* + * Copyright (2025) The Delta Lake Project Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.delta.kernel.exceptions; + +import io.delta.kernel.annotation.Evolving; +import java.util.Set; + +/** + * Exception thrown when Kernel encounters unsupported writer table features. + * + * @since 4.1.0 + */ +@Evolving +public class UnsupportedWriterFeatureException extends UnsupportedTableFeatureException { + + public UnsupportedWriterFeatureException(String tablePath, Set writerFeatures) { + super( + tablePath, + writerFeatures, + String.format( + "Unsupported Delta writer features: table `%s` requires writer table features [%s] " + + "which is unsupported by this version of Delta Kernel.", + tablePath, String.join(", ", writerFeatures))); + } +} diff --git a/kernel/kernel-api/src/main/java/io/delta/kernel/internal/DeltaErrors.java b/kernel/kernel-api/src/main/java/io/delta/kernel/internal/DeltaErrors.java index 47aea911ed9..117dd0322b1 100644 --- a/kernel/kernel-api/src/main/java/io/delta/kernel/internal/DeltaErrors.java +++ b/kernel/kernel-api/src/main/java/io/delta/kernel/internal/DeltaErrors.java @@ -101,14 +101,9 @@ public static KernelException timestampAfterLatestCommit( return new KernelException(message); } - public static KernelException noCommitFilesFoundForVersionRange( + public static CommitRangeNotFoundException noCommitFilesFoundForVersionRange( String tablePath, long startVersion, Optional endVersionOpt) { - String message = - String.format( - "%s: Requested table changes between [%s, %s] but no log files found in the requested" - + " version range.", - tablePath, startVersion, endVersionOpt); - return new KernelException(message); + return new CommitRangeNotFoundException(tablePath, startVersion, endVersionOpt); } public static KernelException startVersionNotFound( @@ -146,53 +141,39 @@ public static KernelException invalidVersionRange(long startVersion, long endVer } /* ------------------------ PROTOCOL EXCEPTIONS ----------------------------- */ - public static KernelException unsupportedReaderProtocol( + public static UnsupportedProtocolVersionException unsupportedReaderProtocol( String tablePath, int tableReaderVersion) { - String message = - String.format( - "Unsupported Delta protocol reader version: table `%s` requires reader version %s " - + "which is unsupported by this version of Delta Kernel.", - tablePath, tableReaderVersion); - return new KernelException(message); + return new UnsupportedProtocolVersionException( + tablePath, + tableReaderVersion, + UnsupportedProtocolVersionException.ProtocolVersionType.READER); } - public static KernelException unsupportedWriterProtocol( + public static UnsupportedProtocolVersionException unsupportedWriterProtocol( String tablePath, int tableWriterVersion) { - String message = - String.format( - "Unsupported Delta protocol writer version: table `%s` requires writer version %s " - + "which is unsupported by this version of Delta Kernel.", - tablePath, tableWriterVersion); - return new KernelException(message); + return new UnsupportedProtocolVersionException( + tablePath, + tableWriterVersion, + UnsupportedProtocolVersionException.ProtocolVersionType.WRITER); } - public static KernelException unsupportedTableFeature(String feature) { + public static UnsupportedTableFeatureException unsupportedTableFeature(String feature) { String message = String.format( "Unsupported Delta table feature: table requires feature \"%s\" " + "which is unsupported by this version of Delta Kernel.", feature); - return new KernelException(message); + return new UnsupportedTableFeatureException(null, feature, message); } - public static KernelException unsupportedReaderFeatures( + public static UnsupportedReaderFeatureException unsupportedReaderFeatures( String tablePath, Set readerFeatures) { - String message = - String.format( - "Unsupported Delta reader features: table `%s` requires reader table features [%s] " - + "which is unsupported by this version of Delta Kernel.", - tablePath, String.join(", ", readerFeatures)); - return new KernelException(message); + return new UnsupportedReaderFeatureException(tablePath, readerFeatures); } - public static KernelException unsupportedWriterFeatures( + public static UnsupportedWriterFeatureException unsupportedWriterFeatures( String tablePath, Set writerFeatures) { - String message = - String.format( - "Unsupported Delta writer feature: table `%s` requires writer table feature \"%s\" " - + "which is unsupported by this version of Delta Kernel.", - tablePath, writerFeatures); - return new KernelException(message); + return new UnsupportedWriterFeatureException(tablePath, writerFeatures); } public static KernelException columnInvariantsNotSupported() { diff --git a/kernel/kernel-api/src/test/scala/io/delta/kernel/exceptions/ExceptionSuite.scala b/kernel/kernel-api/src/test/scala/io/delta/kernel/exceptions/ExceptionSuite.scala new file mode 100644 index 00000000000..304a5a7f6ae --- /dev/null +++ b/kernel/kernel-api/src/test/scala/io/delta/kernel/exceptions/ExceptionSuite.scala @@ -0,0 +1,198 @@ +/* + * Copyright (2025) The Delta Lake Project Authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.delta.kernel.exceptions + +import java.util.Optional + +import scala.collection.JavaConverters._ + +import io.delta.kernel.exceptions.UnsupportedProtocolVersionException.ProtocolVersionType + +import org.scalatest.funsuite.AnyFunSuite + +/** + * Unit tests for Kernel exception types. + */ +class ExceptionSuite extends AnyFunSuite { + + test("UnsupportedReaderFeatureException - basic functionality") { + val tablePath = "/path/to/table" + val features = Set("feature1", "feature2").asJava + + val ex = new UnsupportedReaderFeatureException(tablePath, features) + + assert(ex.getTablePath == tablePath) + assert(ex.getUnsupportedFeatures.asScala == Set("feature1", "feature2")) + assert(ex.getMessage.contains("reader table features")) + assert(ex.getMessage.contains("feature1")) + assert(ex.getMessage.contains("feature2")) + assert(ex.isInstanceOf[UnsupportedTableFeatureException]) + } + + test("UnsupportedWriterFeatureException - basic functionality") { + val tablePath = "/path/to/table" + val features = Set("writerFeature").asJava + + val ex = new UnsupportedWriterFeatureException(tablePath, features) + + assert(ex.getTablePath == tablePath) + assert(ex.getUnsupportedFeatures.asScala == Set("writerFeature")) + assert(ex.getMessage.contains("writer table features")) + assert(ex.getMessage.contains("writerFeature")) + assert(ex.isInstanceOf[UnsupportedTableFeatureException]) + } + + test("UnsupportedTableFeatureException - custom message") { + val tablePath = "/path/to/table" + val feature = "singleFeature" + + val ex = new UnsupportedTableFeatureException(tablePath, feature, "Custom message") + + assert(ex.getTablePath == tablePath) + assert(ex.getUnsupportedFeatures.asScala == Set(feature)) + assert(ex.getMessage == "Custom message") + } + + test("UnsupportedTableFeatureException - null features returns empty set") { + val ex = new UnsupportedTableFeatureException( + "/table", + null.asInstanceOf[java.util.Set[String]], + "Message") + + assert(ex.getUnsupportedFeatures.isEmpty) + } + + test("UnsupportedTableFeatureException - returned set is unmodifiable") { + val features = new java.util.HashSet[String]() + features.add("feature1") + val ex = new UnsupportedTableFeatureException("/table", features, "Message") + + intercept[UnsupportedOperationException] { + ex.getUnsupportedFeatures.add("feature2") + } + } + + test("UnsupportedProtocolVersionException - reader version") { + val tablePath = "/path/to/table" + val version = 3 + + val ex = new UnsupportedProtocolVersionException( + tablePath, + version, + ProtocolVersionType.READER) + + assert(ex.getTablePath == tablePath) + assert(ex.getVersion == version) + assert(ex.getVersionType == ProtocolVersionType.READER) + assert(ex.getMessage.contains("reader")) + assert(ex.getMessage.contains("version 3")) + } + + test("UnsupportedProtocolVersionException - writer version") { + val tablePath = "/path/to/table" + val version = 7 + + val ex = new UnsupportedProtocolVersionException( + tablePath, + version, + ProtocolVersionType.WRITER) + + assert(ex.getTablePath == tablePath) + assert(ex.getVersion == version) + assert(ex.getVersionType == ProtocolVersionType.WRITER) + assert(ex.getMessage.contains("writer")) + assert(ex.getMessage.contains("version 7")) + } + + test("CommitRangeNotFoundException - with end version") { + val tablePath = "/path/to/table" + val startVersion = 5L + val endVersion = Optional.of(java.lang.Long.valueOf(10L)) + + val ex = new CommitRangeNotFoundException(tablePath, startVersion, endVersion) + + assert(ex.getTablePath == tablePath) + assert(ex.getStartVersion == startVersion) + assert(ex.getEndVersion == endVersion) + assert(ex.getMessage.contains("Requested table changes between [5, Optional[10]]")) + assert(ex.getMessage.contains("no log files found")) + } + + test("CommitRangeNotFoundException - without end version") { + val tablePath = "/path/to/table" + val startVersion = 100L + val endVersion = Optional.empty[java.lang.Long]() + + val ex = new CommitRangeNotFoundException(tablePath, startVersion, endVersion) + + assert(ex.getTablePath == tablePath) + assert(ex.getStartVersion == startVersion) + assert(!ex.getEndVersion.isPresent) + assert(ex.getMessage.contains("Requested table changes between [100, Optional.empty]")) + } + + test("exception hierarchy - all extend KernelException") { + assert(classOf[UnsupportedTableFeatureException].getSuperclass == classOf[KernelException]) + assert(classOf[UnsupportedReaderFeatureException].getSuperclass == + classOf[UnsupportedTableFeatureException]) + assert(classOf[UnsupportedWriterFeatureException].getSuperclass == + classOf[UnsupportedTableFeatureException]) + assert(classOf[UnsupportedProtocolVersionException].getSuperclass == + classOf[KernelException]) + assert(classOf[CommitRangeNotFoundException].getSuperclass == classOf[KernelException]) + assert(classOf[KernelException].getSuperclass == classOf[RuntimeException]) + } + + test("ProtocolVersionType enum - basic functionality") { + // Test enum values + assert(ProtocolVersionType.values().length == 2) + assert(ProtocolVersionType.valueOf("READER") == ProtocolVersionType.READER) + assert(ProtocolVersionType.valueOf("WRITER") == ProtocolVersionType.WRITER) + + // Test that it can be used in pattern matching + val versionType = ProtocolVersionType.READER + val result = versionType match { + case ProtocolVersionType.READER => "reader" + case ProtocolVersionType.WRITER => "writer" + } + assert(result == "reader") + } + + test("exception messages are informative") { + val readerEx = new UnsupportedReaderFeatureException( + "/table", + Set("deletionVectors", "columnMapping").asJava) + assert(readerEx.getMessage.contains("deletionVectors")) + assert(readerEx.getMessage.contains("columnMapping")) + assert(readerEx.getMessage.contains("/table")) + + val protocolEx = new UnsupportedProtocolVersionException( + "/another/table", + 5, + ProtocolVersionType.WRITER) + assert(protocolEx.getMessage.contains("/another/table")) + assert(protocolEx.getMessage.contains("5")) + assert(protocolEx.getMessage.contains("writer")) + + val commitRangeEx = new CommitRangeNotFoundException( + "/third/table", + 10, + Optional.of(20L)) + assert(commitRangeEx.getMessage.contains("/third/table")) + assert(commitRangeEx.getMessage.contains("10")) + assert(commitRangeEx.getMessage.contains("20")) + } +}