Files
tidb/pkg/planner/core/rule_column_pruning.go

127 lines
4.3 KiB
Go

// Copyright 2016 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 core
import (
"context"
"slices"
"github.com/pingcap/errors"
"github.com/pingcap/tidb/pkg/expression"
"github.com/pingcap/tidb/pkg/meta/model"
"github.com/pingcap/tidb/pkg/parser/mysql"
"github.com/pingcap/tidb/pkg/planner/core/base"
"github.com/pingcap/tidb/pkg/planner/core/operator/logicalop"
"github.com/pingcap/tidb/pkg/planner/util"
"github.com/pingcap/tidb/pkg/planner/util/optimizetrace"
"github.com/pingcap/tidb/pkg/planner/util/optimizetrace/logicaltrace"
"github.com/pingcap/tidb/pkg/util/intest"
)
// ColumnPruner is used to prune unnecessary columns.
type ColumnPruner struct {
}
// Optimize implements base.LogicalOptRule.<0th> interface.
func (*ColumnPruner) Optimize(_ context.Context, lp base.LogicalPlan, opt *optimizetrace.LogicalOptimizeOp) (base.LogicalPlan, bool, error) {
planChanged := false
lp, err := lp.PruneColumns(slices.Clone(lp.Schema().Columns), opt)
if err != nil {
return nil, planChanged, err
}
intest.AssertNoError(noZeroColumnLayOut(lp), "After column pruning, some operator got zero row output. Please fix it.")
return lp, planChanged, nil
}
func noZeroColumnLayOut(p base.LogicalPlan) error {
for _, child := range p.Children() {
if err := noZeroColumnLayOut(child); err != nil {
return err
}
}
if p.Schema().Len() == 0 {
// The p don't hold its schema. So we don't need check itself.
if len(p.Children()) > 0 && p.Schema() == p.Children()[0].Schema() {
return nil
}
_, ok := p.(*logicalop.LogicalTableDual)
if !ok {
return errors.Errorf("Operator %s has zero row output", p.ExplainID().String())
}
}
return nil
}
func pruneByItems(p base.LogicalPlan, old []*util.ByItems, opt *optimizetrace.LogicalOptimizeOp) (byItems []*util.ByItems,
parentUsedCols []*expression.Column) {
prunedByItems := make([]*util.ByItems, 0)
byItems = make([]*util.ByItems, 0, len(old))
seen := make(map[string]struct{}, len(old))
for _, byItem := range old {
pruned := true
hash := string(byItem.Expr.HashCode())
_, hashMatch := seen[hash]
seen[hash] = struct{}{}
cols := expression.ExtractColumns(byItem.Expr)
if !hashMatch {
if len(cols) == 0 {
if !expression.IsRuntimeConstExpr(byItem.Expr) {
pruned = false
byItems = append(byItems, byItem)
}
} else if byItem.Expr.GetType(p.SCtx().GetExprCtx().GetEvalCtx()).GetType() != mysql.TypeNull {
pruned = false
parentUsedCols = append(parentUsedCols, cols...)
byItems = append(byItems, byItem)
}
}
if pruned {
prunedByItems = append(prunedByItems, byItem)
}
}
logicaltrace.AppendByItemsPruneTraceStep(p, prunedByItems, opt)
return
}
// Name implements base.LogicalOptRule.<1st> interface.
func (*ColumnPruner) Name() string {
return "column_prune"
}
func preferKeyColumnFromTable(dataSource *DataSource, originColumns []*expression.Column,
originSchemaColumns []*model.ColumnInfo) (*expression.Column, *model.ColumnInfo) {
var resultColumnInfo *model.ColumnInfo
var resultColumn *expression.Column
if dataSource.table.Type().IsClusterTable() && len(originColumns) > 0 {
// use the first column.
resultColumnInfo = originSchemaColumns[0]
resultColumn = originColumns[0]
} else {
if dataSource.HandleCols != nil {
resultColumn = dataSource.HandleCols.GetCol(0)
resultColumnInfo = resultColumn.ToInfo()
} else if dataSource.table.Meta().PKIsHandle {
// dataSource.HandleCols = nil doesn't mean datasource doesn't have a intPk handle.
// since datasource.HandleCols will be cleared in the first columnPruner.
resultColumn = dataSource.UnMutableHandleCols.GetCol(0)
resultColumnInfo = resultColumn.ToInfo()
} else {
resultColumn = dataSource.newExtraHandleSchemaCol()
resultColumnInfo = model.NewExtraHandleColInfo()
}
}
return resultColumn, resultColumnInfo
}