Repository: pingcap/tidb
File: pkg/statistics/handle/globalstats/global_stats.go (Go)

Code before:
		}
	}

	// After collect all the statistics from the partition-level stats,
	// we should merge them together.
	for i := range globalStats.Num {
		if len(allHg[i]) == 0 {
			// If all partitions have no stats, we skip merging global stats because it may not handle the case `len(allHg[i]) == 0`
			// correctly. It can avoid unexpected behaviors such as nil pointer panic.
			continue
		}
		// FMSketch use many memory, so we first deal with it and then destroy it.
		// Merge FMSketch.
		// NOTE: allFms maybe contain empty.
		globalStats.Fms[i] = allFms[i][0]
		for j := 1; j < len(allFms[i]); j++ {
			if globalStats.Fms[i] == nil {
				globalStats.Fms[i] = allFms[i][j]
			} else {
				globalStats.Fms[i].MergeFMSketch(allFms[i][j])
				allFms[i][j] = nil
			}
		}

		// Update the global NDV.
		globalStatsNDV := min(globalStats.Fms[i].NDV(), globalStats.Count)
		globalStats.Fms[i] = nil

		// Merge CMSketch.
		globalStats.Cms[i] = allCms[i][0]
		for j := 1; j < len(allCms[i]); j++ {
			err = globalStats.Cms[i].MergeCMSketch(allCms[i][j])
			if err != nil {
				return
			}
		}

		// Merge topN.
		// Note: We need to merge TopN before merging the histogram.
		// Because after merging TopN, some numbers will be left.
		// These remaining topN numbers will be used as a separate bucket for later histogram merging.
		var poppedTopN []statistics.TopNMeta
		wrapper := NewStatsWrapper(allHg[i], allTopN[i])
		globalStats.TopN[i], poppedTopN, allHg[i], err = mergeGlobalStatsTopN(gpool, sc, wrapper,
			sc.GetSessionVars().StmtCtx.TimeZone(), sc.GetSessionVars().AnalyzeVersion, uint32(opts[ast.AnalyzeOptNumTopN]), isIndex)
		if err != nil {
			return
		}

		// Merge histogram.
		globalStats.Hg[i], err = statistics.MergePartitionHist2GlobalHist(sc.GetSessionVars().StmtCtx, allHg[i], poppedTopN,

Review comment (bug):
globalStats.Fms[i] can still end up nil here if allFms[i] contains only nil entries (e.g. when the partition has histograms but no stored FM sketch rows). In that case, globalStats.Fms[i].NDV() will panic. Please add a nil guard before computing globalStatsNDV (and decide on a fallback, such as using hg.NDV or 0) to avoid crashing during global stats merge.
```suggestion
		globalStatsNDV := uint64(0)
		if globalStats.Fms[i] != nil {
			globalStatsNDV = min(globalStats.Fms[i].NDV(), globalStats.Count)
		}
```

Code after:
		}
	}

	// After collect all the statistics from the partition-level stats,
	// we should merge them together.
	for i := range globalStats.Num {
		if len(allHg[i]) == 0 {
			// If all partitions have no stats, we skip merging global stats because it may not handle the case `len(allHg[i]) == 0`
			// correctly. It can avoid unexpected behaviors such as nil pointer panic.
			continue
		}
		// FMSketch use many memory, so we first deal with it and then destroy it.
		// Merge FMSketch.
		// NOTE: allFms maybe contain empty.
		globalStats.Fms[i] = allFms[i][0]
		for j := 1; j < len(allFms[i]); j++ {
			if globalStats.Fms[i] == nil {
				globalStats.Fms[i] = allFms[i][j]
			} else {
				globalStats.Fms[i].MergeFMSketch(allFms[i][j])
				allFms[i][j] = nil // Release for GC.
			}
		}

		// Update the global NDV.
		globalStatsNDV := min(globalStats.Fms[i].NDV(), globalStats.Count)
		globalStats.Fms[i] = nil // Release for GC.

		// Merge CMSketch.
		globalStats.Cms[i] = allCms[i][0]
		for j := 1; j < len(allCms[i]); j++ {
			err = globalStats.Cms[i].MergeCMSketch(allCms[i][j])
			if err != nil {
				return
			}
		}

		// Merge topN.
		// Note: We need to merge TopN before merging the histogram.
		// Because after merging TopN, some numbers will be left.
		// These remaining topN numbers will be used as a separate bucket for later histogram merging.
		var poppedTopN []statistics.TopNMeta
		wrapper := NewStatsWrapper(allHg[i], allTopN[i])
		globalStats.TopN[i], poppedTopN, allHg[i], err = mergeGlobalStatsTopN(gpool, sc, wrapper,
			sc.GetSessionVars().StmtCtx.TimeZone(), sc.GetSessionVars().AnalyzeVersion, uint32(opts[ast.AnalyzeOptNumTopN]), isIndex)
		if err != nil {
			return
		}

		// Merge histogram.
		globalStats.Hg[i], err = statistics.MergePartitionHist2GlobalHist(sc.GetSessionVars().StmtCtx, allHg[i], poppedTopN,