This is an automated email from the ASF dual-hosted git repository.
dtenedor pushed a commit to branch branch-4.x
in repository https://gitbox.apache.org/repos/asf/spark.git
The following commit(s) were added to refs/heads/branch-4.x by this push:
new 7b1f6f6f6a14 [SPARK-57328][SQL] Extract matchedIdentifier into a
static, resolver-parameterized helper
7b1f6f6f6a14 is described below
commit 7b1f6f6f6a14dd501165080fc7903f4c5892e940
Author: Daniel Tenedorio <[email protected]>
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 <[email protected]>
Signed-off-by: Daniel Tenedorio <[email protected]>
(cherry picked from commit d31cf1921c18ee87f385cd670d495c994b26a354)
Signed-off-by: Daniel Tenedorio <[email protected]>
---
.../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: [email protected]
For additional commands, e-mail: [email protected]