Skip to content

[DNM] planner, statistics: use selected partition stats for dynamic pruning#67139

Closed
Reminiscent wants to merge 2 commits into
pingcap:release-8.5from
Reminiscent:codex/release-8.5-selected-partition-stats
Closed

[DNM] planner, statistics: use selected partition stats for dynamic pruning#67139
Reminiscent wants to merge 2 commits into
pingcap:release-8.5from
Reminiscent:codex/release-8.5-selected-partition-stats

Conversation

@Reminiscent
Copy link
Copy Markdown
Contributor

@Reminiscent Reminiscent commented Mar 19, 2026

What problem does this PR solve?

This is cherry pick of #66920

Issue Number: ref #66919

Problem Summary:
TiDB uses dynamic partition pruning by default. In some queries that only touch a subset of partitions, cardinality estimation still falls back to the partition table stats path, which is less accurate than using stats merged from the selected partitions. Reusing such partition-specific pruning information through plan cache is also unsafe.

What changed and how does it work?

Reuse statically-determined partition pruning results in the dynamic pruning path to collect the selected partition IDs for stats estimation.
Merge stats from the selected partitions at runtime instead of using partition table global stats.
Mark statements that rely on static partition pruning information as uncacheable, and skip cached plans when rebuilding them would depend on partition-specific pruning results.
Add regression coverage for merged partition stats and plan-cache behavior.

Check List

Tests

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

Side effects

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

Documentation

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

Release note

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

None

Summary by CodeRabbit

  • New Features

    • Added a session variable to enable using selected-partition statistics during dynamic partition pruning.
  • Improvements

    • Better aggregation and use of partition statistics to improve planning for partitioned tables.
    • Plan cache now avoids caching when static partition pruning is in effect to prevent incorrect reuse.
  • Tests

    • Added and updated tests covering dynamic/static partition pruning and selected-partition stats scenarios.

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

ti-chi-bot Bot commented Mar 19, 2026

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

To merge this cherry pick:

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

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

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

coderabbitai Bot commented Mar 19, 2026

📝 Walkthrough

Walkthrough

This PR adds selective partition-statistics support for dynamic partition pruning, introduces session/sysvar controls, tracks prepared-plan build and static-prune states, aggregates per-partition counts, adjusts planner/pruning/plan-cache behavior to avoid unsafe cached plans, and adds/updates tests and BUILD shards.

Changes

