Skip to content

planner: support semi join rewrite as an alternative logical plan - #69279

Merged
ti-chi-bot[bot] merged 2 commits into
pingcap:masterfrom
AilinKid:codex/semi-join-rewrite-alternative
Jun 22, 2026
Merged

ti-chi-bot[bot] merged 2 commits into
pingcap:masterfrom
AilinKid:codex/semi-join-rewrite-alternative

Conversation

@AilinKid

@AilinKid AilinKid commented Jun 17, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: close #67467

Problem Summary:

The optimizer can use SEMI_JOIN_REWRITE when it is forced by hint or session variable, but alternative logical plan search did not try that rewrite as a competing logical plan when alternative logical plans are enabled.

What changed and how does it work?

This PR records when the first logical plan build sees a semi join that can try SEMI_JOIN_REWRITE, and adds an alternative logical-plan round that temporarily enables semi join rewrite for that build+optimize round. The new round follows the existing alternativeRound setup/cleanup pattern and restores EnableSemiJoinRewrite after the round finishes.

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

Release Notes

  • Tests

    • Expanded test coverage for semi-join rewrite operations and updated expected query execution plans for correlated subquery cases.
  • Query Optimization

    • Enhanced optimizer to evaluate alternative semi-join rewrite strategies when appropriate configuration flags are enabled, with updated plan selection logic.

@ti-chi-bot

ti-chi-bot Bot commented Jun 17, 2026

Copy link
Copy Markdown

Skipping CI for Draft Pull Request.
If you want CI signal for your change, please convert it to an actual PR.
You can still manually trigger a test run with /test all

@ti-chi-bot ti-chi-bot Bot added do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. release-note-none Denotes a PR that doesn't merit a release note. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. sig/planner SIG: Planner labels Jun 17, 2026
@AilinKid
AilinKid marked this pull request as ready for review June 17, 2026 08:13
@ti-chi-bot ti-chi-bot Bot removed the do-not-merge/work-in-progress Indicates that a PR should not merge because it is a work in progress. label Jun 17, 2026
@coderabbitai

coderabbitai Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds a new "semi-join-rewrite" alternative logical plan round to the optimizer. A new AlternativeLogicalPlanSemiJoinRewrite flag is introduced in StatementContext, set by buildSemiJoin when conditions are met, and consumed by the optimizer loop to temporarily enable EnableSemiJoinRewrite for one extra planning round. Failpoint conditions and test expectations are updated accordingly.

Changes

Semi-join rewrite alternative logical plan round

Layer / File(s) Summary
StatementContext signal
pkg/sessionctx/stmtctx/stmtctx.go
Adds AlternativeLogicalPlanSemiJoinRewrite bool field, resets it in ResetAlternativeLogicalPlanSignals, and adds MarkAlternativeLogicalPlanSemiJoinRewrite() method.
buildSemiJoin marks signal
pkg/planner/core/logical_plan_builder.go
In buildSemiJoin, calls MarkAlternativeLogicalPlanSemiJoinRewrite() when alternative plans are enabled, forced rewrite is off, and EnableSemiJoinRewrite is disabled.
Optimizer: new alternative round
pkg/planner/optimize.go
Adds shouldTrySemiJoinRewriteRound predicate, savedEnableSemiJoinRewrite for state restore, and a new "semi-join-rewrite" entry in alternativeRounds that temporarily enables EnableSemiJoinRewrite. Updates the failpoint trigger to match round.name+":"+OriginalText().
Failpoint updates and test coverage
pkg/planner/core/casetest/correlated/correlated_test.go, pkg/planner/core/casetest/rule/rule_outer_to_semi_join_test.go, pkg/planner/core/casetest/rule/testdata/correlate_suite_out.json, pkg/planner/core/casetest/rule/testdata/correlate_suite_xut.json
Updates correlated test failpoint payloads to use "non-decorrelate:" prefix; extends TestSemiJoinRewrite with an assertion that DELETE ... IN (subquery) uses IndexHashJoin without the hint; updates plan shape expectations in both correlate suite JSON files.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • pingcap/tidb#66206: Implements CorrelateSolver and correlated-subquery test suites whose planning behavior is directly affected by the new semi-join-rewrite alternative round and StatementContext flag.
  • pingcap/tidb#68752: Extends the correlate-subquery planning pipeline that this PR's new round feeds into.
  • pingcap/tidb#68796: Modifies the same alternative logical-plan infrastructure in pkg/planner/optimize.go and StatementContext that this PR extends with the semi-join-rewrite round.

