Skip to content

planner, executor: support merge sort for IN conditions in IndexMerge partial paths#67771

Merged
ti-chi-bot[bot] merged 13 commits into
pingcap:masterfrom
time-and-fate:2604-65712-3
May 12, 2026
Merged

planner, executor: support merge sort for IN conditions in IndexMerge partial paths#67771
ti-chi-bot[bot] merged 13 commits into
pingcap:masterfrom
time-and-fate:2604-65712-3

Conversation

@time-and-fate
Copy link
Copy Markdown
Member

@time-and-fate time-and-fate commented Apr 15, 2026

What problem does this PR solve?

Issue Number: ref #65712 close #67775

Problem Summary:

For queries like SELECT * FROM t WHERE (a = 1 OR b IN (1,2)) ORDER BY c LIMIT 5, where indexes idx_a_c(a, c) and idx_b_c(b, c) are available:

  • The a = 1 partial path on idx_a_c can directly satisfy ORDER BY c (PropMatched).
  • The b IN (1,2) partial path on idx_b_c cannot directly satisfy ORDER BY c, because the ranges [1,1] and [2,2] are individually ordered by c but not globally ordered.

Previously, PropMatchedNeedMergeSort (introduced in #62694) was rejected for IndexMerge partial paths, causing the plan to fall back to a global TopN without Limit pushdown. This could scan all matching rows (e.g., ~750K) before sorting, resulting in much worse performance.

This is Solution 3 described in the issue.

What changed and how does it work?

This PR leverages the merge sort infrastructure from #62694 to support PropMatchedNeedMergeSort for IndexMerge partial paths. The IN condition ranges are split into groups, each producing a sorted stream, which are then merged.

After this fix, the plan becomes:

IndexMerge  type: union, limit embedded(offset:0, count:5)
├─Limit(Build)  offset:0, count:5
│ └─IndexRangeScan  range:[1,1], keep order:true
├─Limit(Build)  offset:0, count:5
│ └─IndexRangeScan  range:[1,1], [2,2], keep order:true
└─TableRowIDScan(Probe)  keep order:false
  • planner
    • matchPropForIndexMergeAlternatives() and isMatchPropForIndexMerge(): Accept PropMatchedNeedMergeSort from individual partial paths by using .Matched() instead of == PropMatched. The IndexMerge-level result remains PropMatched (the IndexMerge itself satisfies the order via its own inter-path merge sort).
    • ConvertToPartialIndexScan() and convertToPartialTableScan(): Remove the assertion that blocked PropMatchedNeedMergeSort, and copy GroupedRanges/GroupByColIdxs from the AccessPath to the physical scan when the path needs merge sort.
  • executor
    • Replace separate keyRanges and partitionKeyRanges fields with a unified partialWorkerKVRanges [][]*kvRangesWithPhysicalTblID. Each entry carries a PhysicalTableID alongside its kv ranges, unifying partition mode, grouped ranges (from IN conditions), and the combination of both. This follows the same pattern as IndexLookUpExecutor.groupedKVRanges from planner, executor: support access path keep order with IN conditions using merge sort #62694.
    • New buildPartialWorkerKVRanges() method in Open() builds kv ranges for each partial plan considering both partitions and grouped ranges.
    • rebuildRangeForCorCol(): After rebuilding ranges for correlated columns, also rebuild GroupedRanges using GroupRangesByCols().
    • startPartialTableWorker(): Pass GroupedRanges/GroupByColIdxs to the TableReaderExecutor, which already has full support for grouped ranges.
    • memIndexMergeReader (UnionScan): Updated to use the unified partialWorkerKVRanges field, replacing the previous partitionKVRanges/partitionTables and direct keyRanges access. The PhysicalTableID from each entry is used for partition handle conversion.

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

None

Summary by CodeRabbit

  • Tests
    • Added comprehensive integration test coverage for index merge queries combining IN/OR predicates with ORDER BY LIMIT clauses, validating result ordering and row limiting.
    • Extended testing for transaction isolation scenarios with uncommitted inserts, parameterized prepared statement execution with multiple values, and index merge behavior on partitioned tables.

… partial paths

When an IndexMerge query has a partial path with IN conditions (e.g., `b IN (1,2)`),
the partial path could not keep order because IN conditions produce multiple disjoint
ranges that are individually sorted but not globally sorted. This caused IndexMerge
to fall back to a global TopN without Limit pushdown, resulting in much worse
performance (e.g., 0.01s -> 1.53s).

This PR leverages the merge sort infrastructure introduced in pingcap#62694 to support
`PropMatchedNeedMergeSort` for IndexMerge partial paths. The IN condition ranges
are split into groups, each producing a sorted stream, which are then merged via
the existing `sortedSelectResults` mechanism.

Changes:
- planner: Accept `PropMatchedNeedMergeSort` from `matchProperty()` for IndexMerge
  partial paths, and propagate `GroupedRanges`/`GroupByColIdxs` to physical scans.
- executor: Replace separate `keyRanges` and `partitionKeyRanges` fields with a
  unified `partialWorkerKVRanges` that handles partitions, grouped ranges, or both.
  Rebuild `GroupedRanges` for correlated columns. Pass grouped ranges to
  `TableReaderExecutor` for table scan partial paths.
- executor: Update `memIndexMergeReader` (UnionScan) to use the unified field.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@ti-chi-bot ti-chi-bot Bot added release-note-none Denotes a PR that doesn't merit a release note. do-not-merge/needs-tests-checked labels Apr 15, 2026
@pantheon-ai
Copy link
Copy Markdown

pantheon-ai Bot commented Apr 15, 2026

Review Complete

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

ℹ️ Learn more details on Pantheon AI.

@ti-chi-bot ti-chi-bot Bot added sig/planner SIG: Planner size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Apr 15, 2026
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

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

IndexMerge execution now unifies per-partial-plan KV-range handling via partialWorkerKVRanges (ranges grouped by physical table IDs). Correlated-column range rebuilding reggroups ranges by GroupByColIdxs. Planner and executor wire grouped-range metadata through partial index/table scans to enable keeping order in IN-condition access paths as IndexMerge components.

Changes

IndexMerge Grouped KV-Ranges and Worker Execution Flow

Layer / File(s) Summary
Data Structure
pkg/executor/index_merge_reader.go
Introduces partialWorkerKVRanges [][]*kvRangesWithPhysicalTblID to store pre-built per-partial-plan KV ranges grouped by physical table ID, replacing separate keyRanges/partitionKeyRanges structures.
Range Building
pkg/executor/index_merge_reader.go
New buildPartialWorkerKVRanges method constructs unified per-partial-worker KV-range streams, handling partition-table mode, grouped ranges from PhysicalIndexScan.GroupedRanges, and table-scan range splitting across int64 boundary.
Correlated-Column Rebuilding
pkg/executor/index_merge_reader.go
Enhanced rebuildRangeForCorCol reggroups rebuilt ranges by GroupByColIdxs using plannercore.GroupRangesByCols for both PhysicalIndexScan and PhysicalTableScan.
Worker Startup & Extraction
pkg/executor/index_merge_reader.go
startWorkers now sizes fetch channel by len(e.partialPlans); startPartialIndexWorker extracts ordered KeyRanges from partialWorkerKVRanges[workID]; startPartialTableWorker passes groupedRanges and groupByColIdxs into TableReaderExecutor.
Memory Reader Alignment
pkg/executor/mem_reader.go
Replaces partitionTables and partitionKVRanges with partialWorkerKVRanges; updates range iteration and partition-handle wrapping to use physical table IDs from kvRangesWithPhysicalTblID entries.
Planner Property Matching
pkg/planner/core/find_best_task.go
Replaces PropMatched enum comparisons with matchProperty(...).Matched() calls in index-merge logic; propagates path.GroupedRanges and path.GroupByColIdxs into generated PhysicalTableScan when non-empty; removes merge-sort invariant assertion.
Physical Operator Setup
pkg/planner/core/operator/physicalop/physical_index_scan.go
Removes merge-sort disallowance assertion; propagates path.GroupedRanges and path.GroupByColIdxs onto generated PhysicalIndexScan in merge-sort setup when ranges are non-empty.
Integration Tests
tests/integrationtest/t/index_merge.test, tests/integrationtest/r/index_merge.result
Adds test cases for IN/OR predicates with ORDER BY ... LIMIT, covering partial-order preservation, merge-sorting, uncommitted rows in transactions, and PREPARE/EXECUTE coverage.
Planner Coverage & Results
tests/integrationtest/t/planner/core/grouped_ranges_order_by.test, tests/integrationtest/r/planner/core/grouped_ranges_order_by.result
Adds comprehensive IndexMerge planner tests for IN/OR with ORDER BY and LIMIT, including prepared statements, transactional uncommitted rows, correlated subqueries, and partitioned-table variants.
Physical Plan Fixtures
tests/integrationtest/r/planner/core/casetest/physicalplantest/physical_plan.result
Updates expected plan shapes for multi-range IndexMerge: replaces TopN root with Projection over IndexMerge with limit embedded; adds Limit(Build) wrappers and sets IndexRangeScan to keep order:true.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • pingcap/tidb#66746: Continuation and refactoring of grouped-range/per-partial-plan KV-ranges wiring and worker/executor topic changes.
  • pingcap/tidb#66719: Related correlated-column range handling; updates planner to promote correlated predicates into access conditions, which pairs with this PR's executor/planner grouped-range propagation.
  • pingcap/tidb#66713: Related planner property-matching API changes; both use/introduce matchProperty() returning PhysicalPropMatchResult with .Matched() calls.

Suggested labels

size/XXL, ok-to-test, approved, lgtm

Suggested reviewers

  • qw4990
  • winoros

Poem

🐰 Hop, hop! The ranges group and flow,
Correlated columns steal the show,
IndexMerge keeps order true,
WITH IN and ORDER BY, we're through!
Grouped KV ranges, workers aligned,
A cleaner path for planner to find.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.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 PR title 'planner, executor: support merge sort for IN conditions in IndexMerge partial paths' clearly and specifically describes the main changes across both planner and executor packages.
Description check ✅ Passed The PR description comprehensively covers the problem statement, solution details, and implementation across both planner and executor, with integration tests included.
Linked Issues check ✅ Passed The PR implements Solution 3 from issue #65712 as stated in linked issue #67775, enabling IndexMerge to support PropMatchedNeedMergeSort for IN-condition grouped ranges through planner and executor changes.
Out of Scope Changes check ✅ Passed All code changes are directly aligned with supporting merge sort for IN conditions in IndexMerge partial paths; test additions appropriately validate the new functionality.

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

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

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

Copy link
Copy Markdown

@pantheon-ai pantheon-ai Bot left a comment

Choose a reason for hiding this comment

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

✅ Code looks good. No issues found.

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: 2

🧹 Nitpick comments (1)
tests/integrationtest/t/index_merge.test (1)

241-285: Add one primary IN (...)/table-partial case here as well.

These cases only drive secondary-index partials (idx_a_c / idx_b_c). The planner change also added grouped-range propagation in convertToPartialTableScan(), but nothing in this block makes IndexMerge choose a table/primary partial path that needs the same merge-sort treatment. A small case like id in (...) or b in (...) order by id limit ... would cover that new branch and keep it from regressing silently.

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

In `@tests/integrationtest/t/index_merge.test` around lines 241 - 285, Add a test
that exercises a primary/table-partial path by including a case like "id IN
(...) or b IN (...) ORDER BY id LIMIT ..." so IndexMerge must consider a
primary-key partial and be merge-sorted; specifically insert a new block in
index_merge.test similar to other cases that uses the /*+
use_index_merge(t_im_in) */ hint and queries "select ... from t_im_in where id
in (1,2) or b in (1,2) order by id limit 5" (also include an EXPLAIN and a
SELECT, and a PREPARE/EXECUTE variant if desired) to cover the
convertToPartialTableScan() branch and prevent regressions in
primary/table-partial handling by IndexMerge.
🤖 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/executor/index_merge_reader.go`:
- Around line 210-216: The code only extracts GroupedRanges from
PhysicalIndexScan, missing the case where plan[0] is a
*physicalop.PhysicalTableScan; update the branch in index_merge_reader.go (the
block around plan[0] handling) to check for *physicalop.PhysicalTableScan as
well and set groupedRanges = x.GroupedRanges when that type matches (same as for
PhysicalIndexScan), so memIndexMergeReader/partialWorkerKVRanges will use
table-scan grouped ranges (consistent with rebuildRangeForCorCol behavior).

In `@pkg/planner/core/find_best_task.go`:
- Around line 1433-1435: The code currently reduces the detailed result from
matchProperty(...) to a boolean via .Matched(), losing the PropMatched vs
PropMatchedNeedMergeSort distinction and allowing stale
AccessPath.GroupedRanges/GroupByColIdxs to leak; update the logic in the loop
that iterates oneAlternative to either (a) preserve the full
PhysicalPropMatchResult returned by matchProperty(...) and use its Matched/Kind
to decide match, or (b) after calling matchProperty(...) and finding it is NOT
PropMatchedNeedMergeSort, explicitly clear the mutable grouping fields on the
AccessPath (AccessPath.GroupedRanges and AccessPath.GroupByColIdxs) so stale
grouping metadata cannot affect subsequent direct-match plans; apply the same
fix to the similar block around match checks at the other location mentioned
(the block around lines referenced as 1543-1545).

---

Nitpick comments:
In `@tests/integrationtest/t/index_merge.test`:
- Around line 241-285: Add a test that exercises a primary/table-partial path by
including a case like "id IN (...) or b IN (...) ORDER BY id LIMIT ..." so
IndexMerge must consider a primary-key partial and be merge-sorted; specifically
insert a new block in index_merge.test similar to other cases that uses the /*+
use_index_merge(t_im_in) */ hint and queries "select ... from t_im_in where id
in (1,2) or b in (1,2) order by id limit 5" (also include an EXPLAIN and a
SELECT, and a PREPARE/EXECUTE variant if desired) to cover the
convertToPartialTableScan() branch and prevent regressions in
primary/table-partial handling by IndexMerge.
🪄 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: 681e8948-d1b3-46a2-b1d7-eb5ffbc906b8

📥 Commits

Reviewing files that changed from the base of the PR and between 5733f22 and bdcdb56.

📒 Files selected for processing (7)
  • pkg/executor/index_merge_reader.go
  • pkg/executor/mem_reader.go
  • pkg/executor/test/indexmergereadtest/index_merge_reader_test.go
  • pkg/planner/core/find_best_task.go
  • pkg/planner/core/operator/physicalop/physical_index_scan.go
  • tests/integrationtest/r/index_merge.result
  • tests/integrationtest/t/index_merge.test

Comment thread pkg/executor/index_merge_reader.go Outdated
Comment on lines 1433 to 1435
for _, oneAccessPath := range oneAlternative {
// Satisfying the property by a merge sort is not supported for partial paths of index merge.
if !noSortItem && matchProperty(ds, oneAccessPath, prop) != property.PropMatched {
if !noSortItem && !matchProperty(ds, oneAccessPath, prop).Matched() {
match = 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 | 🟠 Major

Preserve the exact partial-path match result instead of collapsing it to .Matched().

This drops the distinction between PropMatched and PropMatchedNeedMergeSort, but the later partial-scan conversion recovers that distinction from AccessPath.GroupedRanges / GroupByColIdxs. Those fields are mutable planner scratch state, and matchProperty() only populates them on the merge-sort path; it does not clear them on plain matches in the code shown here. If the same AccessPath is reused across property explorations, stale grouping metadata can leak into a later direct-match plan and force unnecessary grouped cop tasks / merge-sort. Please either clear the grouping fields whenever the current match is not PropMatchedNeedMergeSort, or carry the per-partial PhysicalPropMatchResult through instead of reducing it to a bool.

Also applies to: 1543-1545

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

In `@pkg/planner/core/find_best_task.go` around lines 1433 - 1435, The code
currently reduces the detailed result from matchProperty(...) to a boolean via
.Matched(), losing the PropMatched vs PropMatchedNeedMergeSort distinction and
allowing stale AccessPath.GroupedRanges/GroupByColIdxs to leak; update the logic
in the loop that iterates oneAlternative to either (a) preserve the full
PhysicalPropMatchResult returned by matchProperty(...) and use its Matched/Kind
to decide match, or (b) after calling matchProperty(...) and finding it is NOT
PropMatchedNeedMergeSort, explicitly clear the mutable grouping fields on the
AccessPath (AccessPath.GroupedRanges and AccessPath.GroupByColIdxs) so stale
grouping metadata cannot affect subsequent direct-match plans; apply the same
fix to the similar block around match checks at the other location mentioned
(the block around lines referenced as 1543-1545).

@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 15, 2026

Codecov Report

❌ Patch coverage is 74.03846% with 27 lines in your changes missing coverage. Please review.
✅ Project coverage is 77.0182%. Comparing base (8ad6083) to head (93a8286).

Additional details and impacted files
@@               Coverage Diff                @@
##             master     #67771        +/-   ##
================================================
- Coverage   77.7059%   77.0182%   -0.6877%     
================================================
  Files          1991       1973        -18     
  Lines        552094     552883       +789     
================================================
- Hits         429010     425821      -3189     
- Misses       122164     127055      +4891     
+ Partials        920          7       -913     
Flag Coverage Δ
integration 41.3494% <74.0384%> (+1.5476%) ⬆️

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

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

@time-and-fate
Copy link
Copy Markdown
Member Author

/retest

@time-and-fate
Copy link
Copy Markdown
Member Author

/retest

@ti-chi-bot ti-chi-bot Bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels May 9, 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.

🧹 Nitpick comments (1)
tests/integrationtest/t/planner/core/grouped_ranges_order_by.test (1)

320-408: 💤 Low value

Verify integration test recording workflow.

Confirm that the .result file was generated using the recording command specified in docs/agents/testing-flow.mdIntegration tests section, rather than using a -record flag. As per coding guidelines, integration tests under tests/integrationtest/** must follow the documented recording workflow.

The test coverage itself is comprehensive and well-structured, covering:

  • Basic IndexMerge with ORDER BY ... LIMIT (ASC/DESC)
  • Prepared statements with parameterized IN values and LIMIT
  • UnionScan behavior with uncommitted rows
  • Correlated subqueries with no_decorrelate()
  • Partitioned table support

As per coding guidelines, integration tests in tests/integrationtest/** must use the recording command documented in docs/agents/testing-flow.mdIntegration tests (not -record).

🤖 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 `@tests/integrationtest/t/planner/core/grouped_ranges_order_by.test` around
lines 320 - 408, The recorded .result for the integration test
grouped_ranges_order_by.test was created with the deprecated -record flag
instead of the documented recording command; re-run the test recording using the
exact command from docs/agents/testing-flow.md → Integration tests (not
-record), regenerate the .result artifact for the test that covers the
statements around stmt_im and the IndexMerge queries on t_im_in/t_im_part
(including the prepared statement 'stmt_im' and the correlated subquery using
no_decorrelate()), replace the existing .result with the newly recorded file,
and commit the updated artifact ensuring no -record usage remains.
🤖 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.

Nitpick comments:
In `@tests/integrationtest/t/planner/core/grouped_ranges_order_by.test`:
- Around line 320-408: The recorded .result for the integration test
grouped_ranges_order_by.test was created with the deprecated -record flag
instead of the documented recording command; re-run the test recording using the
exact command from docs/agents/testing-flow.md → Integration tests (not
-record), regenerate the .result artifact for the test that covers the
statements around stmt_im and the IndexMerge queries on t_im_in/t_im_part
(including the prepared statement 'stmt_im' and the correlated subquery using
no_decorrelate()), replace the existing .result with the newly recorded file,
and commit the updated artifact ensuring no -record usage remains.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0b01707e-548a-49e6-97c6-5a20c5b8d215

📥 Commits

Reviewing files that changed from the base of the PR and between 5d0cba0 and de9f51b.

📒 Files selected for processing (2)
  • tests/integrationtest/r/planner/core/grouped_ranges_order_by.result
  • tests/integrationtest/t/planner/core/grouped_ranges_order_by.test

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR improves IndexMerge planning/execution for ORDER BY ... LIMIT queries when some partial paths contain IN ranges that are only locally ordered (per-range) but not globally ordered, by enabling merge-sort across range groups for partial paths (using the existing PropMatchedNeedMergeSort infrastructure).

Changes:

  • Planner: allow PropMatchedNeedMergeSort for IndexMerge partial paths; propagate GroupedRanges/GroupByColIdxs from AccessPath into partial physical scans.
  • Executor: unify partial-path kv-range construction into partialWorkerKVRanges to support partitions and grouped ranges (including UnionScan/mem readers), and rebuild grouped ranges after correlated-range rebuilds.
  • Tests: add/extend integration tests to validate plans and correctness across plain queries, DESC, prepared statements, UnionScan, correlated subqueries, and partitioned tables.

Reviewed changes

Copilot reviewed 9 out of 9 changed files in this pull request and generated no comments.

Show a summary per file
File Description
tests/integrationtest/t/planner/core/grouped_ranges_order_by.test Adds integration coverage for IndexMerge + IN + ORDER BY LIMIT across multiple scenarios (including partition/UnionScan/correlation).
tests/integrationtest/t/index_merge.test Adds targeted IndexMerge regression tests for IN-driven merge sort behavior with ORDER BY LIMIT.
tests/integrationtest/r/planner/core/grouped_ranges_order_by.result Updates expected plans/results to reflect limit pushdown + ordered partial paths.
tests/integrationtest/r/planner/core/casetest/physicalplantest/physical_plan.result Updates expected physical plans to reflect embedded limits + keep order on partial paths.
tests/integrationtest/r/index_merge.result Updates expected output/plans for new regression test cases.
pkg/planner/core/operator/physicalop/physical_index_scan.go Allows partial index scans to carry GroupedRanges/GroupByColIdxs for merge sort within a partial path.
pkg/planner/core/find_best_task.go Accepts .Matched() for IndexMerge partial paths, resets grouped-range fields per matchProperty() call, and propagates grouped-range metadata into partial scans.
pkg/executor/mem_reader.go Updates mem IndexMerge reader to use unified partialWorkerKVRanges (including partition handle wrapping via physical table IDs).
pkg/executor/index_merge_reader.go Builds unified partialWorkerKVRanges, rebuilds grouped ranges after correlated-range rebuild, and passes grouped-range metadata to table readers.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread pkg/planner/core/find_best_task.go
@ti-chi-bot ti-chi-bot Bot added approved needs-1-more-lgtm Indicates a PR needs 1 more LGTM. labels May 11, 2026
Comment thread pkg/executor/index_merge_reader.go Outdated
physTblID int64
isCommonHdl bool
}
var tables []tblInfo
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Can we pre-allocate the memory for the slice?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Updated.

if err != nil {
return err
}
combined := append(firstKV.FirstPartitionRange(), secondKV.FirstPartitionRange()...)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

pre-alloc for the combined too.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I think the current implementation is optimal. The compiler should handle this well.

@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented May 12, 2026

[APPROVALNOTIFIER] This PR is APPROVED

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

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 May 12, 2026
@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented May 12, 2026

[LGTM Timeline notifier]

Timeline:

  • 2026-05-11 09:32:39.857727657 +0000 UTC m=+85328.390506966: ☑️ agreed by qw4990.
  • 2026-05-12 09:46:16.618394267 +0000 UTC m=+172545.151173576: ☑️ agreed by winoros.

@ti-chi-bot ti-chi-bot Bot merged commit 173eaf2 into pingcap:master May 12, 2026
36 checks passed
@ti-chi-bot ti-chi-bot Bot added the needs-cherry-pick-release-8.5 Should cherry pick this PR to release-8.5 branch. label May 29, 2026
@ti-chi-bot
Copy link
Copy Markdown
Member

In response to a cherrypick label: new pull request created to branch release-8.5: #68753.
But this PR has conflicts, please resolve them!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved lgtm needs-cherry-pick-release-8.5 Should cherry pick this PR to release-8.5 branch. 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.

Support access path with IN condition to keep order when it's a partial path of IndexMerge

5 participants