Skip to content

[Do Not Merge/ POC only] planner: support rewrite join to apply rule#67693

Closed
Reminiscent wants to merge 1 commit into
pingcap:release-8.5from
Reminiscent:codex-joinToApply
Closed

[Do Not Merge/ POC only] planner: support rewrite join to apply rule#67693
Reminiscent wants to merge 1 commit into
pingcap:release-8.5from
Reminiscent:codex-joinToApply

Conversation

@Reminiscent
Copy link
Copy Markdown
Contributor

@Reminiscent Reminiscent commented Apr 10, 2026

What problem does this PR solve?

Issue Number: close #51116

Problem Summary:
support rewrite join to apply rule

What changed and how does it work?

Check List

Tests

  • Unit test
  • Integration test
  • Manual test (add detailed scripts or steps below)
  • No need to test
    • I checked and no code files have been changed.

Side effects

  • Performance regression: Consumes more CPU
  • Performance regression: Consumes more Memory
  • Breaking backward compatibility

Documentation

  • Affects user behaviors
  • Contains syntax changes
  • Contains variable changes
  • Contains experimental features
  • Changes MySQL compatibility

Release note

Please refer to Release Notes Language Style Guide to write a quality release note.

None

Summary by CodeRabbit

  • New Features
    • Introduced JOIN_TO_APPLY() optimizer hint enabling optimized execution plans for LEFT JOIN queries containing window functions, allowing the planner to convert eligible join patterns into Apply operations for improved performance.
    • Enhanced query optimizer with window-based join transformation support.

@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented Apr 10, 2026

This cherry pick PR is for a release branch and has not yet been approved by triage owners.
Adding the do-not-merge/cherry-pick-not-approved label.

To merge this cherry pick:

  1. It must be LGTMed and approved by the reviewers firstly.
  2. For pull requests to TiDB-x branches, it must have no failed tests.
  3. AFTER it has lgtm and approved labels, please wait for the cherry-pick merging approval from triage owners.
Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@ti-chi-bot ti-chi-bot Bot added release-note-none Denotes a PR that doesn't merit a release note. do-not-merge/cherry-pick-not-approved labels Apr 10, 2026
@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented Apr 10, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

This pull-request has been approved by:
Once this PR has been reviewed and has the lgtm label, please assign bornchanger, time-and-fate, yudongusa for approval. For more information see the Code Review Process.
Please ensure that each of them provides their approval before proceeding.

The full list of commands accepted by this bot can be found here.

Details Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@ti-chi-bot ti-chi-bot Bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. sig/planner SIG: Planner labels Apr 10, 2026
@tiprow
Copy link
Copy Markdown

tiprow Bot commented Apr 10, 2026

Hi @Reminiscent. Thanks for your PR.

PRs from untrusted users cannot be marked as trusted with /ok-to-test in this repo meaning untrusted PR authors can never trigger tests themselves. Collaborators can still trigger tests on the PR using /test all.

I understand the commands that are listed here.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 10, 2026

📝 Walkthrough

Walkthrough

The PR adds support for the JOIN_TO_APPLY optimizer hint, enabling the planner to convert eligible left outer joins into LogicalApply operations for improved index utilization. Changes span parser/hint infrastructure, a new optimization rule with complex eligibility validation, optimizer integration, and comprehensive test coverage.

Changes

