* Dropped unused codekit config * Integrated dynamic and static bindata for public * Ignore public bindata * Add a general generate make task * Integrated flexible public assets into web command * Updated vendoring, added all missiong govendor deps * Made the linter happy with the bindata and dynamic code * Moved public bindata definition to modules directory * Ignoring the new bindata path now * Updated to the new public modules import path * Updated public bindata command and drop the new prefix
		
			
				
	
	
		
			927 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
			
		
		
	
	
			927 lines
		
	
	
		
			23 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
// Copyright 2015 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,
 | 
						|
// See the License for the specific language governing permissions and
 | 
						|
// limitations under the License.
 | 
						|
 | 
						|
package plan
 | 
						|
 | 
						|
import (
 | 
						|
	"github.com/juju/errors"
 | 
						|
	"github.com/ngaut/log"
 | 
						|
	"github.com/pingcap/tidb/ast"
 | 
						|
	"github.com/pingcap/tidb/infoschema"
 | 
						|
	"github.com/pingcap/tidb/model"
 | 
						|
	"github.com/pingcap/tidb/mysql"
 | 
						|
	"github.com/pingcap/tidb/parser/opcode"
 | 
						|
	"github.com/pingcap/tidb/terror"
 | 
						|
	"github.com/pingcap/tidb/util/charset"
 | 
						|
	"github.com/pingcap/tidb/util/types"
 | 
						|
)
 | 
						|
 | 
						|
// Error instances.
 | 
						|
var (
 | 
						|
	ErrUnsupportedType = terror.ClassOptimizerPlan.New(CodeUnsupportedType, "Unsupported type")
 | 
						|
)
 | 
						|
 | 
						|
// Error codes.
 | 
						|
const (
 | 
						|
	CodeUnsupportedType terror.ErrCode = 1
 | 
						|
)
 | 
						|
 | 
						|
// BuildPlan builds a plan from a node.
 | 
						|
// It returns ErrUnsupportedType if ast.Node type is not supported yet.
 | 
						|
func BuildPlan(node ast.Node, sb SubQueryBuilder) (Plan, error) {
 | 
						|
	builder := planBuilder{sb: sb}
 | 
						|
	p := builder.build(node)
 | 
						|
	return p, builder.err
 | 
						|
}
 | 
						|
 | 
						|
// planBuilder builds Plan from an ast.Node.
 | 
						|
// It just builds the ast node straightforwardly.
 | 
						|
type planBuilder struct {
 | 
						|
	err    error
 | 
						|
	hasAgg bool
 | 
						|
	sb     SubQueryBuilder
 | 
						|
	obj    interface{}
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) build(node ast.Node) Plan {
 | 
						|
	switch x := node.(type) {
 | 
						|
	case *ast.AdminStmt:
 | 
						|
		return b.buildAdmin(x)
 | 
						|
	case *ast.AlterTableStmt:
 | 
						|
		return b.buildDDL(x)
 | 
						|
	case *ast.CreateDatabaseStmt:
 | 
						|
		return b.buildDDL(x)
 | 
						|
	case *ast.CreateIndexStmt:
 | 
						|
		return b.buildDDL(x)
 | 
						|
	case *ast.CreateTableStmt:
 | 
						|
		return b.buildDDL(x)
 | 
						|
	case *ast.DeallocateStmt:
 | 
						|
		return &Deallocate{Name: x.Name}
 | 
						|
	case *ast.DeleteStmt:
 | 
						|
		return b.buildDelete(x)
 | 
						|
	case *ast.DropDatabaseStmt:
 | 
						|
		return b.buildDDL(x)
 | 
						|
	case *ast.DropIndexStmt:
 | 
						|
		return b.buildDDL(x)
 | 
						|
	case *ast.DropTableStmt:
 | 
						|
		return b.buildDDL(x)
 | 
						|
	case *ast.ExecuteStmt:
 | 
						|
		return &Execute{Name: x.Name, UsingVars: x.UsingVars}
 | 
						|
	case *ast.ExplainStmt:
 | 
						|
		return b.buildExplain(x)
 | 
						|
	case *ast.InsertStmt:
 | 
						|
		return b.buildInsert(x)
 | 
						|
	case *ast.PrepareStmt:
 | 
						|
		return b.buildPrepare(x)
 | 
						|
	case *ast.SelectStmt:
 | 
						|
		return b.buildSelect(x)
 | 
						|
	case *ast.UnionStmt:
 | 
						|
		return b.buildUnion(x)
 | 
						|
	case *ast.UpdateStmt:
 | 
						|
		return b.buildUpdate(x)
 | 
						|
	case *ast.UseStmt:
 | 
						|
		return b.buildSimple(x)
 | 
						|
	case *ast.SetCharsetStmt:
 | 
						|
		return b.buildSimple(x)
 | 
						|
	case *ast.SetStmt:
 | 
						|
		return b.buildSimple(x)
 | 
						|
	case *ast.ShowStmt:
 | 
						|
		return b.buildShow(x)
 | 
						|
	case *ast.DoStmt:
 | 
						|
		return b.buildSimple(x)
 | 
						|
	case *ast.BeginStmt:
 | 
						|
		return b.buildSimple(x)
 | 
						|
	case *ast.CommitStmt:
 | 
						|
		return b.buildSimple(x)
 | 
						|
	case *ast.RollbackStmt:
 | 
						|
		return b.buildSimple(x)
 | 
						|
	case *ast.CreateUserStmt:
 | 
						|
		return b.buildSimple(x)
 | 
						|
	case *ast.SetPwdStmt:
 | 
						|
		return b.buildSimple(x)
 | 
						|
	case *ast.GrantStmt:
 | 
						|
		return b.buildSimple(x)
 | 
						|
	case *ast.TruncateTableStmt:
 | 
						|
		return b.buildDDL(x)
 | 
						|
	}
 | 
						|
	b.err = ErrUnsupportedType.Gen("Unsupported type %T", node)
 | 
						|
	return nil
 | 
						|
}
 | 
						|
 | 
						|
