From 9884816ca2f2e2e8d930848e9e7551fef57ecadd Mon Sep 17 00:00:00 2001 From: jackylee Date: Mon, 20 Jul 2026 22:36:26 +0800 Subject: [PATCH] fix(spark): include the offending path when schema inference finds no Vortex files Signed-off-by: jackylee --- .../dev/vortex/spark/VortexDataSourceV2.java | 8 ++- .../VortexDataSourceInferSchemaTest.java | 67 +++++++++++++++++++ 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 java/vortex-spark/src/test/java/dev/vortex/spark/VortexDataSourceInferSchemaTest.java diff --git a/java/vortex-spark/src/main/java/dev/vortex/spark/VortexDataSourceV2.java b/java/vortex-spark/src/main/java/dev/vortex/spark/VortexDataSourceV2.java index a5ce4e3b1ce..bd434bcc8d2 100644 --- a/java/vortex-spark/src/main/java/dev/vortex/spark/VortexDataSourceV2.java +++ b/java/vortex-spark/src/main/java/dev/vortex/spark/VortexDataSourceV2.java @@ -62,7 +62,7 @@ public VortexDataSourceV2() { * * @param options the data source options containing file paths * @return the inferred Spark SQL schema - * @throws RuntimeException if required path options are missing + * @throws IllegalArgumentException if no Vortex files can be found under the supplied paths * @throws RuntimeException if there's an error reading the file or converting the schema */ @Override @@ -87,7 +87,11 @@ public StructType inferSchema(CaseInsensitiveStringMap options) { .findFirst(); if (firstFile.isEmpty()) { - throw new RuntimeException(String.format("UNABLE_TO_INFER_SCHEMA format: %s", shortName())); + throw new IllegalArgumentException(String.format( + "Unable to infer schema for %s: no .vortex files found under path %s. " + + "Check that the path is correct and contains at least one Vortex file, " + + "or provide an explicit schema.", + shortName(), pathToInfer)); } else { pathToInfer = firstFile.get(); } diff --git a/java/vortex-spark/src/test/java/dev/vortex/spark/VortexDataSourceInferSchemaTest.java b/java/vortex-spark/src/test/java/dev/vortex/spark/VortexDataSourceInferSchemaTest.java new file mode 100644 index 00000000000..8c103cea9b9 --- /dev/null +++ b/java/vortex-spark/src/test/java/dev/vortex/spark/VortexDataSourceInferSchemaTest.java @@ -0,0 +1,67 @@ +// SPDX-License-Identifier: Apache-2.0 +// SPDX-FileCopyrightText: Copyright the Vortex contributors + +package dev.vortex.spark; + +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.nio.file.Path; +import org.apache.spark.sql.SparkSession; +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.io.TempDir; + +/** Tests for schema inference failure reporting in {@link VortexDataSourceV2}. */ +@TestInstance(TestInstance.Lifecycle.PER_CLASS) +public final class VortexDataSourceInferSchemaTest { + + private SparkSession spark; + + @TempDir + Path tempDir; + + @BeforeAll + public void setUp() { + spark = SparkSession.builder() + .appName("VortexInferSchemaTest") + .master("local[1]") + .config("spark.driver.host", "127.0.0.1") + .config("spark.ui.enabled", "false") + .getOrCreate(); + } + + @AfterAll + public void tearDown() { + if (spark != null) { + spark.stop(); + } + } + + @Test + @DisplayName("Reading a directory without Vortex files reports the offending path") + public void inferSchemaFailureNamesThePath() { + String emptyDir = tempDir.toString(); + + Throwable thrown = assertThrows( + Throwable.class, () -> spark.read().format("vortex").load(emptyDir)); + + String allMessages = messagesOf(thrown); + assertTrue( + allMessages.contains("no .vortex files found"), + "error should explain that no Vortex files were found, got: " + allMessages); + assertTrue(allMessages.contains(emptyDir), "error should name the offending path, got: " + allMessages); + } + + /** Concatenates the messages of the whole cause chain, since Spark may wrap data source exceptions. */ + private static String messagesOf(Throwable thrown) { + StringBuilder sb = new StringBuilder(); + for (Throwable t = thrown; t != null; t = t.getCause()) { + sb.append(t.getMessage()).append('\n'); + } + return sb.toString(); + } +}