Cohort / File(s) Summary
Hint Parser & Recognition
pkg/parser/hintparser.y, pkg/parser/misc.go
Added JOIN_TO_APPLY token definition to the hint parser grammar and mapped it to hintJoinToApply in the scanner's hint token map for recognition during parsing.
Hint Infrastructure & Propagation
pkg/util/hint/hint.go
Introduced HintJoinToApply constant and EnableJoinToApply boolean field to StmtHints. Extended ParseStmtHints to recognize and track the hint, emit duplicate-hint warnings, and enable the flag when present. Updated isStmtHint to classify join_to_apply as a statement-level hint.
Hint Parsing Test
pkg/parser/hintparser_test.go
Updated TestParseHint to include JOIN_TO_APPLY() in test input and expected AST output, verifying correct parser recognition of the new hint token.
Optimizer Rule Integration
pkg/planner/core/optimizer.go, pkg/planner/core/rule/logical_rules.go
Added FlagJoinToApply constant to the flag bitmask set and inserted the JoinToApplyRule into the optRuleList immediately after ConvertOuterToInnerJoin.
Plan Builder Initialization
pkg/planner/core/planbuilder.go
Extended PlanBuilder.Init to conditionally enable FlagJoinToApply when StmtCtx.EnableJoinToApply is true, integrating hint-level control into the optimization flag set.
JoinToApply Rule Implementation
pkg/planner/core/rule_join_to_apply.go
Added new JoinToApplyRule type implementing bottom-up rewrite of eligible left outer joins into LogicalApply. Validates that the right subtree contains no CTEs, identifies join equalities whose inner-side columns map through transparent operators (Selection, passthrough Projection) and a single LogicalWindow barrier to a LogicalDataSource, correlates outer columns with inner columns for window partition matching, and ensures the datasource has usable TiKV access paths for correlated access. Retains non-matching equalities as apply-on conditions and emits hint warnings for ineligible cases.
Hint Preservation in Encoded Plans
pkg/executor/adapter.go
Extended getEncodedPlan to preserve HintJoinToApply alongside other table-level hints when generating hint strings for encoded plan restoration.
Integration Test Cases
tests/integrationtest/t/planner/core/casetest/hint/join_to_apply.test
Added 128 lines of SQL test cases validating the JOIN_TO_APPLY() hint behavior: left joins with window functions rewriting to Apply when eligible, rejection when join keys don't match window partition columns, handling transparent operators under the window, index path availability constraints, and multi-column partition coverage scenarios.
Integration Test Expected Results
tests/integrationtest/r/planner/core/casetest/hint/join_to_apply.result
Added 261 lines of expected EXPLAIN format='brief' output covering all test scenarios, including detailed operator stacks, join conditions, filter predicates, and warning assertions for ineligible rewrite cases.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~50 minutes

Possibly related PRs

Suggested labels

sig/planner, size/XXL

Suggested reviewers

  • hawkingrei
  • qw4990
  • terry1purcell

Poem

