Repository: pingcap/tidb
File: pkg/planner/core/stats.go (Go)

Code before:
		return nil
	}
	path.AccessConds = res.AccessConds
	path.TableFilters = res.RemainedConds
	path.EqCondCount = res.EqCondCount
	path.EqOrInCondCount = res.EqOrInCount
	path.IsDNFCond = res.IsDNFCond
	path.MinAccessCondsForDNFCond = res.MinAccessCondsForDNFCond
	path.ConstCols = make([]bool, len(path.IdxCols))
	if res.ColumnValues != nil {
		for i := range path.ConstCols {
			path.ConstCols[i] = res.ColumnValues[i] != nil
		}
	}
	indexCols := path.IdxCols
	if len(indexCols) > len(path.Index.Columns) { // remove clustered primary key if it has been added to path.IdxCols
		indexCols = indexCols[0:len(path.Index.Columns)]
	}
	count, err := cardinality.GetRowCountByIndexRanges(sctx, histColl, path.Index.ID, path.Ranges, indexCols)
	path.CountAfterAccess, path.MinCountAfterAccess, path.MaxCountAfterAccess = count.Est, count.MinEst, count.MaxEst
	return err
}

// fixTiCIIndexRangesForIntHandle fixes the TiCI index ranges for int handle.
// For normal index range, its min value is NULL or the MinNotNull, and max value is MaxValue.
// But for TiCI index range which directly uses table's int pk range, we should set the min value to the min int value and max value to max int value.
func fixTiCIIndexRangesForIntHandle(ranges []*ranger.Range, isUnsigned bool) {
	var minDatum, maxDatum types.Datum
	// isUnsigned indicates whether the handle is unsigned.
	// Now we just cast the unsigned int to int and then store the int value inside the Datum.
	// We wrap the uint64/int64 with Datum here to keep us untouched with Datum's internal representation.
	if isUnsigned {
		minDatum = types.NewUintDatum(0)
		maxDatum = types.NewUintDatum(math.MaxUint64)
	} else {
		minDatum = types.NewIntDatum(math.MinInt64)
		maxDatum = types.NewIntDatum(math.MaxInt64)
	}
	for i := len(ranges) - 1; i >= 0; i-- {
		ran := ranges[i]
		if ran.HighVal[0].IsNull() {
			ranges = append(ranges[:i], ranges[i+1:]...)
			continue
		}
		if ran.LowVal[0].IsNull() || ran.LowVal[0].Kind() == types.KindMinNotNull {
			ran.LowVal[0].SetInt64(minDatum.GetInt64())
			ran.LowExclude = false
		}
		if ran.HighVal[0].Kind() == types.KindMaxValue {
			ran.HighVal[0].SetInt64(maxDatum.GetInt64())
		}

Review comment (suggestion):
The root cause that we need this function is that we reused `DetachCondAndBuildRangeForIndex` for the TiCI path, right?
If it's correct, I think we can add it to the comments to let others understand it better.

Code after:
		return nil
	}
	path.AccessConds = res.AccessConds
	path.TableFilters = res.RemainedConds
	path.EqCondCount = res.EqCondCount
	path.EqOrInCondCount = res.EqOrInCount
	path.IsDNFCond = res.IsDNFCond
	path.MinAccessCondsForDNFCond = res.MinAccessCondsForDNFCond
	path.ConstCols = make([]bool, len(path.IdxCols))
	if res.ColumnValues != nil {
		for i := range path.ConstCols {
			path.ConstCols[i] = res.ColumnValues[i] != nil
		}
	}
	indexCols := path.IdxCols
	if len(indexCols) > len(path.Index.Columns) { // remove clustered primary key if it has been added to path.IdxCols
		indexCols = indexCols[0:len(path.Index.Columns)]
	}
	count, err := cardinality.GetRowCountByIndexRanges(sctx, histColl, path.Index.ID, path.Ranges, indexCols)
	path.CountAfterAccess, path.MinCountAfterAccess, path.MaxCountAfterAccess = count.Est, count.MinEst, count.MaxEst
	return err
}

// fixTiCIIndexRangesForIntHandle fixes the TiCI index ranges for int handle.
// For normal index range, its min value is NULL or the MinNotNull, and max value is MaxValue.
// But for TiCI index range which directly uses table's int pk range, we should set the min value to the min int value and max value to max int value.
// And unlike the normal index, the code converting the int pk's range to the final kv.KeyRange doesn't handle the NULL.
// So after calling the function of extract normal index ranges, we need to fix the ranges for TiCI index with int handle.
func fixTiCIIndexRangesForIntHandle(ranges []*ranger.Range, isUnsigned bool) []*ranger.Range {
	var minDatum, maxDatum types.Datum
	// isUnsigned indicates whether the handle is unsigned.
	// Now we just cast the unsigned int to int and then store the int value inside the Datum.
	// We wrap the uint64/int64 with Datum here to keep us untouched with Datum's internal representation.
	if isUnsigned {
		minDatum = types.NewUintDatum(0)
		maxDatum = types.NewUintDatum(math.MaxUint64)
	} else {
		minDatum = types.NewIntDatum(math.MinInt64)
		maxDatum = types.NewIntDatum(math.MaxInt64)
	}
	for i := len(ranges) - 1; i >= 0; i-- {
		ran := ranges[i]
		// Remove the [NULL, NULL] which is invalid when generating int handle's kv.KeyRange.
		if ran.HighVal[0].IsNull() {
			ranges = slices.Delete(ranges, i, i+1)
			continue
		}
		// Convert the min and max value to the int min/max value.
		if ran.LowVal[0].IsNull() || ran.LowVal[0].Kind() == types.KindMinNotNull {
			ran.LowVal[0].SetInt64(minDatum.GetInt64())
			ran.LowExclude = false