Suggested labels

ok-to-test

Suggested reviewers

  • qw4990
  • windtalker
  • yudongusa

Poem

🐇 A semi-join stood all alone in the queue,
No hint to rewrite it — what should the planner do?
A new alternative round now checks for the sign,
Temporarily enables the flag — how divine!
The optimizer hops and selects the best plan,
IndexHashJoin chosen as fast as it can! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely summarizes the main change: adding support for semi join rewrite as an alternative logical plan during optimization.
Description check ✅ Passed The PR description follows the template structure with Issue Number, Problem Summary, What Changed/How It Works, and all required checklists. It adequately explains the motivation and implementation approach.
Linked Issues check ✅ Passed The PR fully addresses issue #67467 objectives: recording semi joins during logical plan build, adding an alternative round with temporary semi-join rewrite enablement, and including unit tests verifying the functionality.
Out of Scope Changes check ✅ Passed All changes are in-scope: test updates for semi-join rewrite behavior, failpoint modifications for test correctness, new session context flag for tracking alternative plans, and optimizer logic updates to implement the feature.

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

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

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 golangci-lint (2.12.2)

Command failed


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.

@AilinKid
AilinKid force-pushed the codex/semi-join-rewrite-alternative branch from e8d3d20 to 5e977c6 Compare June 17, 2026 08:28
@ti-chi-bot ti-chi-bot Bot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jun 17, 2026
@codecov

codecov Bot commented Jun 17, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 36.36364% with 14 lines in your changes missing coverage. Please review.
✅ Project coverage is 74.5896%. Comparing base (0b376ea) to head (d3aaafb).
⚠️ Report is 25 commits behind head on master.

Additional details and impacted files
@@               Coverage Diff                @@
##             master     #69279        +/-   ##
================================================
- Coverage   76.3199%   74.5896%   -1.7304%     
================================================
  Files          2041       2034         -7     
  Lines        562128     575119     +12991     
================================================
- Hits         429016     428979        -37     
- Misses       132205     145609     +13404     
+ Partials        907        531       -376     
Flag Coverage Δ
integration 41.1170% <36.3636%> (+1.4592%) ⬆️

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

Components Coverage Δ
dumpling 60.4610% <ø> (ø)
parser ∅ <ø> (∅)
br 47.5536% <ø> (-15.2186%) ⬇️
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@AilinKid
AilinKid force-pushed the codex/semi-join-rewrite-alternative branch from 5e977c6 to 56bfb2d Compare June 17, 2026 09:28
@ti-chi-bot ti-chi-bot Bot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Jun 17, 2026
@AilinKid

Copy link
Copy Markdown
Contributor Author

/test mysql-test

@qw4990

qw4990 commented Jun 18, 2026

Copy link
Copy Markdown
Contributor

/retest

@ti-chi-bot ti-chi-bot Bot added approved needs-1-more-lgtm Indicates a PR needs 1 more LGTM. labels Jun 18, 2026
@AilinKid

Copy link
Copy Markdown
Contributor Author

/retest-required

@ti-chi-bot

ti-chi-bot Bot commented Jun 20, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: qw4990, terry1purcell

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

The pull request process is described 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 lgtm and removed needs-1-more-lgtm Indicates a PR needs 1 more LGTM. labels Jun 20, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jun 20, 2026

Copy link
Copy Markdown

[LGTM Timeline notifier]

Timeline:

  • 2026-06-18 07:42:10.307280525 +0000 UTC m=+1637031.377597905: ☑️ agreed by qw4990.
  • 2026-06-20 16:59:40.640987049 +0000 UTC m=+1843281.711304439: ☑️ agreed by terry1purcell.