🐰 A hint to transform joins with care,
Through windows we peek with indexed flair,
No decoration masks the way,
JOIN_TO_APPLY saves the day! ✨

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 53.33% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check ❓ Inconclusive The PR description follows the template with a valid issue reference, but 'What changed and how does it work?' section is empty, leaving implementation details unexplained. Fill in the 'What changed and how does it work?' section with a detailed explanation of the implementation and changes made in this PR.
✅ Passed checks (3 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The PR successfully implements the join-to-apply optimization rule addressing issue #51116, adding hint support, parser grammar, optimizer integration, and comprehensive integration tests.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the join-to-apply rewrite feature: hint parsing, optimizer rules, statement context handling, and integration tests validating the feature.
Title check ✅ Passed The PR title clearly indicates the main change: adding support for a 'join to apply' rewrite rule in the planner, which aligns with the substantial changes across parser, executor, planner, and hint components.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Reminiscent
Copy link
Copy Markdown
Contributor Author

/ok-to-test

@ti-chi-bot ti-chi-bot Bot added the ok-to-test Indicates a PR is ready to be tested. label Apr 10, 2026
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/integrationtest/t/planner/core/casetest/hint/join_to_apply.test (1)

120-128: Add a no-hint control for the aggregation barrier case.

This only snapshots the hinted plan, so it does not actually prove the new rule is a no-op here. Adding the same EXPLAIN without JOIN_TO_APPLY() would make regressions unambiguous if this barrier shape ever starts rewriting indirectly.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/integrationtest/t/planner/core/casetest/hint/join_to_apply.test` around
lines 120 - 128, Add a no-hint control by duplicating the EXPLAIN block without
the JOIN_TO_APPLY() hint so the test asserts the hinted plan is unchanged versus
the baseline; specifically, add an additional "explain format = 'brief'" +
SELECT ... (the same query joining jt_outer and the aggregated jt_inner using
max and group by i.k) but remove the /*+ JOIN_TO_APPLY() */ hint so the test
compares the hinted snapshot against the no-hint baseline and will catch any
indirect rewriting of the aggregation barrier.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pkg/planner/core/rule_join_to_apply.go`:
- Around line 93-145: tryRewriteJoinToApply currently emits statement-level
warnings via join.SCtx().GetSessionVars().StmtCtx.SetHintWarning during per-join
checks (e.g., when no correlatable keys or no usable TiKV path), which causes
misleading warnings for mixed queries; stop emitting warnings inside
tryRewriteJoinToApply and instead return applicability info to the caller (e.g.,
change tryRewriteJoinToApply signature to return (base.LogicalPlan, bool,
string) or an enum/reason) so the traversal driver can aggregate results across
all joins and call StmtCtx.SetHintWarning only once if no join in the whole
statement is eligible; update call sites that invoke tryRewriteJoinToApply to
collect reasons/flags and emit the single statement-level warning after the full
rewrite pass.

---

Nitpick comments:
In `@tests/integrationtest/t/planner/core/casetest/hint/join_to_apply.test`:
- Around line 120-128: Add a no-hint control by duplicating the EXPLAIN block
without the JOIN_TO_APPLY() hint so the test asserts the hinted plan is
unchanged versus the baseline; specifically, add an additional "explain format =
'brief'" + SELECT ... (the same query joining jt_outer and the aggregated
jt_inner using max and group by i.k) but remove the /*+ JOIN_TO_APPLY() */ hint
so the test compares the hinted snapshot against the no-hint baseline and will
catch any indirect rewriting of the aggregation barrier.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 82fcb7d4-eb23-4a29-8bf7-d06da70ad147

📥 Commits

Reviewing files that changed from the base of the PR and between b6119a9 and 5e5a886.

📒 Files selected for processing (12)
  • pkg/executor/adapter.go
  • pkg/parser/hintparser.go
  • pkg/parser/hintparser.y
  • pkg/parser/hintparser_test.go
  • pkg/parser/misc.go
  • pkg/planner/core/optimizer.go
  • pkg/planner/core/planbuilder.go
  • pkg/planner/core/rule/logical_rules.go
  • pkg/planner/core/rule_join_to_apply.go
  • pkg/util/hint/hint.go
  • tests/integrationtest/r/planner/core/casetest/hint/join_to_apply.result
  • tests/integrationtest/t/planner/core/casetest/hint/join_to_apply.test

