From be8ad3831117d5facdddeb2ea42fc82d7c235e2c Mon Sep 17 00:00:00 2001 From: Jia Yu Date: Tue, 30 Jun 2026 14:05:04 -0700 Subject: [PATCH] [GH-3089] Fix empty-envelope false candidate and RS_DWithin EXPLAIN label in raster distance join MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two issues in the raster distance-join machinery built around RS_DWithin (GH-3089), both confined to spark/common: 1. Empty-envelope false candidate. When a raster or geometry input is NULL, the join substitutes an empty GeometryCollection. expandRasterFilterEnvelope then expanded that geometry's degenerate envelope by `distance`, producing a non-empty R-tree filter envelope that pulls the row in as a coarse-filter candidate (and can yield a NaN-bounded envelope). Return the empty shape unchanged so the filter excludes it. 2. Misleading EXPLAIN output. The raster distance branch of BroadcastIndexJoinExec.simpleString printed "RS_Distance(left, right) < r" — a function that does not exist in Sedona. Print "RS_DWithin(left, right, r)" to match the actual join predicate. --- .../sedona_sql/strategy/join/BroadcastIndexJoinExec.scala | 2 +- .../sql/sedona_sql/strategy/join/TraitJoinQueryBase.scala | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/BroadcastIndexJoinExec.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/BroadcastIndexJoinExec.scala index c210debb744..f67fea76f00 100644 --- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/BroadcastIndexJoinExec.scala +++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/BroadcastIndexJoinExec.scala @@ -122,7 +122,7 @@ case class BroadcastIndexJoinExec( case (None, _, false) => s"ST_$spatialPredicate($windowExpression, $objectExpression)" case (None, _, true) => s"RS_$spatialPredicate($windowExpression, $objectExpression)" case (Some(r), _, true) => - s"RS_Distance($windowExpression, $objectExpression) < $r" + s"RS_DWithin($windowExpression, $objectExpression, $r)" } override def simpleString(maxFields: Int): String = diff --git a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/TraitJoinQueryBase.scala b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/TraitJoinQueryBase.scala index c59c4f464a4..99e31498737 100644 --- a/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/TraitJoinQueryBase.scala +++ b/spark/common/src/main/scala/org/apache/spark/sql/sedona_sql/strategy/join/TraitJoinQueryBase.scala @@ -188,6 +188,12 @@ trait TraitJoinQueryBase { private[join] def expandRasterFilterEnvelope( baseShape: Geometry, distance: Double): Geometry = { + // An empty shape (e.g. the empty GeometryCollection substituted for a NULL raster/geometry) + // must stay empty: expanding its degenerate envelope by `distance` would yield a non-empty + // filter geometry that spuriously matches rows the predicate should never join. + if (baseShape.isEmpty) { + return baseShape + } val envelope = baseShape.getEnvelopeInternal val touchesPole = envelope.getMaxY >= 90.0 || envelope.getMinY <= -90.0 val tooWide = envelope.getWidth > 180.0