274 lines
8.6 KiB
Go
274 lines
8.6 KiB
Go
// Copyright 2019 PingCAP, Inc.
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
package util
|
|
|
|
import (
|
|
"github.com/pingcap/tidb/expression"
|
|
"github.com/pingcap/tidb/kv"
|
|
"github.com/pingcap/tidb/parser/ast"
|
|
"github.com/pingcap/tidb/parser/model"
|
|
"github.com/pingcap/tidb/sessionctx"
|
|
"github.com/pingcap/tidb/types"
|
|
"github.com/pingcap/tidb/util/collate"
|
|
"github.com/pingcap/tidb/util/ranger"
|
|
)
|
|
|
|
// AccessPath indicates the way we access a table: by using single index, or by using multiple indexes,
|
|
// or just by using table scan.
|
|
type AccessPath struct {
|
|
Index *model.IndexInfo
|
|
FullIdxCols []*expression.Column
|
|
FullIdxColLens []int
|
|
IdxCols []*expression.Column
|
|
IdxColLens []int
|
|
// ConstCols indicates whether the column is constant under the given conditions for all index columns.
|
|
ConstCols []bool
|
|
Ranges []*ranger.Range
|
|
// CountAfterAccess is the row count after we apply range seek and before we use other filter to filter data.
|
|
// For index merge path, CountAfterAccess is the row count after partial paths and before we apply table filters.
|
|
CountAfterAccess float64
|
|
// CountAfterIndex is the row count after we apply filters on index and before we apply the table filters.
|
|
CountAfterIndex float64
|
|
AccessConds []expression.Expression
|
|
EqCondCount int
|
|
EqOrInCondCount int
|
|
IndexFilters []expression.Expression
|
|
TableFilters []expression.Expression
|
|
// PartialIndexPaths store all index access paths.
|
|
// If there are extra filters, store them in TableFilters.
|
|
PartialIndexPaths []*AccessPath
|
|
|
|
StoreType kv.StoreType
|
|
|
|
IsDNFCond bool
|
|
|
|
// IsTiFlashGlobalRead indicates whether this path is a remote read path for tiflash
|
|
IsTiFlashGlobalRead bool
|
|
|
|
// IsIntHandlePath indicates whether this path is table path.
|
|
IsIntHandlePath bool
|
|
IsCommonHandlePath bool
|
|
// Forced means this path is generated by `use/force index()`.
|
|
Forced bool
|
|
// IsSingleScan indicates whether the path is a single index/table scan or table access after index scan.
|
|
IsSingleScan bool
|
|
}
|
|
|
|
// IsTablePath returns true if it's IntHandlePath or CommonHandlePath.
|
|
func (path *AccessPath) IsTablePath() bool {
|
|
return path.IsIntHandlePath || path.IsCommonHandlePath
|
|
}
|
|
|
|
// SplitCorColAccessCondFromFilters move the necessary filter in the form of index_col = corrlated_col to access conditions.
|
|
// The function consider the `idx_col_1 = const and index_col_2 = cor_col and index_col_3 = const` case.
|
|
// It enables more index columns to be considered. The range will be rebuilt in 'ResolveCorrelatedColumns'.
|
|
func (path *AccessPath) SplitCorColAccessCondFromFilters(ctx sessionctx.Context, eqOrInCount int) (access, remained []expression.Expression) {
|
|
// The plan cache do not support subquery now. So we skip this function when
|
|
// 'MaybeOverOptimized4PlanCache' function return true .
|
|
if expression.MaybeOverOptimized4PlanCache(ctx, path.TableFilters) {
|
|
return nil, path.TableFilters
|
|
}
|
|
access = make([]expression.Expression, len(path.IdxCols)-eqOrInCount)
|
|
used := make([]bool, len(path.TableFilters))
|
|
for i := eqOrInCount; i < len(path.IdxCols); i++ {
|
|
matched := false
|
|
for j, filter := range path.TableFilters {
|
|
if used[j] || !isColEqCorColOrConstant(ctx, filter, path.IdxCols[i]) {
|
|
continue
|
|
}
|
|
matched = true
|
|
access[i-eqOrInCount] = filter
|
|
if path.IdxColLens[i] == types.UnspecifiedLength {
|
|
used[j] = true
|
|
}
|
|
break
|
|
}
|
|
if !matched {
|
|
access = access[:i-eqOrInCount]
|
|
break
|
|
}
|
|
}
|
|
for i, ok := range used {
|
|
if !ok {
|
|
remained = append(remained, path.TableFilters[i]) // nozero
|
|
}
|
|
}
|
|
return access, remained
|
|
}
|
|
|
|
// isColEqCorColOrConstant checks if the expression is a eq function that one side is constant or correlated column
|
|
// and another is column.
|
|
func isColEqCorColOrConstant(ctx sessionctx.Context, filter expression.Expression, col *expression.Column) bool {
|
|
f, ok := filter.(*expression.ScalarFunction)
|
|
if !ok || f.FuncName.L != ast.EQ {
|
|
return false
|
|
}
|
|
_, collation := f.CharsetAndCollation()
|
|
if c, ok := f.GetArgs()[0].(*expression.Column); ok {
|
|
if c.RetType.EvalType() == types.ETString && !collate.CompatibleCollate(collation, c.RetType.Collate) {
|
|
return false
|
|
}
|
|
if _, ok := f.GetArgs()[1].(*expression.Constant); ok {
|
|
if col.Equal(nil, c) {
|
|
return true
|
|
}
|
|
}
|
|
if _, ok := f.GetArgs()[1].(*expression.CorrelatedColumn); ok {
|
|
if col.Equal(nil, c) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
if c, ok := f.GetArgs()[1].(*expression.Column); ok {
|
|
if c.RetType.EvalType() == types.ETString && !collate.CompatibleCollate(collation, c.RetType.Collate) {
|
|
return false
|
|
}
|
|
if _, ok := f.GetArgs()[0].(*expression.Constant); ok {
|
|
if col.Equal(nil, c) {
|
|
return true
|
|
}
|
|
}
|
|
if _, ok := f.GetArgs()[0].(*expression.CorrelatedColumn); ok {
|
|
if col.Equal(nil, c) {
|
|
return true
|
|
}
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// OnlyPointRange checks whether each range is a point(no interval range exists).
|
|
func (path *AccessPath) OnlyPointRange(sctx sessionctx.Context) bool {
|
|
if path.IsIntHandlePath {
|
|
for _, ran := range path.Ranges {
|
|
if !ran.IsPointNullable(sctx) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
for _, ran := range path.Ranges {
|
|
// Not point or the not full matched.
|
|
if !ran.IsPointNonNullable(sctx) || len(ran.HighVal) != len(path.Index.Columns) {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Col2Len maps expression.Column.UniqueID to column length
|
|
type Col2Len map[int64]int
|
|
|
|
// ExtractCol2Len collects index/table columns with lengths from expressions. If idxCols and idxColLens are not nil, it collects index columns with lengths(maybe prefix lengths).
|
|
// Otherwise it collects table columns with full lengths.
|
|
func ExtractCol2Len(exprs []expression.Expression, idxCols []*expression.Column, idxColLens []int) Col2Len {
|
|
col2len := make(Col2Len, len(idxCols))
|
|
for _, expr := range exprs {
|
|
extractCol2LenFromExpr(expr, idxCols, idxColLens, col2len)
|
|
}
|
|
return col2len
|
|
}
|
|
|
|
func extractCol2LenFromExpr(expr expression.Expression, idxCols []*expression.Column, idxColLens []int, col2Len Col2Len) {
|
|
switch v := expr.(type) {
|
|
case *expression.Column:
|
|
if idxCols == nil {
|
|
col2Len[v.UniqueID] = types.UnspecifiedLength
|
|
} else {
|
|
for i, col := range idxCols {
|
|
if col != nil && v.EqualByExprAndID(nil, col) {
|
|
col2Len[v.UniqueID] = idxColLens[i]
|
|
break
|
|
}
|
|
}
|
|
}
|
|
case *expression.ScalarFunction:
|
|
for _, arg := range v.GetArgs() {
|
|
extractCol2LenFromExpr(arg, idxCols, idxColLens, col2Len)
|
|
}
|
|
}
|
|
}
|
|
|
|
// compareLength will compare the two column lengths. The return value:
|
|
// (1) -1 means that l is shorter than r;
|
|
// (2) 0 means that l equals to r;
|
|
// (3) 1 means that l is longer than r;
|
|
func compareLength(l, r int) int {
|
|
if l == r {
|
|
return 0
|
|
}
|
|
if l == types.UnspecifiedLength {
|
|
return 1
|
|
}
|
|
if r == types.UnspecifiedLength {
|
|
return -1
|
|
}
|
|
if l > r {
|
|
return 1
|
|
}
|
|
return -1
|
|
}
|
|
|
|
// dominate return true if each column of c2 exists in c1 and c2's column length is no longer than c1's column length.
|
|
func (c1 Col2Len) dominate(c2 Col2Len) bool {
|
|
if len(c2) > len(c1) {
|
|
return false
|
|
}
|
|
for colID, len2 := range c2 {
|
|
len1, ok := c1[colID]
|
|
if !ok || compareLength(len2, len1) == 1 {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// CompareCol2Len will compare the two Col2Len maps. The last return value is used to indicate whether they are comparable.
|
|
// When the second return value is true, the first return value:
|
|
// (1) -1 means that c1 is worse than c2;
|
|
// (2) 0 means that c1 equals to c2;
|
|
// (3) 1 means that c1 is better than c2;
|
|
func CompareCol2Len(c1, c2 Col2Len) (int, bool) {
|
|
l1, l2 := len(c1), len(c2)
|
|
if l1 > l2 {
|
|
if c1.dominate(c2) {
|
|
return 1, true
|
|
}
|
|
return 0, false
|
|
}
|
|
if l1 < l2 {
|
|
if c2.dominate(c1) {
|
|
return -1, true
|
|
}
|
|
return 0, false
|
|
}
|
|
// If c1 and c2 have the same columns but have different lengths on some column, we regard c1 and c2 incomparable.
|
|
for colID, colLen2 := range c2 {
|
|
colLen1, ok := c1[colID]
|
|
if !ok || colLen1 != colLen2 {
|
|
return 0, false
|
|
}
|
|
}
|
|
return 0, true
|
|
}
|
|
|
|
// GetCol2LenFromAccessConds returns columns with lengths from path.AccessConds.
|
|
func (path *AccessPath) GetCol2LenFromAccessConds() Col2Len {
|
|
if path.IsTablePath() {
|
|
return ExtractCol2Len(path.AccessConds, nil, nil)
|
|
}
|
|
return ExtractCol2Len(path.AccessConds, path.IdxCols, path.IdxColLens)
|
|
}
|