You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@spark.apache.org by dt...@apache.org on 2026/06/18 00:31:33 UTC

(spark) branch master updated: [SPARK-57328][SQL] Extract matchedIdentifier into a static, resolver-parameterized helper

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

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


The following commit(s) were added to refs/heads/master by this push:
     new d31cf1921c18 [SPARK-57328][SQL] Extract matchedIdentifier into a static, resolver-parameterized helper
d31cf1921c18 is described below

commit d31cf1921c18ee87f385cd670d495c994b26a354
Author: Daniel Tenedorio <da...@databricks.com>
AuthorDate: Wed Jun 17 17:31:20 2026 -0700

    [SPARK-57328][SQL] Extract matchedIdentifier into a static, resolver-parameterized helper
    
    ## What changes were proposed in this pull request?
    
    This PR refactors the relation-identifier suffix-match logic (`matchedIdentifier`) out of the
    `ResolveHints.ResolveJoinStrategyHints` rule and into a static, resolver-parameterized helper,
    so the logic can be shared without depending on fixed-point rule state.
    
    - Added a static `ResolveHints.matchedIdentifier(identInHint, identInQuery, resolver)` helper that
      takes the `resolver: Resolver` (i.e. `(String, String) => Boolean`) as an explicit parameter.
    - Moved the explanatory doc comment (describing the tail-match semantics and the catalog-independent
      comparison) onto the static helper.
    - `ResolveJoinStrategyHints` now keeps a thin private `matchedIdentifier` that delegates to the
      static helper, passing its `resolver`.
    
    Passing the `resolver` in explicitly gives both the fixed-point analyzer and the single-pass
    resolver one source of truth for hint-to-relation matching, without either having to depend on the
    other's rule state.
    
    No behavior change: the helper body is identical to the original.
    
    ## How was this patch tested?
    
    Existing join-hint resolution test coverage (e.g. join hint suites) pins the behavior; this is a
    pure extraction with no functional change.
    
    ## Was this patch authored or co-authored using generative AI tooling?
    
    No
    
    Closes #56478 from dtenedor/refactor-matched-identifier.
    
    Authored-by: Daniel Tenedorio <da...@databricks.com>
    Signed-off-by: Daniel Tenedorio <da...@databricks.com>
---
 .../spark/sql/catalyst/analysis/ResolveHints.scala | 55 +++++++++++++---------
 1 file changed, 34 insertions(+), 21 deletions(-)

diff --git a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveHints.scala b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveHints.scala
index f6d20dd71b1f..c352f0552339 100644
--- a/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveHints.scala
+++ b/sql/catalyst/src/main/scala/org/apache/spark/sql/catalyst/analysis/ResolveHints.scala
@@ -38,6 +38,38 @@ import org.apache.spark.sql.internal.SQLConf
  */
 object ResolveHints {
 
+  /**
+   * Checks if the given multi-part identifiers are matched with each other.
+   *
+   * The [[ResolveJoinStrategyHints]] rule is applied before the resolution batch in the analyzer
+   * and we cannot semantically compare them at this stage. Therefore, we follow a simple rule;
+   * they match if an identifier in a hint is a tail of an identifier in a relation. This process
+   * is independent of a session catalog (`currentDb` in [[SessionCatalog]]) and it just compares
+   * them literally.
+   *
+   * For example,
+   *  - in a query `SELECT /*+ BROADCAST(t) */ * FROM db1.t JOIN t`,
+   *    the broadcast hint will match both tables, `db1.t` and `t`,
+   *    even when the current db is `db2`.
+   *  - in a query `SELECT /*+ BROADCAST(default.t) */ * FROM default.t JOIN t`,
+   *    the broadcast hint will match the left-side table only, `default.t`.
+   *
+   * The `resolver` is passed in explicitly so that this logic can be shared by both the
+   * fixed-point analyzer and the single-pass resolver, neither of which has to depend on the
+   * other's rule state.
+   */
+  def matchedIdentifier(
+      identInHint: Seq[String],
+      identInQuery: Seq[String],
+      resolver: Resolver): Boolean = {
+    if (identInHint.length <= identInQuery.length) {
+      identInHint.zip(identInQuery.takeRight(identInHint.length))
+        .forall { case (i1, i2) => resolver(i1, i2) }
+    } else {
+      false
+    }
+  }
+
   /**
    * The list of allowed join strategy hints is defined in [[JoinStrategyHint.strategies]], and a
    * sequence of relation aliases can be specified with a join strategy hint, e.g., "MERGE(a, c)",
@@ -65,27 +97,8 @@ object ResolveHints {
             _.toUpperCase(Locale.ROOT)).contains(hintName.toUpperCase(Locale.ROOT))))
     }
 
-    // This method checks if given multi-part identifiers are matched with each other.
-    // The [[ResolveJoinStrategyHints]] rule is applied before the resolution batch
-    // in the analyzer and we cannot semantically compare them at this stage.
-    // Therefore, we follow a simple rule; they match if an identifier in a hint
-    // is a tail of an identifier in a relation. This process is independent of a session
-    // catalog (`currentDb` in [[SessionCatalog]]) and it just compares them literally.
-    //
-    // For example,
-    //  * in a query `SELECT /*+ BROADCAST(t) */ * FROM db1.t JOIN t`,
-    //    the broadcast hint will match both tables, `db1.t` and `t`,
-    //    even when the current db is `db2`.
-    //  * in a query `SELECT /*+ BROADCAST(default.t) */ * FROM default.t JOIN t`,
-    //    the broadcast hint will match the left-side table only, `default.t`.
-    private def matchedIdentifier(identInHint: Seq[String], identInQuery: Seq[String]): Boolean = {
-      if (identInHint.length <= identInQuery.length) {
-        identInHint.zip(identInQuery.takeRight(identInHint.length))
-          .forall { case (i1, i2) => resolver(i1, i2) }
-      } else {
-        false
-      }
-    }
+    private def matchedIdentifier(identInHint: Seq[String], identInQuery: Seq[String]): Boolean =
+      ResolveHints.matchedIdentifier(identInHint, identInQuery, resolver)
 
     private def extractIdentifier(r: SubqueryAlias): Seq[String] = {
       r.identifier.qualifier :+ r.identifier.name


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@spark.apache.org
For additional commands, e-mail: commits-help@spark.apache.org