mirror of
https://codeberg.org/forgejo/forgejo.git
synced 2024-12-15 11:58:11 -05:00
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
|
||
|
}
|