@qw4990

qw4990 commented Jun 22, 2026

Copy link
Copy Markdown
Contributor

/retest

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/planner/optimize.go`:
- Around line 637-640: The package-level variable `savedEnableSemiJoinRewrite`
creates a shared mutable state that can cause race conditions when multiple
sessions call the optimize function concurrently, as different sessions' cleanup
operations can overwrite each other's saved values. Move the
`savedEnableSemiJoinRewrite` variable from package scope to be local within the
optimize function or the specific invocation scope where it is used, and update
the cleanup code (around lines 678-679) that restores the
`EnableSemiJoinRewrite` value to reference the invocation-local variable instead
of the global one. This ensures each session maintains its own isolated state
during the semi-join-rewrite round.
🪄 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: ae47a475-38b3-461d-a896-5fddc2804e8c

📥 Commits

Reviewing files that changed from the base of the PR and between 234eee9 and d3aaafb.

📒 Files selected for processing (7)
  • pkg/planner/core/casetest/correlated/correlated_test.go
  • pkg/planner/core/casetest/rule/rule_outer_to_semi_join_test.go
  • pkg/planner/core/casetest/rule/testdata/correlate_suite_out.json
  • pkg/planner/core/casetest/rule/testdata/correlate_suite_xut.json
  • pkg/planner/core/logical_plan_builder.go
  • pkg/planner/optimize.go
  • pkg/sessionctx/stmtctx/stmtctx.go

Comment thread pkg/planner/optimize.go
Comment on lines +637 to +640
// savedEnableSemiJoinRewrite holds the pre-round value of
// EnableSemiJoinRewrite so setup/cleanup can restore it after the
// semi-join-rewrite round. Safe because optimize is single-threaded per session.
var savedEnableSemiJoinRewrite bool

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 | 🟠 Major | ⚡ Quick win

Scope EnableSemiJoinRewrite restore state to the current invocation.

Line [637] introduces a package-level mutable savedEnableSemiJoinRewrite; concurrent optimize calls across sessions can race and restore the wrong session value in cleanup (Lines [678-679]). This should be invocation-local, not global state.

Suggested fix
-// savedEnableSemiJoinRewrite holds the pre-round value of
-// EnableSemiJoinRewrite so setup/cleanup can restore it after the
-// semi-join-rewrite round. Safe because optimize is single-threaded per session.
-var savedEnableSemiJoinRewrite bool
-
 var alternativeRounds = []alternativeRound{
@@
 	{
 		name:    "semi-join-rewrite",
 		enabled: shouldTrySemiJoinRewriteRound,
 		setup: func(sv *variable.SessionVars) {
-			savedEnableSemiJoinRewrite = sv.EnableSemiJoinRewrite
 			sv.EnableSemiJoinRewrite = true
 		},
 		cleanup: func(sv *variable.SessionVars) {
-			sv.EnableSemiJoinRewrite = savedEnableSemiJoinRewrite
+			sv.EnableSemiJoinRewrite = false
 		},
 	},

Also applies to: 675-680

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pkg/planner/optimize.go` around lines 637 - 640, The package-level variable
`savedEnableSemiJoinRewrite` creates a shared mutable state that can cause race
conditions when multiple sessions call the optimize function concurrently, as
different sessions' cleanup operations can overwrite each other's saved values.
Move the `savedEnableSemiJoinRewrite` variable from package scope to be local
within the optimize function or the specific invocation scope where it is used,
and update the cleanup code (around lines 678-679) that restores the
`EnableSemiJoinRewrite` value to reference the invocation-local variable instead
of the global one. This ensures each session maintains its own isolated state
during the semi-join-rewrite round.

@AilinKid

Copy link
Copy Markdown
Contributor Author

/test check-dev2

@ti-chi-bot
ti-chi-bot Bot merged commit e7b15cd into pingcap:master Jun 22, 2026
34 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved lgtm release-note-none Denotes a PR that doesn't merit a release note. sig/planner SIG: Planner size/L Denotes a PR that changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

enable semi_join_rewrite in multi alternative implementation

3 participants