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

Code before:
		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, it's 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 ineternal 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())
		}
	}
}

func getGeneralAttributesFromPaths(paths []*util.AccessPath, totalRowCount float64) (float64, bool) {
	minSelectivity := 1.0
	indexForce := false
	for _, path := range paths {
		// For table path and index merge path, AccessPath.CountAfterIndex is not set and meaningless,
		// but we still consider their AccessPath.CountAfterAccess.
		if totalRowCount > 0 {
			if path.IsTablePath() || path.PartialIndexPaths != nil {
				minSelectivity = min(minSelectivity, path.CountAfterAccess/totalRowCount)
			} else {
				minSelectivity = min(minSelectivity, path.CountAfterIndex/totalRowCount)
			}
		}

Review comment (suggestion):
The function modifies the `ranges` slice by removing elements during iteration (line 502), but the modified slice is not returned or assigned back to the caller. The `ranges` parameter is a slice, and when you do `ranges = append(ranges[:i], ranges[i+1:]...)`, this only modifies the local slice header, not the original slice that was passed in. The caller will still see the removed elements. You should either return the modified slice or pass `*[]*ranger.Range` as the parameter type.

Code after:
		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
		}
		if ran.HighVal[0].Kind() == types.KindMaxValue {
			ran.HighVal[0].SetInt64(maxDatum.GetInt64())
		}
	}
	return ranges
}

func getGeneralAttributesFromPaths(paths []*util.AccessPath, totalRowCount float64) (float64, bool) {
	minSelectivity := 1.0
	indexForce := false
	for _, path := range paths {
		// For table path and index merge path, AccessPath.CountAfterIndex is not set and meaningless,
		// but we still consider their AccessPath.CountAfterAccess.
		if totalRowCount > 0 {
			if path.IsTablePath() || path.PartialIndexPaths != nil {