Skip to content

planner: optimize for full range (#66304) | tidb-test=pr/2743 - #66695

Merged
ti-chi-bot[bot] merged 12 commits into
pingcap:release-8.5from
ti-chi-bot:cherry-pick-66304-to-release-8.5
Jun 2, 2026
Merged

ti-chi-bot[bot] merged 12 commits into
pingcap:release-8.5from
ti-chi-bot:cherry-pick-66304-to-release-8.5

Conversation

@ti-chi-bot

@ti-chi-bot ti-chi-bot commented Mar 5, 2026 •

Copy link
Copy Markdown
Member

This is an automated cherry-pick of #66304

What problem does this PR solve?

Issue Number: ref #64791 ref #63856 ref #63235

Problem Summary:

Removed link to tidb-test PR | tidb-test=pr/2708

What changed and how does it work?

This PR provides 2 main optimizations:

  1. It allows index row count estimation to be skipped if the predicate ranges cover the entire index range.
  2. Moves the "full range" and "equal predicate count" to the top of the skylinePruning logic so that these are evaluated once for each candidate plan, and not repeated as part of the repeated processing by compareCandidates.

There are 3 issues referenced here that this PR improves upon:
#64791 - relates to async loading of statistics. This PR will avoid estimation for columns that exist in the index, but not in the query - meaning that estimation logic will build a full range. Such columns may not have had their statistics sync loaded (since they aren't interesting to the query).
#63856 - relates to long planning times with large number of indexes. Reducing overhead in skylinePruning and skipping over estimation for uninteresting indexes will improve planning time.
#63235 - customer reported that the fullrange check in skylinePruning was expensive. Reducing the number of times that this is set in skylinePruning should reduce that overhead. However, it is now included in regular index estimation which can incur cost - which should be negligible or irrelevant if estimation is skipped for any indexes.

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

    • Optimized index range cardinality estimation by introducing fast-path logic to skip unnecessary calculations when queries use full-range index scans.
    • Enhanced index column extraction logic for more accurate statistics calculation.
    • Refactored query planner's candidate path evaluation for improved plan selection decisions.
  • Tests

    • Added comprehensive test coverage for the optimized index estimation fast-path behavior.

@ti-chi-bot ti-chi-bot added 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/L Denotes a PR that changes 100-499 lines, ignoring generated files. type/cherry-pick-for-release-8.5 This PR is cherry-picked to release-8.5 from a source PR. labels Mar 5, 2026
@ti-chi-bot ti-chi-bot mentioned this pull request Mar 5, 2026
1 of 13 tasks
@ti-chi-bot

Copy link
Copy Markdown
Member Author

@terry1purcell This PR has conflicts, I have hold it.
Please resolve them or ask others to resolve them, then comment /unhold to remove the hold label.

@ti-chi-bot

ti-chi-bot Bot commented Mar 5, 2026

Copy link
Copy Markdown

@ti-chi-bot: ## If you want to know how to resolve it, please read the guide in TiDB Dev Guide.

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 ti-community-infra/tichi repository.

@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

This PR refactors TiDB's query planner to optimize cardinality estimation and path selection. It introduces early-return logic for index range estimation when covering full NULL-inclusive ranges, restructures candidate path comparison with new state fields, adjusts index column extraction logic, and optimizes table path statistics computation.

Changes

Cohort / File(s) Summary
Index Estimation Optimization
pkg/planner/cardinality/row_count_index.go, pkg/planner/cardinality/selectivity_test.go
Adds canSkipIndexEstimation() and isFullRangeIncludingNulls() helpers to enable early-return path in GetRowCountByIndexRanges when index ranges cover full NULL-inclusive range, bypassing expensive estimation. Introduces new test TestCanSkipIndexEstimation to validate fast-path behavior.
Path Selection Refactoring
pkg/planner/core/find_best_task.go
Restructures candidatePath by replacing matchPropResult with isMatchProp, adding isFullRange and eqOrInCount fields. Changes compareEqOrIn() signature to return single int. Updates comparison and pruning logic throughout to reference new candidate state fields.
Statistics and Path Utilities
pkg/planner/core/stats.go, pkg/planner/util/path.go
Swaps index column extraction to use IndexInfo2PrefixCols for prefix-only columns and IndexInfo2Cols for full columns. Removes GroupedRanges and GroupByColIdxs from AccessPath struct. Adds IsFullScanRange() method to AccessPath. Introduces optimization in deriveTablePathStats to skip expensive range computation when no access conditions exist.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • qw4990
  • winoros
  • henrybw

Poem

🐰 Hops through index ranges swift,
Early returns are quite the gift,
Candidates aligned with care,
Full ranges make the planner rare!
Stats optimized, paths refined—
Query speed of a better kind!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 35.90% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description check ✅ Passed The PR description includes issue references, problem summary, detailed explanation of changes, and completed test/documentation checklists, meeting template requirements.
Title check ✅ Passed The title 'planner: optimize for full range (#66304)' accurately describes the primary optimization: skipping index estimation when predicates cover full ranges and moving full-range evaluations to reduce repeated computation.

✏️ 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.

@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: 6

Caution

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

⚠️ Outside diff range comments (2)
pkg/planner/cardinality/BUILD.bazel (1)

49-66: ⚠️ Potential issue | 🟠 Major

Add bazel_prepare output to this PR for Bazel-file changes.

Since this PR changes a BUILD.bazel file, include the generated Bazel metadata updates produced by make bazel_prepare.

As per coding guidelines: "MUST run make bazel_prepare and include resulting Bazel metadata changes in the PR when adding/moving/renaming/removing Go files, changing Bazel files, updating Bazel test targets, or changing go.mod/go.sum".

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

In `@pkg/planner/cardinality/BUILD.bazel` around lines 49 - 66, This change
touches Bazel BUILD metadata (the cardiniality_test target in BUILD.bazel with a
modified shard_count), so run make bazel_prepare locally to regenerate Bazel
metadata and include the resulting changes from that command in the PR; commit
the generated Bazel outputs (the files created/updated by bazel_prepare)
alongside this BUILD.bazel edit so the repository's Bazel state remains
consistent with the change to the cardiniality_test target.
pkg/planner/cardinality/row_count_index.go (1)

41-42: ⚠️ Potential issue | 🔴 Critical

Fix signature mismatch: some callers pass extra argument that function doesn't accept

Three test call sites in pkg/planner/cardinality/selectivity_test.go (lines 343, 351, 358) are passing a 5th nil argument, but GetRowCountByIndexRanges is defined to accept only 4 parameters. Update the function signature to accept the extra parameter, or remove the extra argument from these call sites to align all callers with the function definition.

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

In `@pkg/planner/cardinality/row_count_index.go` around lines 41 - 42, The
function signature for GetRowCountByIndexRanges currently accepts four
parameters but some callers pass a fifth nil; update the signature to accept an
extra parameter (e.g., extra interface{} or opts interface{}) so callers that
pass nil compile, e.g., change func GetRowCountByIndexRanges(sctx
planctx.PlanContext, coll *statistics.HistColl, idxID int64, indexRanges
[]*ranger.Range) to include a final extra parameter and leave the new argument
unused inside the function (or use it if needed), ensuring all existing call
sites that pass nil continue to work; update any references to
GetRowCountByIndexRanges accordingly.
🤖 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/cardinality/BUILD.bazel`:
- Around line 62-66: Resolve the unresolved Git conflict in the BUILD file by
removing the conflict markers (<<<<<<<, =======, >>>>>>>) and leaving a single
valid shard_count assignment for the cardinality target (choose either
shard_count = 34 or shard_count = 45 based on intended change for the
planner/cardinality logic and branch context, ensuring only one shard_count line
remains), then run make bazel_prepare and include the generated Bazel metadata
changes in the PR so the BUILD change and metadata stay in sync.

In `@pkg/planner/cardinality/row_count_index.go`:
- Around line 64-83: The diff contains unresolved merge conflict markers and
mixed implementations: one branch returns a float64 count while the other uses
statistics.RowEstimate, causing compile errors; remove the conflict markers and
unify to a single implementation that uses hasColumnStats(sctx, coll, idxCols)
&& !ranger.HasFullRange(indexRanges, false) to call
getPseudoRowCountWithPartialStats(sctx, coll, indexRanges,
float64(coll.RealtimeCount), idxCols) and assign result =
statistics.RowEstimate{Est: count, MinEst: count, MaxEst: maxCount}, otherwise
compute colsLen (use idx.Info.Unique and len(idx.Info.Columns)), call
getPseudoRowCountByIndexRanges(sc.TypeCtx(), indexRanges,
float64(coll.RealtimeCount), colsLen) and wrap the returned count with
statistics.DefaultRowEst(count); ensure variables count, maxCount, err and
result have consistent types, preserve sc.EnableOptimizerCETrace logging by
calling ceTraceRange(sctx, coll.PhysicalID, colNames, indexRanges, "Index
Stats-Pseudo", uint64(result.Est)) or equivalent using result.Est, and remove
all conflict markers so the file compiles.

In `@pkg/planner/cardinality/selectivity_test.go`:
- Around line 306-360: The test fails to compile because it uses ast.NewCIStr
(undefined here) and assumes a different cardinality API shape; change the
TableByName call to use pmodel.NewCIStr (replace ast.NewCIStr with
pmodel.NewCIStr) so the CIStr import matches the rest of the file, and update
the three calls to cardinality.GetRowCountByIndexRanges and their result usages
to match the current cardinality implementation (adjust the function parameters
if it doesn't accept a nil for the last arg and access the actual result field
name returned by that function instead of .Est if the real struct uses a
different field).

In `@pkg/planner/core/find_best_task.go`:
- Around line 682-693: Remove the leftover merge-conflict markers and reconcile
the candidatePath struct/field model so it uses the new fields instead of the
old isMatchProp boolean; specifically replace the old isMatchProp bool with
matchPropResult property.PhysicalPropMatchResult and include the new fields
partialOrderMatchResult property.PartialOrderMatchResult, isFullRange bool and
eqOrInCount int while keeping indexJoinCols int, then update all usages of
candidatePath that referenced isMatchProp or the old model (including logic
around TopN/partial order, full-range checks and equal-predicate counting) to
use matchPropResult, partialOrderMatchResult, isFullRange and eqOrInCount
accordingly and remove any remaining conflict markers at the noted locations so
the file compiles.

In `@pkg/planner/core/stats.go`:
- Around line 335-357: The deriveTablePathStats function contains unresolved
merge conflict markers and an undeclared identifier lenAccessConds; remove the
conflict block and unify the intended logic: replace the conflict with the
optimized branch that checks if lenAccessConds==0 (ensure lenAccessConds is
defined/available in scope) and if ds.Table.GetPartitionedTable()==nil then set
path.CountAfterAccess = float64(ds.StatisticTable.RealtimeCount), otherwise call
cardinality.GetRowCountByColumnRanges (capture statistics.RowEstimate and set
path.CountAfterAccess = countEst.Est); then, if !isIm invoke
adjustCountAfterAccess(ds, path) to apply the lower-bound logic. Ensure you
remove references to the old GetRowCountByIntColumnRanges or reconcile its usage
with GetRowCountByColumnRanges and fix imports/types so the function compiles.
- Around line 351-352: The call to GetRowCountByColumnRanges uses the old
5-arg/RowEstimate API; update it to the current 4-arg signature that returns a
float64: call cardinality.GetRowCountByColumnRanges(ds.SCtx(),
&ds.StatisticTable.HistColl, pkCol.ID, path.Ranges), capture the returned
float64 (not a RowEstimate) and assign it directly to path.CountAfterAccess (or
convert as needed), and remove the extra boolean argument and RowEstimate
handling.

---

Outside diff comments:
In `@pkg/planner/cardinality/BUILD.bazel`:
- Around line 49-66: This change touches Bazel BUILD metadata (the
cardiniality_test target in BUILD.bazel with a modified shard_count), so run
make bazel_prepare locally to regenerate Bazel metadata and include the
resulting changes from that command in the PR; commit the generated Bazel
outputs (the files created/updated by bazel_prepare) alongside this BUILD.bazel
edit so the repository's Bazel state remains consistent with the change to the
cardiniality_test target.

In `@pkg/planner/cardinality/row_count_index.go`:
- Around line 41-42: The function signature for GetRowCountByIndexRanges
currently accepts four parameters but some callers pass a fifth nil; update the
signature to accept an extra parameter (e.g., extra interface{} or opts
interface{}) so callers that pass nil compile, e.g., change func
GetRowCountByIndexRanges(sctx planctx.PlanContext, coll *statistics.HistColl,
idxID int64, indexRanges []*ranger.Range) to include a final extra parameter and
leave the new argument unused inside the function (or use it if needed),
ensuring all existing call sites that pass nil continue to work; update any
references to GetRowCountByIndexRanges accordingly.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: ebd6c3d9-5dca-4927-8375-f5f4d8879658

📥 Commits

Reviewing files that changed from the base of the PR and between 8f1e9a9 and 6e47658.

📒 Files selected for processing (5)
  • pkg/planner/cardinality/BUILD.bazel
  • pkg/planner/cardinality/row_count_index.go
  • pkg/planner/cardinality/selectivity_test.go
  • pkg/planner/core/find_best_task.go
  • pkg/planner/core/stats.go

Comment thread pkg/planner/cardinality/BUILD.bazel Outdated
Comment thread pkg/planner/cardinality/row_count_index.go Outdated
Comment thread pkg/planner/cardinality/selectivity_test.go Outdated
Comment thread pkg/planner/core/find_best_task.go Outdated
Comment thread pkg/planner/core/stats.go Outdated
Comment thread pkg/planner/core/stats.go Outdated
@ti-chi-bot ti-chi-bot Bot added cherry-pick-approved Cherry pick PR approved by release team. and removed do-not-merge/cherry-pick-not-approved labels Mar 5, 2026

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

Caution

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

⚠️ Outside diff range comments (1)
pkg/planner/core/find_best_task.go (1)

1178-1193: ⚠️ Potential issue | 🟠 Major

Use unsigned-aware full-range detection for index-join candidates.

At Line 1191, ranger.HasFullRange(path.Ranges, false) hardcodes signed-handle semantics. For unsigned PK-handle ranges (e.g., [0,+inf)), this can cache isFullRange incorrectly and bias later prefer-range pruning decisions.

💡 Proposed fix
-func getIndexCandidateForIndexJoin(sctx planctx.PlanContext, path *util.AccessPath, indexJoinCols int) *candidatePath {
+func getIndexCandidateForIndexJoin(sctx planctx.PlanContext, tableInfo *model.TableInfo, path *util.AccessPath, indexJoinCols int) *candidatePath {
 	candidate := &candidatePath{path: path, indexJoinCols: indexJoinCols}
 	// cherry-pick from the master: false instead of property.PropNotMatched in PR#63958
 	candidate.isMatchProp = false
 	candidate.accessCondsColMap = util.ExtractCol2Len(sctx.GetExprCtx().GetEvalCtx(), path.AccessConds, path.IdxCols, path.IdxColLens)
 	candidate.indexCondsColMap = util.ExtractCol2Len(sctx.GetExprCtx().GetEvalCtx(), append(path.AccessConds, path.IndexFilters...), path.FullIdxCols, path.FullIdxColLens)
@@
-	candidate.isFullRange = ranger.HasFullRange(path.Ranges, false)
+	candidate.isFullRange = path.IsFullScanRange(tableInfo)
 	candidate.eqOrInCount = candidate.equalPredicateCount()
 	return candidate
 }
// update call site(s) accordingly:
candidate := getIndexCandidateForIndexJoin(sctx, ds.TableInfo, path, indexJoinCols)
🤖 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 1178 - 1193, The call in
getIndexCandidateForIndexJoin sets candidate.isFullRange using
ranger.HasFullRange(path.Ranges, false) which forces signed semantics and
misdetects unsigned PK-handle ranges; change the code to use unsigned-aware
full-range detection by passing the correct unsigned flag (derived from the
table/column metadata) into HasFullRange (update getIndexCandidateForIndexJoin
signature if needed to accept the table info or an isUnsigned boolean), and
ensure candidate.isFullRange is computed via ranger.HasFullRange(path.Ranges,
isUnsigned) so index-join candidates use correct full-range semantics.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@pkg/planner/core/find_best_task.go`:
- Around line 1178-1193: The call in getIndexCandidateForIndexJoin sets
candidate.isFullRange using ranger.HasFullRange(path.Ranges, false) which forces
signed semantics and misdetects unsigned PK-handle ranges; change the code to
use unsigned-aware full-range detection by passing the correct unsigned flag
(derived from the table/column metadata) into HasFullRange (update
getIndexCandidateForIndexJoin signature if needed to accept the table info or an
isUnsigned boolean), and ensure candidate.isFullRange is computed via
ranger.HasFullRange(path.Ranges, isUnsigned) so index-join candidates use
correct full-range semantics.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 427a91ac-a7fa-4c63-9fb3-e1367cb228c8

📥 Commits

Reviewing files that changed from the base of the PR and between 6e47658 and f075431.

📒 Files selected for processing (6)
  • .cursor/commands/new-question.md
  • pkg/planner/cardinality/row_count_index.go
  • pkg/planner/cardinality/selectivity_test.go
  • pkg/planner/core/find_best_task.go
  • pkg/planner/core/stats.go
  • pkg/planner/util/path.go

@ti-chi-bot ti-chi-bot Bot added size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Mar 13, 2026

@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/logical_plans_test.go`:
- Around line 1564-1575: visitInfoArray.Less currently compares fields
independently which can make Less(i,j) and Less(j,i) both true; change it to a
strict lexicographic comparator that compares privilege first, if equal compare
db, if equal compare table, if equal compare column, and only return true when
the first non-equal field is less, otherwise return false — update the
visitInfoArray.Less method to perform sequential equality checks (privilege,
then db, then table, then column) and return false at the end to satisfy
sort.Interface strict weak ordering.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 5e817103-d76f-43d6-bb08-7d04cf063ad7

📥 Commits

Reviewing files that changed from the base of the PR and between f075431 and 1709dd4.

📒 Files selected for processing (2)
  • pkg/planner/core/find_best_task.go
  • pkg/planner/core/logical_plans_test.go

Comment thread pkg/planner/core/logical_plans_test.go Outdated

@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/planner/core/logical_plans_test.go (1)

2940-2957: ⚠️ Potential issue | 🟡 Minor

Missing resource cleanup in test loop.

Same issue as testColumnPrivilegeVisitInfo - the MockContext() creates a domain with a stats handle that should be closed after each iteration to prevent resource leaks.

🔧 Proposed fix
 		checkVisitInfo(t, builder.visitInfo, tt.ans, tt.sql)
 		fp := TryFastPlan(s.ctx, nodeW)
 		require.NotNil(t, fp, "expect '%s' to get point get plan", tt.sql)
 		checkVisitInfo(t, pointGetVisitInfo4Test, tt.ans, tt.sql)
+
+		domain.GetDomain(sctx).StatsHandle().Close()
 	}
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/planner/core/logical_plans_test.go` around lines 2940 - 2957, The test
loop creates a MockContext() for each iteration but never closes the
domain/stats handle it creates, causing resource leaks; after you finish using
sctx (after TryFastPlan and checks) call the appropriate cleanup on the domain
created for that context (e.g. domain.GetDomain(sctx).Close() or the equivalent
StatsHandle.Close() for your environment) to release resources; ensure this
cleanup runs for every iteration (no defer outside loop) and reference
MockContext() and domain.GetDomain(sctx) in the fix so the newly created
domain/stats handle is properly closed each loop.
🤖 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/logical_plans_test.go`:
- Around line 2806-2822: The test loop created a domain via MockContext() but
never closes its stats handle; call domain.GetDomain(sctx).StatsHandle().Close()
at the end of each iteration (after TryFastPlan/require.Nil and before the loop
continues) to mirror testNormalVisitInfo’s cleanup and avoid leaking resources;
locate the MockContext() usage in this test and add the StatsHandle().Close()
call for the domain returned by domain.GetDomain(sctx).

---

Outside diff comments:
In `@pkg/planner/core/logical_plans_test.go`:
- Around line 2940-2957: The test loop creates a MockContext() for each
iteration but never closes the domain/stats handle it creates, causing resource
leaks; after you finish using sctx (after TryFastPlan and checks) call the
appropriate cleanup on the domain created for that context (e.g.
domain.GetDomain(sctx).Close() or the equivalent StatsHandle.Close() for your
environment) to release resources; ensure this cleanup runs for every iteration
(no defer outside loop) and reference MockContext() and domain.GetDomain(sctx)
in the fix so the newly created domain/stats handle is properly closed each
loop.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 35c5233c-a70f-4f49-9a45-baa38d5ded55

📥 Commits

Reviewing files that changed from the base of the PR and between 1709dd4 and f02d756.

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

Comment thread pkg/planner/core/logical_plans_test.go
@terry1purcell
terry1purcell force-pushed the cherry-pick-66304-to-release-8.5 branch from f02d756 to 4579d4a Compare March 14, 2026 00:35
@ti-chi-bot ti-chi-bot Bot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/XXL Denotes a PR that changes 1000+ lines, ignoring generated files. labels Mar 14, 2026

@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 (2)
pkg/planner/core/find_best_task.go (2)

848-865: Refresh the comment to match the new semantics.

compareEqOrIn now compares the cached candidatePath.eqOrInCount, which comes from equalPredicateCount(), not the raw path.EqOrInCondCount. The current doc still reads like it is the raw path field.

As per coding guidelines, comments SHOULD explain non-obvious intent, constraints, invariants, concurrency guarantees, SQL/compatibility contracts, or important performance trade-offs.

🤖 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 848 - 865, Update the top
comment for compareEqOrIn to state that the function compares the cached
candidatePath.eqOrInCount (populated via equalPredicateCount()) rather than the
raw path.EqOrInCondCount; explain the invariant that partial index paths
short-circuit comparison (len(path.PartialIndexPaths)>0 returns tie) and clarify
the intent that a larger cached eqOrInCount wins (returns 1 for lhs, -1 for rhs,
0 for tie), so readers know the comparison uses the precomputed/evaluated
equal-predicate count and not the underlying path field.

1159-1164: Consider centralizing the cached candidate-state initialization.

These constructors now all have to stay in lockstep on isMatchProp, isFullRange, and eqOrInCount. A small shared initializer would reduce drift the next time skyline-pruning state grows again.

As per coding guidelines, code SHOULD remain maintainable for future readers with basic TiDB familiarity and SHOULD be self-documenting through clear naming and structure.

Also applies to: 1168-1174, 1178-1192, 1196-1204, 1208-1212

🤖 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 1159 - 1164, Extract the
repeated candidate-state setup into a single helper (e.g., newCandidatePath or
initCandidatePath) and call it from each constructor (including
getTableCandidate and the other similar blocks around candidate creation). The
helper should accept the candidate (or ds, path, prop) and set isMatchProp via
isMatchProp(ds,path,prop), populate accessCondsColMap with
util.ExtractCol2Len(ds.SCtx().GetExprCtx().GetEvalCtx(), path.AccessConds, nil,
nil), set isFullRange via path.IsFullScanRange(ds.TableInfo), and compute
eqOrInCount by calling equalPredicateCount() on the candidate; replace the
duplicated lines in each constructor with a single call to this helper so all
fields are initialized in one place.
🤖 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/find_best_task.go`:
- Around line 848-865: Update the top comment for compareEqOrIn to state that
the function compares the cached candidatePath.eqOrInCount (populated via
equalPredicateCount()) rather than the raw path.EqOrInCondCount; explain the
invariant that partial index paths short-circuit comparison
(len(path.PartialIndexPaths)>0 returns tie) and clarify the intent that a larger
cached eqOrInCount wins (returns 1 for lhs, -1 for rhs, 0 for tie), so readers
know the comparison uses the precomputed/evaluated equal-predicate count and not
the underlying path field.
- Around line 1159-1164: Extract the repeated candidate-state setup into a
single helper (e.g., newCandidatePath or initCandidatePath) and call it from
each constructor (including getTableCandidate and the other similar blocks
around candidate creation). The helper should accept the candidate (or ds, path,
prop) and set isMatchProp via isMatchProp(ds,path,prop), populate
accessCondsColMap with util.ExtractCol2Len(ds.SCtx().GetExprCtx().GetEvalCtx(),
path.AccessConds, nil, nil), set isFullRange via
path.IsFullScanRange(ds.TableInfo), and compute eqOrInCount by calling
equalPredicateCount() on the candidate; replace the duplicated lines in each
constructor with a single call to this helper so all fields are initialized in
one place.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 419c3cc9-4dab-4834-ad84-1318b4c706ac

📥 Commits

Reviewing files that changed from the base of the PR and between f02d756 and 4579d4a.

📒 Files selected for processing (5)
  • pkg/planner/cardinality/row_count_index.go
  • pkg/planner/cardinality/selectivity_test.go
  • pkg/planner/core/find_best_task.go
  • pkg/planner/core/stats.go
  • pkg/planner/util/path.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/planner/cardinality/row_count_index.go
  • pkg/planner/util/path.go
  • pkg/planner/cardinality/selectivity_test.go

@ti-chi-bot ti-chi-bot Bot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Mar 14, 2026
@codecov

codecov Bot commented Mar 14, 2026 •

Copy link
Copy Markdown

Codecov Report

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

Additional details and impacted files
@@               Coverage Diff                @@
##             release-8.5     #66695   +/-   ##
================================================
  Coverage               ?   55.6143%           
================================================
  Files                  ?       1824           
  Lines                  ?     660364           
  Branches               ?          0           
================================================
  Hits                   ?     367257           
  Misses                 ?     266205           
  Partials               ?      26902           
Flag Coverage Δ
integration 38.6811% <89.5522%> (?)
unit 65.5765% <92.5373%> (?)

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

Components Coverage Δ
dumpling 55.5963% <0.0000%> (?)
parser ∅ <0.0000%> (?)
br 56.0762% <0.0000%> (?)
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@terry1purcell terry1purcell changed the title planner: optimize for full range (#66304) planner: optimize for full range (#66304) | tidb-test=pr/2708 Mar 14, 2026
@EmmaDuDu

Copy link
Copy Markdown

/retest

@terry1purcell

Copy link
Copy Markdown
Contributor

/retest-required

terry1purcell and others added 3 commits May 18, 2026 19:30
The helper is called from both table and index candidate factories,
so the previous "table scan" wording was misleading. Reword to cover
access paths generally and note why tableInfo is needed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… fast path

The previous test set the index to FullLoad, which made
IndexStatsIsInvalid a no-op for the async-load queue — so the test
would pass even if canSkipIndexEstimation were removed or if the
fast-path reorder were reverted.

Switch the mock index to NewStatsAllEvictedStatus so IndexStatsIsInvalid
would queue it for async load if it were ever reached, and assert that
AsyncLoadHistogramNeededItems does not contain the index after the
full-range call. This now fails if the fast path moves back below
IndexStatsIsInvalid. Switch sctx to the testkit's real session via
GetPlanCtx() since recordUsedItemStatsStatus needs a domain to resolve
the table when the stats are not FullLoad.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
TestCanSkipIndexEstimation imports pkg/statistics/asyncload to assert
the fast path does not queue the evicted index for async load. Add
the missing strict dep so nogo / bazel_build don't fail.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@terry1purcell

Copy link
Copy Markdown
Contributor

/retest-required

@ti-chi-bot ti-chi-bot Bot added cherry-pick-approved Cherry pick PR approved by release team. and removed do-not-merge/cherry-pick-not-approved labels May 29, 2026
@ti-chi-bot

ti-chi-bot Bot commented Jun 2, 2026

Copy link
Copy Markdown

[APPROVALNOTIFIER] This PR is APPROVED

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

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

@qw4990

qw4990 commented Jun 2, 2026

Copy link
Copy Markdown
Contributor

/unhold

@ti-chi-bot ti-chi-bot Bot removed the do-not-merge/hold Indicates that a PR should not merge because someone has issued a /hold command. label Jun 2, 2026
@ti-chi-bot
ti-chi-bot Bot merged commit d9e323f into pingcap:release-8.5 Jun 2, 2026
20 checks passed
@ti-chi-bot
ti-chi-bot Bot deleted the cherry-pick-66304-to-release-8.5 branch June 2, 2026 03:06
@ti-chi-bot ti-chi-bot Bot added do-not-merge/cherry-pick-not-approved cherry-pick-approved Cherry pick PR approved by release team. and removed cherry-pick-approved Cherry pick PR approved by release team. do-not-merge/cherry-pick-not-approved labels Jun 2, 2026
@ti-chi-bot ti-chi-bot Bot added do-not-merge/cherry-pick-not-approved cherry-pick-approved Cherry pick PR approved by release team. and removed cherry-pick-approved Cherry pick PR approved by release team. do-not-merge/cherry-pick-not-approved labels Jun 8, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

approved cherry-pick-approved Cherry pick PR approved by release team. lgtm 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/L Denotes a PR that changes 100-499 lines, ignoring generated files. type/cherry-pick-for-release-8.5 This PR is cherry-picked to release-8.5 from a source PR.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

8 participants