Skip to content

planner: index correlated range predicates | tidb-test=pr/2696 - #66719

Merged
ti-chi-bot[bot] merged 6 commits into
pingcap:masterfrom
terry1purcell:correlaterange
Mar 8, 2026
Merged

ti-chi-bot[bot] merged 6 commits into
pingcap:masterfrom
terry1purcell:correlaterange

Conversation

@terry1purcell

@terry1purcell terry1purcell commented Mar 5, 2026 •

Copy link
Copy Markdown
Contributor

What problem does this PR solve?

Issue Number: close #66720

Problem Summary:

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

  • Improvements

    • Planner now treats trailing range predicates that compare index columns to correlated columns (LT/GT/LE/GE) as index access conditions, yielding range-based index scans and more precise execution plans.
  • Tests

    • Added and updated integration tests and expected plans to validate range-scan access paths, explain output, and query correctness across correlated-predicate scenarios.

@ti-chi-bot ti-chi-bot Bot added do-not-merge/needs-linked-issue release-note-none Denotes a PR that doesn't merit a release note. labels Mar 5, 2026
@pantheon-ai

pantheon-ai Bot commented Mar 5, 2026 •

Copy link
Copy Markdown

Review Complete

Findings: 2 issues
Posted: 2
Duplicates/Skipped: 0

ℹ️ Learn more details on Pantheon AI.

@ti-chi-bot ti-chi-bot Bot added sig/planner SIG: Planner size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Mar 5, 2026
@coderabbitai

coderabbitai Bot commented Mar 5, 2026 •

Copy link
Copy Markdown

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Detects correlated-column range predicates (LT/GT/LE/GE) and promotes them to index access conditions; adds a test verifying this behavior and updates planner/executor test expectations and plan outputs to reflect IndexRangeScan usage.

Changes

Cohort / File(s) Summary
New test
pkg/planner/core/integration_test.go
Added TestCorColRangePredicateAccess to verify correlated range predicates are treated as index access conditions and to assert correct results and explain output.
Planner util
pkg/planner/util/path.go
Extended SplitCorColAccessCondFromFilters to detect trailing range predicates comparing an index column to a correlated column; added isColRangeCorCol(...) and adjusted access/filter splitting with collation-aware checks.
Executor tests / plans
pkg/executor/partition_table_test.go, tests/integrationtest/r/planner/core/casetest/rule/rule_join_reorder.result, pkg/planner/core/casetest/indexmerge/testdata/index_merge_suite_out.json, pkg/planner/core/casetest/indexmerge/testdata/index_merge_suite_xut.json
Updated expected plans and test outputs: replace IndexFullScan with IndexRangeScan, add/adjust range annotations and cost estimates where correlated range predicates constrain scans.
Module metadata
go.mod
Minor go.mod updates.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested labels

ok-to-test

Suggested reviewers

  • guo-shaoge
  • mjonss

Poem

🐇 I found a range where correlations play,

I nudged it forward so indexes may sway.
Plans now whisper ranges, tidy and bright,
My little hops made queries take flight.
Thump-thump — a rabbit's joy in code tonight.

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 71.43% 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 description follows the repository template and includes the required Issue Number linking to #66720. However, the 'Problem Summary' and 'What changed and how does it work?' sections are empty, lacking explanation of the changes. Add detailed explanations in 'Problem Summary' and 'What changed and how does it work?' sections to clarify the implementation approach and changes made.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The PR title clearly identifies the main change: adding support for indexing correlated range predicates in the planner, which aligns with the primary objective from issue #66720.
Linked Issues check ✅ Passed The code changes directly implement the objective from issue #66720: adding support for correlated range predicates (LT/GT/LE/GE) in index access conditions, with new test coverage validating the enhanced functionality.
Out of Scope Changes check ✅ Passed All changes are directly related to supporting correlated range predicates: core planner logic, integration tests, test expectation updates, and plan output adjustments. No unrelated modifications detected.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@tiprow

tiprow Bot commented Mar 5, 2026

Copy link
Copy Markdown

Hi @terry1purcell. 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 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 the current code and only fix it if needed.

Inline comments:
In `@pkg/planner/core/integration_test.go`:
- Around line 1583-1667: The test TestCorColRangePredicateAccess claims to cover
LT/GT/LE/GE but omits GE; add a GE case mirroring the LT/GT/LE blocks: run an
"explain format='brief' select ... where exists (select /*+ NO_DECORRELATE() */
1 from t1 t1b where t1b.b = t1a.b and t1b.a >= t1a.a)" into the existing rows
variable, scan rows like the other cases for info := fmt.Sprintf("%v", row[4])
and assert strings.Contains(info, "decided by") && strings.Contains(info,
"ge(test.t1.a, test.t1.a)") with require.True and caller message, and add a
small correctness query (similar to the LE/GT correctness checks) that selects
the matching rows and calls .Check(...) to validate results.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5473b2ca-0e0b-485d-a1ef-a58c592cfdd7