Cohort / File(s) Summary
Session vars & stmtctx
pkg/sessionctx/variable/tidb_vars.go, pkg/sessionctx/variable/sysvar.go, pkg/sessionctx/variable/session.go, pkg/sessionctx/variable/setvar_affect.go, pkg/sessionctx/stmtctx/stmtctx.go
Added tidb_opt_enable_selected_partition_stats sysvar and EnableSelectedPartitionStats session field; added InPreparedPlanBuild and StaticPartitionPrune to StatementContext; included sysvar in hint-updatable verification.
Cardinality helper & tests
pkg/planner/cardinality/pseudo.go, pkg/planner/cardinality/ndv_test.go, pkg/planner/cardinality/BUILD.bazel
Added AggregateSelectedPartitionCounts to sum per-partition Realtime/Modify counts with pseudo/nil safety; added unit test and included it in BUILD; increased shard count for one test target.
DataSource / stats plumbing
pkg/planner/core/operator/logicalop/logical_datasource.go, pkg/planner/core/logical_plan_builder.go, pkg/planner/core/stats.go, pkg/planner/core/collect_column_stats_usage.go
Added StaticPrunedPartitionIDs to DataSource; extended getStatsTable to accept partition IDs and optionally overwrite Realtime/Modify with aggregated selected-partition counts; adjusted where partition IDs are recorded for predicate/stat collection.
Partition processor & pruning logic
pkg/planner/core/rule_partition_processor.go, pkg/planner/core/planbuilder.go
Refactored pruning to compute static-pruned partition IDs, avoid static pruning during prepared-plan build, set StaticPartitionPrune info conditionally, and restrict global-index removal to static-prune path.
Plan cache safety
pkg/planner/core/plan_cache.go, pkg/planner/core/plan_cache_utils.go
Added detection to skip cached plans when static partition pruning would change partition selection; temporarily disable static pruning during prepared-plan build and mark skip reason/warning when caching is disallowed.
Tests & test sharding
pkg/planner/core/plan_cache_partition_table_test.go, pkg/planner/core/casetest/instanceplancache/others_test.go, pkg/planner/core/casetest/planstats/plan_stats_test.go, pkg/planner/core/tests/prepare/*, pkg/planner/core/casetest/planstats/BUILD.bazel, pkg/planner/core/tests/prepare/BUILD.bazel
Added/updated tests to cover selected-partition stats, dynamic/static pruning, and plan-cache behavior; refactored several tests to run across selected-partition-stats modes; added flexible warning/plan assertions; adjusted shard_counts for some test targets.
Planbuilder / union handling
pkg/planner/core/logical_plan_builder.go, pkg/planner/core/rule_partition_processor.go
Changed UnionScan extra-phys-tbl-id behavior for dynamic prune mode; postponed per-partition child construction until pruning results are known and preserved optimizer hints per-partition.

Sequence Diagram(s)

sequenceDiagram
    participant S as Session/Vars
    participant P as Planner/Builder
    participant PP as PartitionProcessor
    participant SC as StatsCollector
    participant PC as PlanCache

    S->>P: Build plan (EnableSelectedPartitionStats)
    P->>P: Set InPreparedPlanBuild=true (for prepared build)
    P->>PP: Request pruning
    PP-->>P: StaticPrunedPartitionIDs (or nil) + pruned result
    P->>SC: Request per-partition stats when StaticPrunedPartitionIDs present
    SC-->>P: Partition stats
    P->>P: AggregateSelectedPartitionCounts -> override RealtimeCount if ok
    P->>PC: Query cached plan
    PC->>PC: shouldSkipCachedPlanForStaticPartitionPruning?
    alt skip
        PC-->>S: Skip cache (SetSkipPlanCache)
    else use
        PC-->>S: Return cached plan
    end
    S->>P: Execute final plan
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested labels

component/statistics, type/cherry-pick-for-release-8.5, ok-to-test

Suggested reviewers

  • qw4990
  • AilinKid
  • yudongusa

Poem

🐰 Hopping through partitions, nose to the ground,
I counted the rows that I happily found.
When stats are selected and pruners agree,
Plans skip the unsafe and run fast and free.
A carrot for tests — hooray, cached plans flee!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 15.91% 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 description follows the required template structure with issue reference, problem summary, and detailed explanation of changes. It covers the motivation (inaccurate cardinality estimation), the solution approach (reusing partition pruning results for stats), and testing (unit and integration tests).
Title check ✅ Passed The title clearly summarizes the main change: enabling dynamic partition pruning to use selected partition statistics instead of global stats.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
📝 Coding Plan
  • Generate coding plan for human review comments

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

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

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

175-182: Document the temporary StmtCtx override.

The save/reset/restore around destBuilder.Build is a non-obvious planner invariant. A short comment here would make future edits much less error-prone.

As per coding guidelines, "Comments SHOULD explain non-obvious intent, constraints, invariants, concurrency guarantees, SQL/compatibility contracts, or important performance trade-offs, and SHOULD NOT restate what the code already makes clear"

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

In `@pkg/planner/core/plan_cache_utils.go` around lines 175 - 182, Add a short
clarifying comment above the block that temporarily saves and overrides
vars.StmtCtx.InPreparedPlanBuild and vars.StmtCtx.StaticPartitionPrune around
the call to destBuilder.Build(ctx, nodeW), explaining that the planner
intentionally sets InPreparedPlanBuild (to isPrepStmt && cacheable) and disables
StaticPartitionPrune only for the duration of building this prepared plan node
and must restore the previous values afterward to preserve planner invariants
for subsequent planning; reference the specific symbols
vars.StmtCtx.InPreparedPlanBuild, vars.StmtCtx.StaticPartitionPrune,
destBuilder.Build and nodeW to make the purpose and necessity of the
save/restore explicit.
pkg/planner/core/casetest/planstats/plan_stats_test.go (1)

641-642: Consider avoiding a root-node hard cast to *PhysicalTableReader in this test.

At Line 641, strict root type assertion can make the test fragile to benign optimizer shape changes. A recursive lookup for the first table reader would keep the test focused on stats semantics.

Possible refactor
-		reader, ok := plan.(*plannercore.PhysicalTableReader)
-		require.True(t, ok)
+		reader := findFirstTableReader(plan)
+		require.NotNil(t, reader)
 		require.Equal(t, tc.expectedRowCount, reader.StatsInfo().HistColl.RealtimeCount)
 	}
 }
+
+func findFirstTableReader(p base.Plan) *plannercore.PhysicalTableReader {
+	if r, ok := p.(*plannercore.PhysicalTableReader); ok {
+		return r
+	}
+	pp, ok := p.(base.PhysicalPlan)
+	if !ok {
+		return nil
+	}
+	for _, child := range pp.Children() {
+		if r := findFirstTableReader(child); r != nil {
+			return r
+		}
+	}
+	return nil
+}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/planner/core/casetest/planstats/plan_stats_test.go` around lines 641 -
642, The test currently does a brittle root-node type assertion (reader, ok :=
plan.(*plannercore.PhysicalTableReader)); instead, implement a small
recursive/walk helper that traverses the plan tree and returns the first node of
type *plannercore.PhysicalTableReader (e.g., findFirstPhysicalTableReader(plan
plannercore.Plan) or walkPlanNodes) and use that result in the assertions; this
keeps the test resilient to harmless plan-shape/optimizer changes while still
validating the reader's statistics.
🤖 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_plan_builder.go`:
- Around line 4557-4559: The current guard using hasGlobalIndex around the
dynamic-prune fallback causes the static-prune fallback to be skipped for
partitioned tables with global indexes; change the logic so that the
static-prune fallback (the code that disables dynamic pruning and sets
StmtCtx.UseDynamicPruneMode = false and emits the warning) always runs when
global stats are missing, while only gating the selected-partition-stats reuse
branch behind hasGlobalIndex; specifically update the block around
slices.ContainsFunc(tableInfo.Indices, ...) / hasGlobalIndex so it only
influences the reuse path (the code in the selected-partition-stats branch) and
not the fallback that flips StmtCtx.UseDynamicPruneMode, and add a planner unit
test/regression for a partitioned table with a global index and missing global
stats (and update related rule testdata as needed, also apply the same fix for
the other occurrence around lines 4587-4595).
- Around line 4223-4230: The current logic reuses
statsHandle.GetPhysicalTableStats(tblInfo.ID, tblInfo) and only overrides
RealtimeCount/ModifyCount, which causes the merged selected-partition stats to
inherit the global table's pseudo/uninitialized state; instead, when
cardinality.AggregateSelectedPartitionCounts returns ok, build statsTbl from the
selected partitions' merged stats (don’t start from
GetPhysicalTableStats(tblInfo.ID,...)), e.g. obtain or construct a TableStats
object that reflects the selected partitions' initialization/state and set
RealtimeCount and ModifyCount from realtimeCount/modifyCount, then set
selectedPartitionCountsOverridden = true so the optimizer uses the partitions'
real initialization status rather than the global pseudo one.

In `@pkg/planner/core/plan_cache_utils.go`:
- Around line 187-190: The warning text is incorrect for the non-prepared
branch: when cacheable && !isPrepStmt && staticPartitionPrune is true you
currently append "skip prepared plan-cache" which misleads SHOW WARNINGS; update
the message produced in sctx.GetSessionVars().StmtCtx.AppendWarning(...) to use
the non-prepared cache warning prefix (e.g., "skip non-prepared plan-cache:
"+reason or the existing non-prepared prefix used elsewhere) so the warning
matches the condition where isPrepStmt is false and reason is "static partition
prune mode used".

In `@pkg/planner/core/plan_cache.go`:
- Around line 384-386: The helper currently treats an access object with a
populated accessObj.err as a non-error by returning (false, nil); instead, when
getDynamicAccessPartition(sctx, tblInfo, partInfo, "") yields accessObj != nil
and accessObj.err is non-empty, stop treating the partition resolution as a
cacheable hit and propagate the error (return false, accessObj.err or wrap and
return that error) so callers know partition resolution failed; update the
branch that checks accessObj and accessObj.err to return a non-nil error rather
than nil.
- Around line 297-317: The current skip logic in
shouldSkipCachedPlanForStaticPartitionPruning (and the similar predicates at the
other noted locations) uses the observed partition subset size to decide to skip
cached plans; change it to detect whether partition pruning is runtime-dependent
instead of checking "fewer than all partitions". Specifically, update
cachedPlanUsesStaticPartitionPruning (and callers like
shouldSkipCachedPlanForStaticPartitionPruning, and the analogous predicates at
the other spots) to return true only when pruning decisions depend on runtime
values (e.g., expressions or parameters evaluated at execution) and return false
when the partition set is statically fixed by the statement/schema (such as
PARTITION(...)). Ensure the guard uses that runtime-dependent flag to skip cache
reuse and leaves cached plans usable for fixed/static partition subsets.

In `@pkg/planner/core/rule_partition_processor.go`:
- Around line 1917-1932: The computation of staticPruned currently uses
len(prunedPartitionIDs) < len(pi.Definitions) which can be true for fixed
PARTITION(...) or dropping/overlapping partitions even when runtime predicates
pruned nothing; change the check that sets staticPruned (and the subsequent
calls to setStaticPartitionPruneInfo and setting ds.StaticPrunedPartitionIDs) to
compare prunedPartitionIDs against the post-filter candidate baseline (the
candidate set after applying PartitionNames/non-dropping logic) rather than
pi.Definitions. Locate the staticPruned assignment and replace its baseline with
the post-PartitionNames/non-dropping candidate count (or an explicitly tracked
baseline variable computed earlier), ensuring the rest of the branch that uses
staticPruned (the calls to setStaticPartitionPruneInfo, the
UseDynamicPruneMode/EnableSelectedPartitionStats guards, and
ds.StaticPrunedPartitionIDs) only run when runtime predicate pruning actually
removed partitions beyond that baseline.

---

Nitpick comments:
In `@pkg/planner/core/casetest/planstats/plan_stats_test.go`:
- Around line 641-642: The test currently does a brittle root-node type
assertion (reader, ok := plan.(*plannercore.PhysicalTableReader)); instead,
implement a small recursive/walk helper that traverses the plan tree and returns
the first node of type *plannercore.PhysicalTableReader (e.g.,
findFirstPhysicalTableReader(plan plannercore.Plan) or walkPlanNodes) and use
that result in the assertions; this keeps the test resilient to harmless
plan-shape/optimizer changes while still validating the reader's statistics.

In `@pkg/planner/core/plan_cache_utils.go`:
- Around line 175-182: Add a short clarifying comment above the block that
temporarily saves and overrides vars.StmtCtx.InPreparedPlanBuild and
vars.StmtCtx.StaticPartitionPrune around the call to destBuilder.Build(ctx,
nodeW), explaining that the planner intentionally sets InPreparedPlanBuild (to
isPrepStmt && cacheable) and disables StaticPartitionPrune only for the duration
of building this prepared plan node and must restore the previous values
afterward to preserve planner invariants for subsequent planning; reference the
specific symbols vars.StmtCtx.InPreparedPlanBuild,
vars.StmtCtx.StaticPartitionPrune, destBuilder.Build and nodeW to make the
purpose and necessity of the save/restore explicit.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: fe448d13-8e63-4fbb-9b52-9721531321a6

📥 Commits

Reviewing files that changed from the base of the PR and between cd46301 and 1348486.

📒 Files selected for processing (19)
  • pkg/planner/cardinality/BUILD.bazel
  • pkg/planner/cardinality/pseudo.go
  • pkg/planner/core/casetest/instanceplancache/others_test.go
  • pkg/planner/core/casetest/planstats/plan_stats_test.go
  • pkg/planner/core/collect_column_stats_usage.go
  • pkg/planner/core/logical_plan_builder.go
  • pkg/planner/core/operator/logicalop/logical_datasource.go
  • pkg/planner/core/plan_cache.go
  • pkg/planner/core/plan_cache_partition_table_test.go
  • pkg/planner/core/plan_cache_utils.go
  • pkg/planner/core/planbuilder.go
  • pkg/planner/core/rule_partition_processor.go
  • pkg/planner/core/stats.go
  • pkg/planner/core/tests/prepare/prepare_test.go
  • pkg/sessionctx/stmtctx/stmtctx.go
  • pkg/sessionctx/variable/session.go
  • pkg/sessionctx/variable/setvar_affect.go
  • pkg/sessionctx/variable/sysvar.go
  • pkg/sessionctx/variable/tidb_vars.go

Comment on lines +4223 to +4230
// Reuse the global stats objects and only narrow the table-level counts to the selected partitions.
statsTbl = statsHandle.GetPhysicalTableStats(tblInfo.ID, tblInfo)
if realtimeCount, modifyCount, ok := cardinality.AggregateSelectedPartitionCounts(uniquePartitionStats); ok {
statsTbl = statsTbl.ShallowCopy()
statsTbl.RealtimeCount = realtimeCount
statsTbl.ModifyCount = modifyCount
selectedPartitionCountsOverridden = true
}
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

Don't anchor merged selected-partition stats on a pseudo global table.

For the multi-partition case, this always starts from tblInfo.ID stats and only overrides the counts. If the global table stats are uninitialized/pseudo while the selected partitions all have non-pseudo stats, the later pseudo checks still win and the optimizer drops back to pseudo stats again. The merged result needs to come from the selected partition stats themselves instead of inheriting the global table's initialization state.

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

In `@pkg/planner/core/logical_plan_builder.go` around lines 4223 - 4230, The
current logic reuses statsHandle.GetPhysicalTableStats(tblInfo.ID, tblInfo) and
only overrides RealtimeCount/ModifyCount, which causes the merged
selected-partition stats to inherit the global table's pseudo/uninitialized
state; instead, when cardinality.AggregateSelectedPartitionCounts returns ok,
build statsTbl from the selected partitions' merged stats (don’t start from
GetPhysicalTableStats(tblInfo.ID,...)), e.g. obtain or construct a TableStats
object that reflects the selected partitions' initialization/state and set
RealtimeCount and ModifyCount from realtimeCount/modifyCount, then set
selectedPartitionCountsOverridden = true so the optimizer uses the partitions'
real initialization status rather than the global pseudo one.

Comment thread pkg/planner/core/logical_plan_builder.go
Comment thread pkg/planner/core/plan_cache_utils.go
Comment on lines +297 to +317
func shouldSkipCachedPlanForStaticPartitionPruning(sctx sessionctx.Context, plan base.Plan) (bool, error) {
if !sctx.GetSessionVars().StmtCtx.UseDynamicPartitionPrune() || !sctx.GetSessionVars().EnableSelectedPartitionStats {
return false, nil
}
switch p := plan.(type) {
case *Update:
if p.SelectPlan == nil {
return false, nil
}
return cachedPlanUsesStaticPartitionPruning(sctx, p.SelectPlan)
case *Delete:
if p.SelectPlan == nil {
return false, nil
}
return cachedPlanUsesStaticPartitionPruning(sctx, p.SelectPlan)
case base.PhysicalPlan:
return cachedPlanUsesStaticPartitionPruning(sctx, p)
default:
return false, nil
}
}
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

Base the cache-skip decision on runtime pruning, not subset size.

This guard now skips reuse whenever the execution touches fewer than all partitions. That also matches fixed subsets from PARTITION (...) and other invariant partition restrictions, where the partition set is part of the statement/schema and does not vary per execution. Those statements will miss plan cache on every reuse even though the cached plan is safe. The skip predicate needs to distinguish runtime-dependent pruning from a fixed partition set.

Also applies to: 380-389, 414-418, 446-458

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

In `@pkg/planner/core/plan_cache.go` around lines 297 - 317, The current skip
logic in shouldSkipCachedPlanForStaticPartitionPruning (and the similar
predicates at the other noted locations) uses the observed partition subset size
to decide to skip cached plans; change it to detect whether partition pruning is
runtime-dependent instead of checking "fewer than all partitions". Specifically,
update cachedPlanUsesStaticPartitionPruning (and callers like
shouldSkipCachedPlanForStaticPartitionPruning, and the analogous predicates at
the other spots) to return true only when pruning decisions depend on runtime
values (e.g., expressions or parameters evaluated at execution) and return false
when the partition set is statically fixed by the statement/schema (such as
PARTITION(...)). Ensure the guard uses that runtime-dependent flag to skip cache
reuse and leaves cached plans usable for fixed/static partition subsets.

Comment thread pkg/planner/core/plan_cache.go Outdated
Comment on lines +384 to +386
accessObj := getDynamicAccessPartition(sctx, tblInfo, partInfo, "")
if accessObj == nil || len(accessObj.err) > 0 {
return false, nil
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

Don't turn partition-resolution failures into cache hits.

If getDynamicAccessPartition() returns an access object with err populated, this helper currently returns (false, nil) and lets cached-plan reuse continue. Since this function is the safety gate, an unresolved partition set should conservatively skip cache (or be surfaced), not fall through as reusable.

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

In `@pkg/planner/core/plan_cache.go` around lines 384 - 386, The helper currently
treats an access object with a populated accessObj.err as a non-error by
returning (false, nil); instead, when getDynamicAccessPartition(sctx, tblInfo,
partInfo, "") yields accessObj != nil and accessObj.err is non-empty, stop
treating the partition resolution as a cacheable hit and propagate the error
(return false, accessObj.err or wrap and return that error) so callers know
partition resolution failed; update the branch that checks accessObj and
accessObj.err to return a non-nil error rather than nil.

Comment thread pkg/planner/core/rule_partition_processor.go Outdated
@codecov
Copy link
Copy Markdown

codecov Bot commented Mar 19, 2026

Codecov Report

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

Additional details and impacted files
@@               Coverage Diff                @@
##             release-8.5     #67139   +/-   ##
================================================
  Coverage               ?   55.0291%           
================================================
  Files                  ?       1823           
  Lines                  ?     655195           
  Branches               ?          0           
================================================
  Hits                   ?     360548           
  Misses                 ?     267774           
  Partials               ?      26873           
Flag Coverage Δ
integration 38.2542% <21.7630%> (?)

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

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

Keep the release-8.5 selected-partition-stats backport compatible with existing planner behavior while preserving the new feature where it is safe.

This commit:
- adds the partition-count aggregation unit test and updates Bazel metadata for the new tests
- keeps explicit PARTITION() queries cacheable while still rejecting runtime-sensitive partition pruning for cached plans
- preserves the legacy dynamic-pruning fallback when global stats are missing, including partitioned tables with global indexes
- avoids adding ExtraPhysTblID to static-pruning UnionScan plans so default-off behavior does not change existing plan shapes
- falls back to the legacy global/pseudo stats path for multi-partition queries when table-level global stats are still pseudo or uninitialized
- adds regression tests for missing global stats, explicit partition plan-cache reuse, and selected partition stats behavior
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 (2)
pkg/planner/cardinality/ndv_test.go (1)

35-39: Assert returned counts on the failure path as well.

The pseudo case currently checks only ok. Adding count assertions locks in the (0, 0, false) contract and catches regressions earlier.

📌 Suggested test tightening
-	_, _, ok = AggregateSelectedPartitionCounts([]*statistics.Table{
+	realtimeCount, modifyCount, ok = AggregateSelectedPartitionCounts([]*statistics.Table{
 		{HistColl: *statistics.NewHistColl(1, true, 10, 2, 0, 0)},
 		pseudoStats,
 	})
 	require.False(t, ok)
+	require.Equal(t, int64(0), realtimeCount)
+	require.Equal(t, int64(0), modifyCount)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/planner/cardinality/ndv_test.go` around lines 35 - 39, The test calls
AggregateSelectedPartitionCounts but only asserts the boolean `ok`; update the
call to capture the two returned counts (e.g., c1, c2, ok :=
AggregateSelectedPartitionCounts(...)) and add assertions that c1 and c2 equal 0
when ok is false (use require.Equal or similar) to lock in the (0, 0, false)
contract — reference the AggregateSelectedPartitionCounts call in ndv_test.go
and the existing require.False assertion to replace with count checks plus
require.False.
pkg/planner/core/plan_cache_partition_table_test.go (1)

364-365: Consider defensive check for warning presence.

Lines 365 and 375 unconditionally access Rows()[0][2], which will panic if no warnings are returned. Other tests in this file use defensive checks like:

if warnings := tk.MustQuery(`show warnings`).Rows(); len(warnings) > 0 {
    requireStaticPartitionPruneWarning(t, warnings[0][2].(string))
}

If the assertion is that warnings must be present, consider using require.NotEmpty first for a clearer failure message:

warnings := tk.MustQuery(`show warnings`).Rows()
require.NotEmpty(t, warnings, "expected static partition prune warning")
requireStaticPartitionPruneWarning(t, warnings[0][2].(string))
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@pkg/planner/core/plan_cache_partition_table_test.go` around lines 364 - 365,
The test unconditionally indexes show warnings result which can panic if empty;
change the two places that access tk.MustQuery(`show warnings`).Rows()[0][2] to
first capture warnings := tk.MustQuery(`show warnings`).Rows(), assert they are
present with require.NotEmpty(t, warnings, "expected static partition prune
warning") (or use len check like other tests), then call
requireStaticPartitionPruneWarning(t, warnings[0][2].(string)); update
references around the assertions involving
tk.Session().GetSessionVars().FoundInPlanCache and
requireStaticPartitionPruneWarning to use this defensive check.
🤖 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/cardinality/ndv_test.go`:
- Around line 35-39: The test calls AggregateSelectedPartitionCounts but only
asserts the boolean `ok`; update the call to capture the two returned counts
(e.g., c1, c2, ok := AggregateSelectedPartitionCounts(...)) and add assertions
that c1 and c2 equal 0 when ok is false (use require.Equal or similar) to lock
in the (0, 0, false) contract — reference the AggregateSelectedPartitionCounts
call in ndv_test.go and the existing require.False assertion to replace with
count checks plus require.False.

In `@pkg/planner/core/plan_cache_partition_table_test.go`:
- Around line 364-365: The test unconditionally indexes show warnings result
which can panic if empty; change the two places that access tk.MustQuery(`show
warnings`).Rows()[0][2] to first capture warnings := tk.MustQuery(`show
warnings`).Rows(), assert they are present with require.NotEmpty(t, warnings,
"expected static partition prune warning") (or use len check like other tests),
then call requireStaticPartitionPruneWarning(t, warnings[0][2].(string)); update
references around the assertions involving
tk.Session().GetSessionVars().FoundInPlanCache and
requireStaticPartitionPruneWarning to use this defensive check.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3041c2f8-7426-4b5c-85a1-62d0598c34f1

📥 Commits

Reviewing files that changed from the base of the PR and between 1348486 and 2ac25d1.

📒 Files selected for processing (9)
  • pkg/planner/cardinality/BUILD.bazel
  • pkg/planner/cardinality/ndv_test.go
  • pkg/planner/core/casetest/planstats/BUILD.bazel
  • pkg/planner/core/casetest/planstats/plan_stats_test.go
  • pkg/planner/core/logical_plan_builder.go
  • pkg/planner/core/plan_cache.go
  • pkg/planner/core/plan_cache_partition_table_test.go
  • pkg/planner/core/rule_partition_processor.go
  • pkg/planner/core/tests/prepare/BUILD.bazel
✅ Files skipped from review due to trivial changes (4)
  • pkg/planner/core/casetest/planstats/BUILD.bazel
  • pkg/planner/core/tests/prepare/BUILD.bazel
  • pkg/planner/cardinality/BUILD.bazel
  • pkg/planner/core/plan_cache.go
🚧 Files skipped from review as they are similar to previous changes (3)
  • pkg/planner/core/casetest/planstats/plan_stats_test.go
  • pkg/planner/core/rule_partition_processor.go
  • pkg/planner/core/logical_plan_builder.go

@Reminiscent Reminiscent changed the title planner, statistics: use selected partition stats for dynamic pruning [DNM] planner, statistics: use selected partition stats for dynamic pruning Mar 19, 2026
@hawkingrei
Copy link
Copy Markdown
Member

/retest

@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented Mar 19, 2026

[APPROVALNOTIFIER] This PR is NOT APPROVED

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

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

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

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

@ti-chi-bot ti-chi-bot Bot added the needs-1-more-lgtm Indicates a PR needs 1 more LGTM. label Mar 19, 2026
@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented Mar 19, 2026

[LGTM Timeline notifier]

Timeline:

  • 2026-03-19 11:59:49.334526318 +0000 UTC m=+444716.422183855: ☑️ agreed by hawkingrei.

@tiprow
Copy link
Copy Markdown

tiprow Bot commented Mar 19, 2026

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

Test name Commit Details Required Rerun command
fast_test_tiprow_for_release 2ac25d1 link true /test fast_test_tiprow_for_release

Full PR test history. Your PR dashboard.

Details

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

@ti-chi-bot
Copy link
Copy Markdown

ti-chi-bot Bot commented Mar 19, 2026

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

Test name Commit Details Required Rerun command
idc-jenkins-ci-tidb/unit-test 2ac25d1 link true /test unit-test

Full PR test history. Your PR dashboard.

Details

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

@mjonss
Copy link
Copy Markdown
Contributor

mjonss commented Mar 24, 2026

@hawkingrei why did you approve this [DNM] PR of a backport of a non-merged PR?

@Reminiscent Reminiscent closed this Apr 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

do-not-merge/cherry-pick-not-approved needs-1-more-lgtm Indicates a PR needs 1 more LGTM. 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.

3 participants