127 lines
4.3 KiB
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
|
|
}
|