// Detect aggregate function or groupby clause.
 | 
						|
func (b *planBuilder) detectSelectAgg(sel *ast.SelectStmt) bool {
 | 
						|
	if sel.GroupBy != nil {
 | 
						|
		return true
 | 
						|
	}
 | 
						|
	for _, f := range sel.GetResultFields() {
 | 
						|
		if ast.HasAggFlag(f.Expr) {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if sel.Having != nil {
 | 
						|
		if ast.HasAggFlag(sel.Having.Expr) {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if sel.OrderBy != nil {
 | 
						|
		for _, item := range sel.OrderBy.Items {
 | 
						|
			if ast.HasAggFlag(item.Expr) {
 | 
						|
				return true
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return false
 | 
						|
}
 | 
						|
 | 
						|
// extractSelectAgg extracts aggregate functions and converts ColumnNameExpr to aggregate function.
 | 
						|
func (b *planBuilder) extractSelectAgg(sel *ast.SelectStmt) []*ast.AggregateFuncExpr {
 | 
						|
	extractor := &ast.AggregateFuncExtractor{AggFuncs: make([]*ast.AggregateFuncExpr, 0)}
 | 
						|
	for _, f := range sel.GetResultFields() {
 | 
						|
		n, ok := f.Expr.Accept(extractor)
 | 
						|
		if !ok {
 | 
						|
			b.err = errors.New("Failed to extract agg expr!")
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		ve, ok := f.Expr.(*ast.ValueExpr)
 | 
						|
		if ok && len(f.Column.Name.O) > 0 {
 | 
						|
			agg := &ast.AggregateFuncExpr{
 | 
						|
				F:    ast.AggFuncFirstRow,
 | 
						|
				Args: []ast.ExprNode{ve},
 | 
						|
			}
 | 
						|
			extractor.AggFuncs = append(extractor.AggFuncs, agg)
 | 
						|
			n = agg
 | 
						|
		}
 | 
						|
		f.Expr = n.(ast.ExprNode)
 | 
						|
	}
 | 
						|
	// Extract agg funcs from having clause.
 | 
						|
	if sel.Having != nil {
 | 
						|
		n, ok := sel.Having.Expr.Accept(extractor)
 | 
						|
		if !ok {
 | 
						|
			b.err = errors.New("Failed to extract agg expr from having clause")
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		sel.Having.Expr = n.(ast.ExprNode)
 | 
						|
	}
 | 
						|
	// Extract agg funcs from orderby clause.
 | 
						|
	if sel.OrderBy != nil {
 | 
						|
		for _, item := range sel.OrderBy.Items {
 | 
						|
			n, ok := item.Expr.Accept(extractor)
 | 
						|
			if !ok {
 | 
						|
				b.err = errors.New("Failed to extract agg expr from orderby clause")
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
			item.Expr = n.(ast.ExprNode)
 | 
						|
			// If item is PositionExpr, we need to rebind it.
 | 
						|
			// For PositionExpr will refer to a ResultField in fieldlist.
 | 
						|
			// After extract AggExpr from fieldlist, it may be changed (See the code above).
 | 
						|
			if pe, ok := item.Expr.(*ast.PositionExpr); ok {
 | 
						|
				pe.Refer = sel.GetResultFields()[pe.N-1]
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return extractor.AggFuncs
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildSubquery(n ast.Node) {
 | 
						|
	sv := &subqueryVisitor{
 | 
						|
		builder: b,
 | 
						|
	}
 | 
						|
	_, ok := n.Accept(sv)
 | 
						|
	if !ok {
 | 
						|
		log.Errorf("Extract subquery error")
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildSelect(sel *ast.SelectStmt) Plan {
 | 
						|
	var aggFuncs []*ast.AggregateFuncExpr
 | 
						|
	hasAgg := b.detectSelectAgg(sel)
 | 
						|
	if hasAgg {
 | 
						|
		aggFuncs = b.extractSelectAgg(sel)
 | 
						|
	}
 | 
						|
	// Build subquery
 | 
						|
	// Convert subquery to expr with plan
 | 
						|
	b.buildSubquery(sel)
 | 
						|
	var p Plan
 | 
						|
	if sel.From != nil {
 | 
						|
		p = b.buildFrom(sel)
 | 
						|
		if b.err != nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		if sel.LockTp != ast.SelectLockNone {
 | 
						|
			p = b.buildSelectLock(p, sel.LockTp)
 | 
						|
			if b.err != nil {
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
		}
 | 
						|
		if hasAgg {
 | 
						|
			p = b.buildAggregate(p, aggFuncs, sel.GroupBy)
 | 
						|
		}
 | 
						|
		p = b.buildSelectFields(p, sel.GetResultFields())
 | 
						|
		if b.err != nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		if hasAgg {
 | 
						|
			p = b.buildAggregate(p, aggFuncs, nil)
 | 
						|
		}
 | 
						|
		p = b.buildSelectFields(p, sel.GetResultFields())
 | 
						|
		if b.err != nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if sel.Having != nil {
 | 
						|
		p = b.buildHaving(p, sel.Having)
 | 
						|
		if b.err != nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if sel.Distinct {
 | 
						|
		p = b.buildDistinct(p)
 | 
						|
		if b.err != nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if sel.OrderBy != nil && !matchOrder(p, sel.OrderBy.Items) {
 | 
						|
		p = b.buildSort(p, sel.OrderBy.Items)
 | 
						|
		if b.err != nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if sel.Limit != nil {
 | 
						|
		p = b.buildLimit(p, sel.Limit)
 | 
						|
		if b.err != nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildFrom(sel *ast.SelectStmt) Plan {
 | 
						|
	from := sel.From.TableRefs
 | 
						|
	if from.Right == nil {
 | 
						|
		return b.buildSingleTable(sel)
 | 
						|
	}
 | 
						|
	return b.buildJoin(sel)
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildSingleTable(sel *ast.SelectStmt) Plan {
 | 
						|
	from := sel.From.TableRefs
 | 
						|
	ts, ok := from.Left.(*ast.TableSource)
 | 
						|
	if !ok {
 | 
						|
		b.err = ErrUnsupportedType.Gen("Unsupported type %T", from.Left)
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	var bestPlan Plan
 | 
						|
	switch v := ts.Source.(type) {
 | 
						|
	case *ast.TableName:
 | 
						|
	case *ast.SelectStmt:
 | 
						|
		bestPlan = b.buildSelect(v)
 | 
						|
	}
 | 
						|
	if bestPlan != nil {
 | 
						|
		return bestPlan
 | 
						|
	}
 | 
						|
	tn, ok := ts.Source.(*ast.TableName)
 | 
						|
	if !ok {
 | 
						|
		b.err = ErrUnsupportedType.Gen("Unsupported type %T", ts.Source)
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	conditions := splitWhere(sel.Where)
 | 
						|
	path := &joinPath{table: tn, conditions: conditions}
 | 
						|
	candidates := b.buildAllAccessMethodsPlan(path)
 | 
						|
	var lowestCost float64
 | 
						|
	for _, v := range candidates {
 | 
						|
		cost := EstimateCost(b.buildPseudoSelectPlan(v, sel))
 | 
						|
		if bestPlan == nil {
 | 
						|
			bestPlan = v
 | 
						|
			lowestCost = cost
 | 
						|
		}
 | 
						|
		if cost < lowestCost {
 | 
						|
			bestPlan = v
 | 
						|
			lowestCost = cost
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return bestPlan
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildAllAccessMethodsPlan(path *joinPath) []Plan {
 | 
						|
	var candidates []Plan
 | 
						|
	p := b.buildTableScanPlan(path)
 | 
						|
	candidates = append(candidates, p)
 | 
						|
	for _, index := range path.table.TableInfo.Indices {
 | 
						|
		ip := b.buildIndexScanPlan(index, path)
 | 
						|
		candidates = append(candidates, ip)
 | 
						|
	}
 | 
						|
	return candidates
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildTableScanPlan(path *joinPath) Plan {
 | 
						|
	tn := path.table
 | 
						|
	p := &TableScan{
 | 
						|
		Table: tn.TableInfo,
 | 
						|
	}
 | 
						|
	// Equal condition contains a column from previous joined table.
 | 
						|
	p.RefAccess = len(path.eqConds) > 0
 | 
						|
	p.SetFields(tn.GetResultFields())
 | 
						|
	var pkName model.CIStr
 | 
						|
	if p.Table.PKIsHandle {
 | 
						|
		for _, colInfo := range p.Table.Columns {
 | 
						|
			if mysql.HasPriKeyFlag(colInfo.Flag) {
 | 
						|
				pkName = colInfo.Name
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for _, con := range path.conditions {
 | 
						|
		if pkName.L != "" {
 | 
						|
			checker := conditionChecker{tableName: tn.TableInfo.Name, pkName: pkName}
 | 
						|
			if checker.check(con) {
 | 
						|
				p.AccessConditions = append(p.AccessConditions, con)
 | 
						|
			} else {
 | 
						|
				p.FilterConditions = append(p.FilterConditions, con)
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			p.FilterConditions = append(p.FilterConditions, con)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildIndexScanPlan(index *model.IndexInfo, path *joinPath) Plan {
 | 
						|
	tn := path.table
 | 
						|
	ip := &IndexScan{Table: tn.TableInfo, Index: index}
 | 
						|
	ip.RefAccess = len(path.eqConds) > 0
 | 
						|
	ip.SetFields(tn.GetResultFields())
 | 
						|
 | 
						|
	condMap := map[ast.ExprNode]bool{}
 | 
						|
	for _, con := range path.conditions {
 | 
						|
		condMap[con] = true
 | 
						|
	}
 | 
						|
out:
 | 
						|
	// Build equal access conditions first.
 | 
						|
	// Starts from the first index column, if equal condition is found, add it to access conditions,
 | 
						|
	// proceed to the next index column. until we can't find any equal condition for the column.
 | 
						|
	for ip.AccessEqualCount < len(index.Columns) {
 | 
						|
		for con := range condMap {
 | 
						|
			binop, ok := con.(*ast.BinaryOperationExpr)
 | 
						|
			if !ok || binop.Op != opcode.EQ {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			if ast.IsPreEvaluable(binop.L) {
 | 
						|
				binop.L, binop.R = binop.R, binop.L
 | 
						|
			}
 | 
						|
			if !ast.IsPreEvaluable(binop.R) {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			cn, ok2 := binop.L.(*ast.ColumnNameExpr)
 | 
						|
			if !ok2 || cn.Refer.Column.Name.L != index.Columns[ip.AccessEqualCount].Name.L {
 | 
						|
				continue
 | 
						|
			}
 | 
						|
			ip.AccessConditions = append(ip.AccessConditions, con)
 | 
						|
			delete(condMap, con)
 | 
						|
			ip.AccessEqualCount++
 | 
						|
			continue out
 | 
						|
		}
 | 
						|
		break
 | 
						|
	}
 | 
						|
 | 
						|
	for con := range condMap {
 | 
						|
		if ip.AccessEqualCount < len(ip.Index.Columns) {
 | 
						|
			// Try to add non-equal access condition for index column at AccessEqualCount.
 | 
						|
			checker := conditionChecker{tableName: tn.TableInfo.Name, idx: index, columnOffset: ip.AccessEqualCount}
 | 
						|
			if checker.check(con) {
 | 
						|
				ip.AccessConditions = append(ip.AccessConditions, con)
 | 
						|
			} else {
 | 
						|
				ip.FilterConditions = append(ip.FilterConditions, con)
 | 
						|
			}
 | 
						|
		} else {
 | 
						|
			ip.FilterConditions = append(ip.FilterConditions, con)
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return ip
 | 
						|
}
 | 
						|
 | 
						|
// buildPseudoSelectPlan pre-builds more complete plans that may affect total cost.
 | 
						|
func (b *planBuilder) buildPseudoSelectPlan(p Plan, sel *ast.SelectStmt) Plan {
 | 
						|
	if sel.OrderBy == nil {
 | 
						|
		return p
 | 
						|
	}
 | 
						|
	if sel.GroupBy != nil {
 | 
						|
		return p
 | 
						|
	}
 | 
						|
	if !matchOrder(p, sel.OrderBy.Items) {
 | 
						|
		np := &Sort{ByItems: sel.OrderBy.Items}
 | 
						|
		np.SetSrc(p)
 | 
						|
		p = np
 | 
						|
	}
 | 
						|
	if sel.Limit != nil {
 | 
						|
		np := &Limit{Offset: sel.Limit.Offset, Count: sel.Limit.Count}
 | 
						|
		np.SetSrc(p)
 | 
						|
		np.SetLimit(0)
 | 
						|
		p = np
 | 
						|
	}
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildSelectLock(src Plan, lock ast.SelectLockType) *SelectLock {
 | 
						|
	selectLock := &SelectLock{
 | 
						|
		Lock: lock,
 | 
						|
	}
 | 
						|
	selectLock.SetSrc(src)
 | 
						|
	selectLock.SetFields(src.Fields())
 | 
						|
	return selectLock
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildSelectFields(src Plan, fields []*ast.ResultField) Plan {
 | 
						|
	selectFields := &SelectFields{}
 | 
						|
	selectFields.SetSrc(src)
 | 
						|
	selectFields.SetFields(fields)
 | 
						|
	return selectFields
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildAggregate(src Plan, aggFuncs []*ast.AggregateFuncExpr, groupby *ast.GroupByClause) Plan {
 | 
						|
	// Add aggregate plan.
 | 
						|
	aggPlan := &Aggregate{
 | 
						|
		AggFuncs: aggFuncs,
 | 
						|
	}
 | 
						|
	aggPlan.SetSrc(src)
 | 
						|
	if src != nil {
 | 
						|
		aggPlan.SetFields(src.Fields())
 | 
						|
	}
 | 
						|
	if groupby != nil {
 | 
						|
		aggPlan.GroupByItems = groupby.Items
 | 
						|
	}
 | 
						|
	return aggPlan
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildHaving(src Plan, having *ast.HavingClause) Plan {
 | 
						|
	p := &Having{
 | 
						|
		Conditions: splitWhere(having.Expr),
 | 
						|
	}
 | 
						|
	p.SetSrc(src)
 | 
						|
	p.SetFields(src.Fields())
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildSort(src Plan, byItems []*ast.ByItem) Plan {
 | 
						|
	sort := &Sort{
 | 
						|
		ByItems: byItems,
 | 
						|
	}
 | 
						|
	sort.SetSrc(src)
 | 
						|
	sort.SetFields(src.Fields())
 | 
						|
	return sort
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildLimit(src Plan, limit *ast.Limit) Plan {
 | 
						|
	li := &Limit{
 | 
						|
		Offset: limit.Offset,
 | 
						|
		Count:  limit.Count,
 | 
						|
	}
 | 
						|
	li.SetSrc(src)
 | 
						|
	li.SetFields(src.Fields())
 | 
						|
	return li
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildPrepare(x *ast.PrepareStmt) Plan {
 | 
						|
	p := &Prepare{
 | 
						|
		Name: x.Name,
 | 
						|
	}
 | 
						|
	if x.SQLVar != nil {
 | 
						|
		p.SQLText, _ = x.SQLVar.GetValue().(string)
 | 
						|
	} else {
 | 
						|
		p.SQLText = x.SQLText
 | 
						|
	}
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildAdmin(as *ast.AdminStmt) Plan {
 | 
						|
	var p Plan
 | 
						|
 | 
						|
	switch as.Tp {
 | 
						|
	case ast.AdminCheckTable:
 | 
						|
		p = &CheckTable{Tables: as.Tables}
 | 
						|
	case ast.AdminShowDDL:
 | 
						|
		p = &ShowDDL{}
 | 
						|
		p.SetFields(buildShowDDLFields())
 | 
						|
	default:
 | 
						|
		b.err = ErrUnsupportedType.Gen("Unsupported type %T", as)
 | 
						|
	}
 | 
						|
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
func buildShowDDLFields() []*ast.ResultField {
 | 
						|
	rfs := make([]*ast.ResultField, 0, 6)
 | 
						|
	rfs = append(rfs, buildResultField("", "SCHEMA_VER", mysql.TypeLonglong, 4))
 | 
						|
	rfs = append(rfs, buildResultField("", "OWNER", mysql.TypeVarchar, 64))
 | 
						|
	rfs = append(rfs, buildResultField("", "JOB", mysql.TypeVarchar, 128))
 | 
						|
	rfs = append(rfs, buildResultField("", "BG_SCHEMA_VER", mysql.TypeLonglong, 4))
 | 
						|
	rfs = append(rfs, buildResultField("", "BG_OWNER", mysql.TypeVarchar, 64))
 | 
						|
	rfs = append(rfs, buildResultField("", "BG_JOB", mysql.TypeVarchar, 128))
 | 
						|
 | 
						|
	return rfs
 | 
						|
}
 | 
						|
 | 
						|
func buildResultField(tableName, name string, tp byte, size int) *ast.ResultField {
 | 
						|
	cs := charset.CharsetBin
 | 
						|
	cl := charset.CharsetBin
 | 
						|
	flag := mysql.UnsignedFlag
 | 
						|
	if tp == mysql.TypeVarchar || tp == mysql.TypeBlob {
 | 
						|
		cs = mysql.DefaultCharset
 | 
						|
		cl = mysql.DefaultCollationName
 | 
						|
		flag = 0
 | 
						|
	}
 | 
						|
 | 
						|
	fieldType := types.FieldType{
 | 
						|
		Charset: cs,
 | 
						|
		Collate: cl,
 | 
						|
		Tp:      tp,
 | 
						|
		Flen:    size,
 | 
						|
		Flag:    uint(flag),
 | 
						|
	}
 | 
						|
	colInfo := &model.ColumnInfo{
 | 
						|
		Name:      model.NewCIStr(name),
 | 
						|
		FieldType: fieldType,
 | 
						|
	}
 | 
						|
	expr := &ast.ValueExpr{}
 | 
						|
	expr.SetType(&fieldType)
 | 
						|
 | 
						|
	return &ast.ResultField{
 | 
						|
		Column:       colInfo,
 | 
						|
		ColumnAsName: colInfo.Name,
 | 
						|
		TableAsName:  model.NewCIStr(tableName),
 | 
						|
		DBName:       model.NewCIStr(infoschema.Name),
 | 
						|
		Expr:         expr,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
// matchOrder checks if the plan has the same ordering as items.
 | 
						|
func matchOrder(p Plan, items []*ast.ByItem) bool {
 | 
						|
	switch x := p.(type) {
 | 
						|
	case *Aggregate:
 | 
						|
		return false
 | 
						|
	case *IndexScan:
 | 
						|
		if len(items) > len(x.Index.Columns) {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		for i, item := range items {
 | 
						|
			if item.Desc {
 | 
						|
				return false
 | 
						|
			}
 | 
						|
			var rf *ast.ResultField
 | 
						|
			switch y := item.Expr.(type) {
 | 
						|
			case *ast.ColumnNameExpr:
 | 
						|
				rf = y.Refer
 | 
						|
			case *ast.PositionExpr:
 | 
						|
				rf = y.Refer
 | 
						|
			default:
 | 
						|
				return false
 | 
						|
			}
 | 
						|
			if rf.Table.Name.L != x.Table.Name.L || rf.Column.Name.L != x.Index.Columns[i].Name.L {
 | 
						|
				return false
 | 
						|
			}
 | 
						|
		}
 | 
						|
		return true
 | 
						|
	case *TableScan:
 | 
						|
		if len(items) != 1 || !x.Table.PKIsHandle {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		if items[0].Desc {
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		var refer *ast.ResultField
 | 
						|
		switch x := items[0].Expr.(type) {
 | 
						|
		case *ast.ColumnNameExpr:
 | 
						|
			refer = x.Refer
 | 
						|
		case *ast.PositionExpr:
 | 
						|
			refer = x.Refer
 | 
						|
		default:
 | 
						|
			return false
 | 
						|
		}
 | 
						|
		if mysql.HasPriKeyFlag(refer.Column.Flag) {
 | 
						|
			return true
 | 
						|
		}
 | 
						|
		return false
 | 
						|
	case *JoinOuter:
 | 
						|
		return false
 | 
						|
	case *JoinInner:
 | 
						|
		return false
 | 
						|
	case *Sort:
 | 
						|
		// Sort plan should not be checked here as there should only be one sort plan in a plan tree.
 | 
						|
		return false
 | 
						|
	case WithSrcPlan:
 | 
						|
		return matchOrder(x.Src(), items)
 | 
						|
	}
 | 
						|
	return true
 | 
						|
}
 | 
						|
 | 
						|
// splitWhere split a where expression to a list of AND conditions.
 | 
						|
func splitWhere(where ast.ExprNode) []ast.ExprNode {
 | 
						|
	var conditions []ast.ExprNode
 | 
						|
	switch x := where.(type) {
 | 
						|
	case nil:
 | 
						|
	case *ast.BinaryOperationExpr:
 | 
						|
		if x.Op == opcode.AndAnd {
 | 
						|
			conditions = append(conditions, splitWhere(x.L)...)
 | 
						|
			conditions = append(conditions, splitWhere(x.R)...)
 | 
						|
		} else {
 | 
						|
			conditions = append(conditions, x)
 | 
						|
		}
 | 
						|
	case *ast.ParenthesesExpr:
 | 
						|
		conditions = append(conditions, splitWhere(x.Expr)...)
 | 
						|
	default:
 | 
						|
		conditions = append(conditions, where)
 | 
						|
	}
 | 
						|
	return conditions
 | 
						|
}
 | 
						|
 | 
						|
// SubQueryBuilder is the interface for building SubQuery executor.
 | 
						|
type SubQueryBuilder interface {
 | 
						|
	Build(p Plan) ast.SubqueryExec
 | 
						|
}
 | 
						|
 | 
						|
// subqueryVisitor visits AST and handles SubqueryExpr.
 | 
						|
type subqueryVisitor struct {
 | 
						|
	builder *planBuilder
 | 
						|
}
 | 
						|
 | 
						|
func (se *subqueryVisitor) Enter(in ast.Node) (out ast.Node, skipChildren bool) {
 | 
						|
	switch x := in.(type) {
 | 
						|
	case *ast.SubqueryExpr:
 | 
						|
		p := se.builder.build(x.Query)
 | 
						|
		// The expr pointor is copyed into ResultField when running name resolver.
 | 
						|
		// So we can not just replace the expr node in AST. We need to put SubQuery into the expr.
 | 
						|
		// See: optimizer.nameResolver.createResultFields()
 | 
						|
		x.SubqueryExec = se.builder.sb.Build(p)
 | 
						|
		return in, true
 | 
						|
	case *ast.Join:
 | 
						|
		// SubSelect in from clause will be handled in buildJoin().
 | 
						|
		return in, true
 | 
						|
	}
 | 
						|
	return in, false
 | 
						|
}
 | 
						|
 | 
						|
func (se *subqueryVisitor) Leave(in ast.Node) (out ast.Node, ok bool) {
 | 
						|
	return in, true
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildUnion(union *ast.UnionStmt) Plan {
 | 
						|
	sels := make([]Plan, len(union.SelectList.Selects))
 | 
						|
	for i, sel := range union.SelectList.Selects {
 | 
						|
		sels[i] = b.buildSelect(sel)
 | 
						|
	}
 | 
						|
	var p Plan
 | 
						|
	p = &Union{
 | 
						|
		Selects: sels,
 | 
						|
	}
 | 
						|
	unionFields := union.GetResultFields()
 | 
						|
	for _, sel := range sels {
 | 
						|
		for i, f := range sel.Fields() {
 | 
						|
			if i == len(unionFields) {
 | 
						|
				b.err = errors.New("The used SELECT statements have a different number of columns")
 | 
						|
				return nil
 | 
						|
			}
 | 
						|
			uField := unionFields[i]
 | 
						|
			/*
 | 
						|
			 * The lengths of the columns in the UNION result take into account the values retrieved by all of the SELECT statements
 | 
						|
			 * SELECT REPEAT('a',1) UNION SELECT REPEAT('b',10);
 | 
						|
			 * +---------------+
 | 
						|
			 * | REPEAT('a',1) |
 | 
						|
			 * +---------------+
 | 
						|
			 * | a             |
 | 
						|
			 * | bbbbbbbbbb    |
 | 
						|
			 * +---------------+
 | 
						|
			 */
 | 
						|
			if f.Column.Flen > uField.Column.Flen {
 | 
						|
				uField.Column.Flen = f.Column.Flen
 | 
						|
			}
 | 
						|
			// For select nul union select "abc", we should not convert "abc" to nil.
 | 
						|
			// And the result field type should be VARCHAR.
 | 
						|
			if uField.Column.Tp == 0 || uField.Column.Tp == mysql.TypeNull {
 | 
						|
				uField.Column.Tp = f.Column.Tp
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	for _, v := range unionFields {
 | 
						|
		v.Expr.SetType(&v.Column.FieldType)
 | 
						|
	}
 | 
						|
 | 
						|
	p.SetFields(unionFields)
 | 
						|
	if union.Distinct {
 | 
						|
		p = b.buildDistinct(p)
 | 
						|
	}
 | 
						|
	if union.OrderBy != nil {
 | 
						|
		p = b.buildSort(p, union.OrderBy.Items)
 | 
						|
	}
 | 
						|
	if union.Limit != nil {
 | 
						|
		p = b.buildLimit(p, union.Limit)
 | 
						|
	}
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildDistinct(src Plan) Plan {
 | 
						|
	d := &Distinct{}
 | 
						|
	d.src = src
 | 
						|
	d.SetFields(src.Fields())
 | 
						|
	return d
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildUpdate(update *ast.UpdateStmt) Plan {
 | 
						|
	sel := &ast.SelectStmt{From: update.TableRefs, Where: update.Where, OrderBy: update.Order, Limit: update.Limit}
 | 
						|
	p := b.buildFrom(sel)
 | 
						|
	if sel.OrderBy != nil && !matchOrder(p, sel.OrderBy.Items) {
 | 
						|
		p = b.buildSort(p, sel.OrderBy.Items)
 | 
						|
		if b.err != nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if sel.Limit != nil {
 | 
						|
		p = b.buildLimit(p, sel.Limit)
 | 
						|
		if b.err != nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	orderedList := b.buildUpdateLists(update.List, p.Fields())
 | 
						|
	if b.err != nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	return &Update{OrderedList: orderedList, SelectPlan: p}
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildUpdateLists(list []*ast.Assignment, fields []*ast.ResultField) []*ast.Assignment {
 | 
						|
	newList := make([]*ast.Assignment, len(fields))
 | 
						|
	for _, assign := range list {
 | 
						|
		offset, err := columnOffsetInFields(assign.Column, fields)
 | 
						|
		if err != nil {
 | 
						|
			b.err = errors.Trace(err)
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
		newList[offset] = assign
 | 
						|
	}
 | 
						|
	return newList
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildDelete(del *ast.DeleteStmt) Plan {
 | 
						|
	sel := &ast.SelectStmt{From: del.TableRefs, Where: del.Where, OrderBy: del.Order, Limit: del.Limit}
 | 
						|
	p := b.buildFrom(sel)
 | 
						|
	if sel.OrderBy != nil && !matchOrder(p, sel.OrderBy.Items) {
 | 
						|
		p = b.buildSort(p, sel.OrderBy.Items)
 | 
						|
		if b.err != nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if sel.Limit != nil {
 | 
						|
		p = b.buildLimit(p, sel.Limit)
 | 
						|
		if b.err != nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	var tables []*ast.TableName
 | 
						|
	if del.Tables != nil {
 | 
						|
		tables = del.Tables.Tables
 | 
						|
	}
 | 
						|
	return &Delete{
 | 
						|
		Tables:       tables,
 | 
						|
		IsMultiTable: del.IsMultiTable,
 | 
						|
		SelectPlan:   p,
 | 
						|
	}
 | 
						|
}
 | 
						|
 | 
						|
func columnOffsetInFields(cn *ast.ColumnName, fields []*ast.ResultField) (int, error) {
 | 
						|
	offset := -1
 | 
						|
	tableNameL := cn.Table.L
 | 
						|
	columnNameL := cn.Name.L
 | 
						|
	if tableNameL != "" {
 | 
						|
		for i, f := range fields {
 | 
						|
			// Check table name.
 | 
						|
			if f.TableAsName.L != "" {
 | 
						|
				if tableNameL != f.TableAsName.L {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				if tableNameL != f.Table.Name.L {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
			}
 | 
						|
			// Check column name.
 | 
						|
			if f.ColumnAsName.L != "" {
 | 
						|
				if columnNameL != f.ColumnAsName.L {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
			} else {
 | 
						|
				if columnNameL != f.Column.Name.L {
 | 
						|
					continue
 | 
						|
				}
 | 
						|
			}
 | 
						|
 | 
						|
			offset = i
 | 
						|
		}
 | 
						|
	} else {
 | 
						|
		for i, f := range fields {
 | 
						|
			matchAsName := f.ColumnAsName.L != "" && f.ColumnAsName.L == columnNameL
 | 
						|
			matchColumnName := f.ColumnAsName.L == "" && f.Column.Name.L == columnNameL
 | 
						|
			if matchAsName || matchColumnName {
 | 
						|
				if offset != -1 {
 | 
						|
					return -1, errors.Errorf("column %s is ambiguous.", cn.Name.O)
 | 
						|
				}
 | 
						|
				offset = i
 | 
						|
			}
 | 
						|
		}
 | 
						|
	}
 | 
						|
	if offset == -1 {
 | 
						|
		return -1, errors.Errorf("column %s not found", cn.Name.O)
 | 
						|
	}
 | 
						|
	return offset, nil
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildShow(show *ast.ShowStmt) Plan {
 | 
						|
	var p Plan
 | 
						|
	p = &Show{
 | 
						|
		Tp:     show.Tp,
 | 
						|
		DBName: show.DBName,
 | 
						|
		Table:  show.Table,
 | 
						|
		Column: show.Column,
 | 
						|
		Flag:   show.Flag,
 | 
						|
		Full:   show.Full,
 | 
						|
		User:   show.User,
 | 
						|
	}
 | 
						|
	p.SetFields(show.GetResultFields())
 | 
						|
	var conditions []ast.ExprNode
 | 
						|
	if show.Pattern != nil {
 | 
						|
		conditions = append(conditions, show.Pattern)
 | 
						|
	}
 | 
						|
	if show.Where != nil {
 | 
						|
		conditions = append(conditions, show.Where)
 | 
						|
	}
 | 
						|
	if len(conditions) != 0 {
 | 
						|
		filter := &Filter{Conditions: conditions}
 | 
						|
		filter.SetSrc(p)
 | 
						|
		p = filter
 | 
						|
	}
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildSimple(node ast.StmtNode) Plan {
 | 
						|
	return &Simple{Statement: node}
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildInsert(insert *ast.InsertStmt) Plan {
 | 
						|
	insertPlan := &Insert{
 | 
						|
		Table:       insert.Table,
 | 
						|
		Columns:     insert.Columns,
 | 
						|
		Lists:       insert.Lists,
 | 
						|
		Setlist:     insert.Setlist,
 | 
						|
		OnDuplicate: insert.OnDuplicate,
 | 
						|
		IsReplace:   insert.IsReplace,
 | 
						|
		Priority:    insert.Priority,
 | 
						|
	}
 | 
						|
	if insert.Select != nil {
 | 
						|
		insertPlan.SelectPlan = b.build(insert.Select)
 | 
						|
		if b.err != nil {
 | 
						|
			return nil
 | 
						|
		}
 | 
						|
	}
 | 
						|
	return insertPlan
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildDDL(node ast.DDLNode) Plan {
 | 
						|
	return &DDL{Statement: node}
 | 
						|
}
 | 
						|
 | 
						|
func (b *planBuilder) buildExplain(explain *ast.ExplainStmt) Plan {
 | 
						|
	if show, ok := explain.Stmt.(*ast.ShowStmt); ok {
 | 
						|
		return b.buildShow(show)
 | 
						|
	}
 | 
						|
	targetPlan := b.build(explain.Stmt)
 | 
						|
	if b.err != nil {
 | 
						|
		return nil
 | 
						|
	}
 | 
						|
	p := &Explain{StmtPlan: targetPlan}
 | 
						|
	p.SetFields(buildExplainFields())
 | 
						|
	return p
 | 
						|
}
 | 
						|
 | 
						|
// See: https://dev.mysql.com/doc/refman/5.7/en/explain-output.html
 | 
						|
func buildExplainFields() []*ast.ResultField {
 | 
						|
	rfs := make([]*ast.ResultField, 0, 10)
 | 
						|
	rfs = append(rfs, buildResultField("", "id", mysql.TypeLonglong, 4))
 | 
						|
	rfs = append(rfs, buildResultField("", "select_type", mysql.TypeVarchar, 128))
 | 
						|
	rfs = append(rfs, buildResultField("", "table", mysql.TypeVarchar, 128))
 | 
						|
	rfs = append(rfs, buildResultField("", "type", mysql.TypeVarchar, 128))
 | 
						|
	rfs = append(rfs, buildResultField("", "possible_keys", mysql.TypeVarchar, 128))
 | 
						|
	rfs = append(rfs, buildResultField("", "key", mysql.TypeVarchar, 128))
 | 
						|
	rfs = append(rfs, buildResultField("", "key_len", mysql.TypeVarchar, 128))
 | 
						|
	rfs = append(rfs, buildResultField("", "ref", mysql.TypeVarchar, 128))
 | 
						|
	rfs = append(rfs, buildResultField("", "rows", mysql.TypeVarchar, 128))
 | 
						|
	rfs = append(rfs, buildResultField("", "Extra", mysql.TypeVarchar, 128))
 | 
						|
	return rfs
 | 
						|
}
 |