You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@sedona.apache.org by ji...@apache.org on 2026/09/28 05:46:27 UTC

(sedona) branch master updated: [GH-3411] Give a clear error when GeoParquet has no input files to read (#3412)

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

jiayuasu pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/sedona.git


The following commit(s) were added to refs/heads/master by this push:
     new 26546451ee6 [GH-3411] Give a clear error when GeoParquet has no input files to read (#3412)
26546451ee6 is described below

commit 26546451ee689c39a0a95117bf73b82bfb00e2d9
Author: Jia Yu <ji...@apache.org>
AuthorDate: Sun Sep 27 22:46:22 2026 -0700

    [GH-3411] Give a clear error when GeoParquet has no input files to read (#3412)
---
 .../datasources/geoparquet/GeoParquetUtils.scala   | 18 +++++++++-
 .../org/apache/sedona/sql/geoparquetIOTests.scala  | 42 ++++++++++++++++++++++
 2 files changed, 59 insertions(+), 1 deletion(-)

diff --git a/spark/common/src/main/scala/org/apache/spark/sql/execution/datasources/geoparquet/GeoParquetUtils.scala b/spark/common/src/main/scala/org/apache/spark/sql/execution/datasources/geoparquet/GeoParquetUtils.scala
index 220aa737120..043fa730d08 100644
--- a/spark/common/src/main/scala/org/apache/spark/sql/execution/datasources/geoparquet/GeoParquetUtils.scala
+++ b/spark/common/src/main/scala/org/apache/spark/sql/execution/datasources/geoparquet/GeoParquetUtils.scala
@@ -20,7 +20,7 @@ package org.apache.spark.sql.execution.datasources.geoparquet
 
 import org.apache.hadoop.fs.{FileStatus, Path}
 import org.apache.parquet.hadoop.ParquetFileWriter
-import org.apache.spark.sql.SparkSession
+import org.apache.spark.sql.{AnalysisException, SparkSession}
 import org.apache.spark.sql.execution.datasources.geoparquet.internal.PortableSQLConf
 import org.apache.spark.sql.types.StructType
 
@@ -31,6 +31,14 @@ object GeoParquetUtils {
       sparkSession: SparkSession,
       parameters: Map[String, String],
       files: Seq[FileStatus]): Option[StructType] = {
+    if (files.isEmpty) {
+      // Spark removes the path from `parameters` before calling us, so a missing path (PySpark's
+      // load(None) calls load() with no paths) and a path without any files look the same here.
+      throw new GeoParquetNoInputFilesException(
+        "Unable to infer schema for GeoParquet: no input files found. Either no path was given " +
+          "(for example, load(None)) or the path contains no GeoParquet files. Pass a path to " +
+          "GeoParquet files, or specify the schema manually.")
+    }
     val conf = new PortableSQLConf(sparkSession.sessionState.conf)
     val parquetOptions = new internal.ParquetOptions(parameters, conf)
     val shouldMergeSchemas = parquetOptions.mergeSchema
@@ -127,3 +135,11 @@ object GeoParquetUtils {
     }
   }
 }
+
+/**
+ * Raised when there are no files to infer a GeoParquet schema from. It is an
+ * [[AnalysisException]] like the UNABLE_TO_INFER_SCHEMA error Spark raises in the same situation,
+ * so existing handlers keep working. Spark 4 only lets subclasses set the message directly.
+ */
+private[geoparquet] class GeoParquetNoInputFilesException(message: String)
+    extends AnalysisException(message)
diff --git a/spark/common/src/test/scala/org/apache/sedona/sql/geoparquetIOTests.scala b/spark/common/src/test/scala/org/apache/sedona/sql/geoparquetIOTests.scala
index 37afe6ad185..61b399ce5a1 100644
--- a/spark/common/src/test/scala/org/apache/sedona/sql/geoparquetIOTests.scala
+++ b/spark/common/src/test/scala/org/apache/sedona/sql/geoparquetIOTests.scala
@@ -25,6 +25,7 @@ import org.apache.parquet.hadoop.ParquetFileReader
 import org.apache.parquet.hadoop.util.HadoopInputFile
 import org.apache.spark.SparkException
 import org.apache.spark.scheduler.{SparkListener, SparkListenerTaskEnd}
+import org.apache.spark.sql.AnalysisException
 import org.apache.spark.sql.Row
 import org.apache.spark.sql.SaveMode
 import org.apache.spark.sql.execution.datasources.geoparquet.{Covering, GeoParquetMetaData, GeometryFieldMetaData}
@@ -44,6 +45,7 @@ import org.scalatest.BeforeAndAfterAll
 
 import java.io.File
 import java.nio.ByteOrder
+import java.nio.file.Files
 import java.util.Collections
 import java.util.concurrent.atomic.AtomicLong
 import java.time.LocalDateTime
@@ -62,6 +64,12 @@ class geoparquetIOTests extends TestBaseScala with BeforeAndAfterAll {
   val overtureBBOX: String = resourceFolder + "geoparquet/overture/bbox.geoparquet"
   override def afterAll(): Unit = FileUtils.deleteDirectory(new File(geoparquetoutputlocation))
 
+  private def withEmptyDirectory(f: String => Unit): Unit = {
+    val dir = Files.createTempDirectory("geoparquet-empty").toFile
+    try f(dir.getPath)
+    finally FileUtils.deleteDirectory(dir)
+  }
+
   describe("GeoParquet IO tests") {
     it("GEOPARQUET Test example1 i.e. naturalearth_lowers dataset's Read and Write") {
       val df = sparkSession.read.format("geoparquet").load(geoparquetdatalocation1)
@@ -237,6 +245,40 @@ class geoparquetIOTests extends TestBaseScala with BeforeAndAfterAll {
       assert(rows(0).getAs[AnyRef]("g1").isInstanceOf[Geometry])
     }
 
+    it("GeoParquet read without a path should say that no path was given") {
+      // PySpark's load(None) calls the JVM load() with no paths.
+      val e = intercept[AnalysisException] {
+        sparkSession.read.format("geoparquet").load()
+      }
+      assert(e.getMessage.contains("no input files found"))
+      assert(e.getMessage.contains("no path was given"))
+    }
+
+    it("GeoParquet read of an empty directory should say that no input files were found") {
+      withEmptyDirectory { dir =>
+        val e = intercept[AnalysisException] {
+          sparkSession.read.format("geoparquet").load(dir)
+        }
+        assert(e.getMessage.contains("no input files found"))
+      }
+    }
+
+    it("GeoParquet read of an empty directory with a user-specified schema returns no rows") {
+      withEmptyDirectory { dir =>
+        val df = sparkSession.read.format("geoparquet").schema("id INT").load(dir)
+        assert(df.count() == 0)
+      }
+    }
+
+    it("GeoParquet read of a glob that matches nothing should report the missing path") {
+      withEmptyDirectory { dir =>
+        val e = intercept[AnalysisException] {
+          sparkSession.read.format("geoparquet").load(dir + "/*.parquet")
+        }
+        assert(e.getMessage.contains("Path does not exist"))
+      }
+    }
+
     it("GeoParquet save should work with empty dataframes") {
       val schema = StructType(
         Seq(