📥 Commits

Reviewing files that changed from the base of the PR and between 4d4043c and c21b569.

📒 Files selected for processing (2)
  • pkg/planner/core/integration_test.go
  • pkg/planner/util/path.go

Comment thread pkg/planner/core/integration_test.go
@terry1purcell terry1purcell changed the title planner: index correlated range predicates planner: index correlated range predicates | tidb-test/pr=2696 Mar 5, 2026
@terry1purcell terry1purcell changed the title planner: index correlated range predicates | tidb-test/pr=2696 planner: index correlated range predicates | tidb-test=pr/2696 Mar 5, 2026
foundRangeAccess := false
for _, row := range rows {
info := fmt.Sprintf("%v", row[4])
if strings.Contains(info, "decided by") && strings.Contains(info, "lt(test.t1.a, test.t1.a)") {

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P2] Test asserts raw EXPLAIN string fragments — brittle CI

Why: strings.Contains matching against human-readable EXPLAIN FORMAT='brief' output means any unrelated formatting change (operator rename, column-qualification change) can silently break this test despite correct plan semantics.

Evidence: pkg/planner/core/integration_test.go:1607 — strings.Contains(info, "lt(test.t1.a, test.t1.a)"). The same fragile pattern also appears at lines 1637 (gt(...)) and 1659 (le(...)). Consider instead asserting on a structured plan representation or a purpose-built test helper that is decoupled from EXPLAIN's exact text format.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This is not a critical concern. Many tests use this type of string comparison to search for text within the plan.

@pantheon-ai pantheon-ai Bot Mar 5, 2026 •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Fair point — strings.Contains on EXPLAIN FORMAT='brief' output is a well-established convention throughout TiDB's integration tests, so this is consistent with the project's existing style. The P2 flag was a low-priority heads-up rather than a blocker. No action needed here.

ℹ️ Learn more details on Pantheon AI.

Comment thread pkg/planner/util/path.go

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
pkg/executor/partition_table_test.go (1)

1570-1581: ⚠️ Potential issue | 🟠 Major

IndexLookUp plan assertions still validate tinner instead of partition tables.

Line 1570 and Line 1613 EXPLAIN queries are still against tinner, so the updated expectations on Line 1578 and Line 1621 don’t actually verify the hash/range partition IndexLookUp path described by those test sections.

🔧 Proposed test correction
- tk.MustQuery(`explain format='brief' select * from touter where touter.a > (select sum(tinner.b) from tinner use index(a) where tinner.a>touter.b)`).Check(testkit.Rows(
+ tk.MustQuery(`explain format='brief' select * from touter where touter.a > (select sum(thash.b) from thash use index(a) where thash.a>touter.b)`).Check(testkit.Rows(
...
- `      ├─Selection(Build) 80000000.00 cop[tikv]  gt(test_parallel_apply.tinner.a, test_parallel_apply.touter.b)`,
- `      │ └─IndexRangeScan 100000000.00 cop[tikv] table:tinner, index:a(a) range: decided by [gt(test_parallel_apply.tinner.a, test_parallel_apply.touter.b)], keep order:false, stats:pseudo`,
- `      └─HashAgg(Probe) 10000.00 cop[tikv]  funcs:sum(test_parallel_apply.tinner.b)->Column#11`,
- `        └─TableRowIDScan 80000000.00 cop[tikv] table:tinner keep order:false, stats:pseudo`))
+ `      ├─Selection(Build) 80000000.00 cop[tikv]  gt(test_parallel_apply.thash.a, test_parallel_apply.touter.b)`,
+ `      │ └─IndexRangeScan 100000000.00 cop[tikv] table:thash, index:a(a) range: decided by [gt(test_parallel_apply.thash.a, test_parallel_apply.touter.b)], keep order:false, stats:pseudo`,
+ `      └─HashAgg(Probe) 10000.00 cop[tikv]  funcs:sum(test_parallel_apply.thash.b)->Column#11`,
+ `        └─TableRowIDScan 80000000.00 cop[tikv] table:thash keep order:false, stats:pseudo`))

- tk.MustQuery(`explain format='brief' select * from touter where touter.a > (select sum(tinner.b) from tinner use index(a) where tinner.a>touter.b)`).Check(testkit.Rows(
+ tk.MustQuery(`explain format='brief' select * from touter where touter.a > (select sum(trange.b) from trange use index(a) where trange.a>touter.b)`).Check(testkit.Rows(
...
- `      ├─Selection(Build) 80000000.00 cop[tikv]  gt(test_parallel_apply.tinner.a, test_parallel_apply.touter.b)`,
- `      │ └─IndexRangeScan 100000000.00 cop[tikv] table:tinner, index:a(a) range: decided by [gt(test_parallel_apply.tinner.a, test_parallel_apply.touter.b)], keep order:false, stats:pseudo`,
- `      └─HashAgg(Probe) 10000.00 cop[tikv]  funcs:sum(test_parallel_apply.tinner.b)->Column#11`,
- `        └─TableRowIDScan 80000000.00 cop[tikv] table:tinner keep order:false, stats:pseudo`))
+ `      ├─Selection(Build) 80000000.00 cop[tikv]  gt(test_parallel_apply.trange.a, test_parallel_apply.touter.b)`,
+ `      │ └─IndexRangeScan 100000000.00 cop[tikv] table:trange, index:a(a) range: decided by [gt(test_parallel_apply.trange.a, test_parallel_apply.touter.b)], keep order:false, stats:pseudo`,
+ `      └─HashAgg(Probe) 10000.00 cop[tikv]  funcs:sum(test_parallel_apply.trange.b)->Column#11`,
+ `        └─TableRowIDScan 80000000.00 cop[tikv] table:trange keep order:false, stats:pseudo`))

As per coding guidelines **/*_test.go: MUST add a regression test and verify it fails before fix and passes after fix for bug fixes.

Also applies to: 1613-1624

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

In `@pkg/executor/partition_table_test.go` around lines 1570 - 1581, The EXPLAIN
assertions still reference the non-partition table `tinner`; update the two
EXPLAIN queries and their expected output to target the partitioned tables used
in the surrounding tests (e.g., `thash` for the hash-partition test and `trange`
for the range-partition test) so the IndexLookUp/IndexRangeScan lines assert
against `table:thash`/`table:trange` and
`test_parallel_apply.thash.*`/`test_parallel_apply.trange.*` columns instead of
`tinner`; modify the expected strings (the lines containing "table:tinner" and
related selection/index text) to match the partition table names and ensure the
inner child comment about IndexLookUp still aligns with the `Apply` child
structure (methods: the EXPLAIN queries and their Check expectations around
IndexLookUp/IndexRangeScan/Selection/HashAgg).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@tests/integrationtest/r/planner/core/casetest/rule/rule_join_reorder.result`:
- Line 4625: Add a regression test to rule_join_reorder.test that covers a NULL
outer correlated value (e.g., create rows where t1.a IS NULL and run the
correlated range query such as "select min(t1.a) from t2 where t2.a > t1.a") so
the planner sees col > NULL and should not treat it as a usable range predicate;
update the test input in rule_join_reorder.test to include the NULL-case and
then regenerate the snapshot file
tests/integrationtest/r/planner/core/casetest/rule/rule_join_reorder.result so
the expected plan no longer shows the correlated predicate being used as an
index range.

---

Outside diff comments:
In `@pkg/executor/partition_table_test.go`:
- Around line 1570-1581: The EXPLAIN assertions still reference the
non-partition table `tinner`; update the two EXPLAIN queries and their expected
output to target the partitioned tables used in the surrounding tests (e.g.,
`thash` for the hash-partition test and `trange` for the range-partition test)
so the IndexLookUp/IndexRangeScan lines assert against
`table:thash`/`table:trange` and
`test_parallel_apply.thash.*`/`test_parallel_apply.trange.*` columns instead of
`tinner`; modify the expected strings (the lines containing "table:tinner" and
related selection/index text) to match the partition table names and ensure the
inner child comment about IndexLookUp still aligns with the `Apply` child
structure (methods: the EXPLAIN queries and their Check expectations around
IndexLookUp/IndexRangeScan/Selection/HashAgg).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 28639803-65b2-4d35-87d6-2a1826e476b5

📥 Commits

Reviewing files that changed from the base of the PR and between 483fe31 and e9a7d36.

📒 Files selected for processing (2)
  • pkg/executor/partition_table_test.go
  • tests/integrationtest/r/planner/core/casetest/rule/rule_join_reorder.result

@codecov

codecov Bot commented Mar 5, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.30435% with 4 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.8330%. Comparing base (4d4043c) to head (6743240).
⚠️ Report is 15 commits behind head on master.

Additional details and impacted files
@@               Coverage Diff                @@
##             master     #66719        +/-   ##
================================================
+ Coverage   77.6930%   77.8330%   +0.1400%     
================================================
  Files          2008       1929        -79     
  Lines        549479     540774      -8705     
================================================
- Hits         426907     420901      -6006     
+ Misses       120907     119858      -1049     
+ Partials       1665         15      -1650     
Flag Coverage Δ
integration 41.9574% <86.9565%> (-6.2328%) ⬇️
unit 77.0032% <91.3043%> (+0.6493%) ⬆️

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

Components Coverage Δ
dumpling 56.7974% <ø> (ø)
parser ∅ <ø> (∅)
br 48.8429% <ø> (-12.0473%) ⬇️
🚀 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.

@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.

🧹 Nitpick comments (1)
pkg/planner/core/integration_test.go (1)

1631-1701: Consider one swapped-operand case to cover both correlated-range directions.

Current predicates are all inner_col OP outer_col. Adding one outer_col OP inner_col case would protect the alternate operand-order branch in correlated range detection.

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

In `@pkg/planner/core/integration_test.go` around lines 1631 - 1701, Add a
complementary test that uses the swapped operand order (outer_col OP inner_col)
to exercise the alternate correlated-range detection branch: add an
explain+correctness pair like the existing GT/LE/GE blocks but with the
predicate reversed (e.g., "select * from t1 t1a where exists (select 1 from t1
t1b where t1a.a < t1b.a and t1b.b = t1a.b)" with NO_DECORRELATE hint), assert
that explain output contains the expected "decided by" string for the
corresponding operator (e.g., "lt(test.t1.a, test.t1.a)") using the same
foundRangeAccess/rows checks, and add a matching tk.MustQuery correctness check
(ordered results) similar to the other blocks so the swapped-operand case is
covered.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@pkg/planner/core/integration_test.go`:
- Around line 1631-1701: Add a complementary test that uses the swapped operand
order (outer_col OP inner_col) to exercise the alternate correlated-range
detection branch: add an explain+correctness pair like the existing GT/LE/GE
blocks but with the predicate reversed (e.g., "select * from t1 t1a where exists
(select 1 from t1 t1b where t1a.a < t1b.a and t1b.b = t1a.b)" with
NO_DECORRELATE hint), assert that explain output contains the expected "decided
by" string for the corresponding operator (e.g., "lt(test.t1.a, test.t1.a)")
using the same foundRangeAccess/rows checks, and add a matching tk.MustQuery
correctness check (ordered results) similar to the other blocks so the
swapped-operand case is covered.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 69c7a565-cb29-41a0-a133-3eabe3266a66

📥 Commits

Reviewing files that changed from the base of the PR and between e9a7d36 and 62ff1af.

📒 Files selected for processing (1)
  • pkg/planner/core/integration_test.go

@terry1purcell

Copy link
Copy Markdown
Contributor Author

/retest-required

@tiprow

tiprow Bot commented Mar 6, 2026

Copy link
Copy Markdown

@terry1purcell: 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.

Details

In response to this:

/retest-required

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.

` └─IndexLookUp 10000.00 root `, // IndexLookUp is a inner child of Apply
` ├─Selection(Build) 80000000.00 cop[tikv] gt(test_parallel_apply.tinner.a, test_parallel_apply.touter.b)`,
` │ └─IndexFullScan 100000000.00 cop[tikv] table:tinner, index:a(a) keep order:false, stats:pseudo`,
` │ └─IndexRangeScan 100000000.00 cop[tikv] table:tinner, index:a(a) range: decided by [gt(test_parallel_apply.tinner.a, test_parallel_apply.touter.b)], keep order:false, stats:pseudo`,

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These are good changes, IndexRangeScan should be better than IndexFullScan.

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

@fixdb fixdb left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

+1

@ti-chi-bot

ti-chi-bot Bot commented Mar 8, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

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

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 Mar 8, 2026
@ti-chi-bot

ti-chi-bot Bot commented Mar 8, 2026

Copy link
Copy Markdown

[LGTM Timeline notifier]

Timeline:

  • 2026-03-06 08:38:19.810365444 +0000 UTC m=+520144.388444638: ☑️ agreed by qw4990.
  • 2026-03-08 01:09:44.863700582 +0000 UTC m=+139616.375758244: ☑️ agreed by fixdb.

@hawkingrei

Copy link
Copy Markdown
Contributor

/retest

1 similar comment
@hawkingrei

Copy link
Copy Markdown
Contributor

/retest

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.

Allow indexing of correlated range predicates

4 participants