Comment on lines +93 to +145
func tryRewriteJoinToApply(join *logicalop.LogicalJoin) (base.LogicalPlan, bool) {
if join.JoinType != logicalop.LeftOuterJoin || !join.SCtx().GetSessionVars().StmtCtx.EnableJoinToApply {
return join, false
}

if containsLogicalCTE(join.Children()[1]) {
// Apply under CTE needs extra builder-time CTE bookkeeping that this
// rule intentionally does not reproduce in the first rollout.
join.SCtx().GetSessionVars().StmtCtx.SetHintWarning("JOIN_TO_APPLY() is inapplicable because the right subtree contains a CTE.")
return join, false
}

var (
window *logicalop.LogicalWindow
dataSource *logicalop.DataSource
)
correlatableKeys := make([]correlatableJoinKey, 0, len(join.EqualConditions))
remainingKeys := make([]*expression.ScalarFunction, 0, len(join.EqualConditions))
for _, eq := range join.EqualConditions {
outerCol, innerCol := extractJoinKeyCols(eq, join.Children()[0].Schema(), join.Children()[1].Schema())
if outerCol == nil || innerCol == nil {
remainingKeys = append(remainingKeys, eq)
continue
}

trace, ok := traceWindowBypassPath(join.Children()[1], innerCol, false)
if !ok {
remainingKeys = append(remainingKeys, eq)
continue
}
if window == nil {
window = trace.window
dataSource = trace.dataSource
} else if window != trace.window || dataSource != trace.dataSource {
remainingKeys = append(remainingKeys, eq)
continue
}
correlatableKeys = append(correlatableKeys, correlatableJoinKey{
eq: eq,
outerCol: outerCol,
innerCol: innerCol,
resolvedCol: trace.partitionCol,
accessCol: trace.accessCol,
})
}

if len(correlatableKeys) == 0 {
join.SCtx().GetSessionVars().StmtCtx.SetHintWarning("JOIN_TO_APPLY() is inapplicable because no join key matches the window partition columns.")
return join, false
}
if !dataSourceHasUsableTiKVAccessPath(dataSource, correlatableKeys) {
join.SCtx().GetSessionVars().StmtCtx.SetHintWarning("JOIN_TO_APPLY() is inapplicable because the inner datasource has no usable TiKV access path on the correlated key.")
return join, false
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Defer JOIN_TO_APPLY() warnings until the rewrite pass finishes.

JOIN_TO_APPLY() is a statement-level hint, but this method warns on each left outer join that it cannot rewrite. In a mixed query, one join can be rewritten successfully while another unrelated left join still leaves a misleading “inapplicable” warning behind. Please track applicability across the whole traversal and only warn when the statement ends up with no eligible rewrite target.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/planner/core/rule_join_to_apply.go` around lines 93 - 145,
tryRewriteJoinToApply currently emits statement-level warnings via
join.SCtx().GetSessionVars().StmtCtx.SetHintWarning during per-join checks
(e.g., when no correlatable keys or no usable TiKV path), which causes
misleading warnings for mixed queries; stop emitting warnings inside
tryRewriteJoinToApply and instead return applicability info to the caller (e.g.,
change tryRewriteJoinToApply signature to return (base.LogicalPlan, bool,
string) or an enum/reason) so the traversal driver can aggregate results across
all joins and call StmtCtx.SetHintWarning only once if no join in the whole
statement is eligible; update call sites that invoke tryRewriteJoinToApply to
collect reasons/flags and emit the single statement-level warning after the full
rewrite pass.

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 10, 2026

Codecov Report

❌ Patch coverage is 61.84739% with 95 lines in your changes missing coverage. Please review.
⚠️ Please upload report for BASE (release-8.5@d64cd8c). Learn more about missing BASE report.

Additional details and impacted files
@@               Coverage Diff                @@
##             release-8.5     #67693   +/-   ##
================================================
  Coverage               ?   42.3257%           
================================================
  Files                  ?       1534           
  Lines                  ?     427355           
  Branches               ?          0           
================================================
  Hits                   ?     180881           
  Misses                 ?     230374           
  Partials               ?      16100           
Flag Coverage Δ
integration 42.3257% <61.8473%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

Components Coverage Δ
dumpling ∅ <0.0000%> (?)
parser ∅ <0.0000%> (?)
br 0.1802% <0.0000%> (?)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented Apr 10, 2026

@Reminiscent: The following tests failed, say /retest to rerun all failed tests or /retest-required to rerun all mandatory failed tests:

Test name Commit Details Required Rerun command
idc-jenkins-ci-tidb/build 5e5a886 link true /test build
idc-jenkins-ci-tidb/unit-test 5e5a886 link true /test unit-test

Full PR test history. Your PR dashboard.

Details

Instructions for interacting with me using PR comments are available here. If you have questions or suggestions related to my behavior, please file an issue against the kubernetes-sigs/prow repository. I understand the commands that are listed here.

@terry1purcell
Copy link
Copy Markdown
Contributor

/hold

DO NOT unhold this without my approval.

@ti-chi-bot ti-chi-bot Bot added the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Apr 11, 2026
@Reminiscent Reminiscent changed the title [DNM] planner: support rewrite join to apply rule [Do Not Merge/ POC only] planner: support rewrite join to apply rule Apr 11, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/cherry-pick-not-approved do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. ok-to-test Indicates a PR is ready to be tested. release-note-none Denotes a PR that doesn't merit a release note. sig/planner SIG: Planner size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants