|
- // 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
- }
|