Skip to content

Commit 9452072

Browse files
terry1purcellclaude
andcommitted
cardinality: assert TestCanSkipIndexEstimation actually exercises the 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>
1 parent c13524f commit 9452072

1 file changed

Lines changed: 24 additions & 3 deletions

File tree

pkg/planner/cardinality/selectivity_test.go

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -575,26 +575,47 @@ func TestCanSkipIndexEstimation(t *testing.T) {
575575
require.NoError(t, err)
576576
idxValues[i].SetBytes(enc)
577577
}
578+
// Mark the index as NOT fully loaded so we can prove the fast path runs before
579+
// IndexStatsIsInvalid: under a not-fully-loaded status, the slow path would queue
580+
// this index into AsyncLoadHistogramNeededItems, and the assertion below would
581+
// fail if canSkipIndexEstimation no longer short-circuited the call.
578582
idxHist := mockStatsHistogram(tblInfo.Indices[0].ID, idxValues, 1, types.NewFieldType(mysql.TypeBlob))
579583
idxHist.NullCount = nullCount
580584
statsTbl.SetIdx(tblInfo.Indices[0].ID, &statistics.Index{
581585
Histogram: *idxHist,
582586
Info: tblInfo.Indices[0],
583-
StatsLoadedStatus: statistics.NewStatsFullLoadStatus(),
587+
StatsLoadedStatus: statistics.NewStatsAllEvictedStatus(),
584588
StatsVer: 2,
585589
})
586590
generateMapsForMockStatsTbl(statsTbl)
587591

588592
idxID := tblInfo.Indices[0].ID
589-
sctx := mock.NewContext()
593+
// Use the testkit's real session so recordUsedItemStatsStatus can resolve the
594+
// table via domain.GetDomain(sctx).InfoSchema(); the bare mock.NewContext() has
595+
// no domain registered and would panic now that the index is not FullLoad.
596+
sctx := tk.Session().GetPlanCtx()
597+
idxItem := model.TableItemID{TableID: tblInfo.ID, ID: idxID, IsIndex: true}
598+
hasAsyncLoadEntry := func() bool {
599+
for _, item := range asyncload.AsyncLoadHistogramNeededItems.AllItems() {
600+
if item.TableItemID == idxItem {
601+
return true
602+
}
603+
}
604+
return false
605+
}
590606

591607
// Full range including NULLs [NULL, +inf) triggers canSkipIndexEstimation fast path.
592-
// Result should equal RealtimeCount exactly (no histogram estimation).
608+
// Result should equal RealtimeCount exactly (no histogram estimation), and the fast
609+
// path must run before IndexStatsIsInvalid so the evicted index is NOT queued for
610+
// async load — otherwise the optimization would still pay for the wasted I/O.
611+
asyncload.AsyncLoadHistogramNeededItems.Delete(idxItem)
593612
fullRanges := ranger.FullRange()
594613
countResult, err := cardinality.GetRowCountByIndexRanges(sctx, &statsTbl.HistColl, idxID, fullRanges, nil)
595614
require.NoError(t, err)
596615
require.Equal(t, float64(realtimeCount), countResult.Est,
597616
"full range [NULL,+inf) should use fast path and return RealtimeCount")
617+
require.False(t, hasAsyncLoadEntry(),
618+
"fast path must short-circuit before IndexStatsIsInvalid and not queue the evicted index for async load")
598619

599620
// Full range excluding NULLs [MinNotNull, +inf) must NOT use the fast path.
600621
// With nullCount > 0, the histogram estimate must be strictly below RealtimeCount;

0 commit comments

Comments
 (0)