forked from forgejo/forgejo
Integrate public as bindata optionally (#293)
* 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
This commit is contained in:
parent
4680c349dd
commit
b6a95a8cb3
691 changed files with 305318 additions and 1272 deletions
109
vendor/github.com/pingcap/tidb/optimizer/plan/cost.go
generated
vendored
Normal file
109
vendor/github.com/pingcap/tidb/optimizer/plan/cost.go
generated
vendored
Normal file
|
@ -0,0 +1,109 @@
|
|||
// 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 (
|
||||
"math"
|
||||
)
|
||||
|
||||
// Pre-defined cost factors.
|
||||
const (
|
||||
FullRangeCount = 10000
|
||||
HalfRangeCount = 4000
|
||||
MiddleRangeCount = 100
|
||||
RowCost = 1.0
|
||||
IndexCost = 2.0
|
||||
SortCost = 2.0
|
||||
FilterRate = 0.5
|
||||
)
|
||||
|
||||
// CostEstimator estimates the cost of a plan.
|
||||
type costEstimator struct {
|
||||
}
|
||||
|
||||
// Enter implements Visitor Enter interface.
|
||||
func (c *costEstimator) Enter(p Plan) (Plan, bool) {
|
||||
return p, false
|
||||
}
|
||||
|
||||
// Leave implements Visitor Leave interface.
|
||||
func (c *costEstimator) Leave(p Plan) (Plan, bool) {
|
||||
switch v := p.(type) {
|
||||
case *IndexScan:
|
||||
c.indexScan(v)
|
||||
case *Limit:
|
||||
v.rowCount = v.Src().RowCount()
|
||||
v.startupCost = v.Src().StartupCost()
|
||||
v.totalCost = v.Src().TotalCost()
|
||||
case *SelectFields:
|
||||
if v.Src() != nil {
|
||||
v.startupCost = v.Src().StartupCost()
|
||||
v.rowCount = v.Src().RowCount()
|
||||
v.totalCost = v.Src().TotalCost()
|
||||
}
|
||||
case *SelectLock:
|
||||
v.startupCost = v.Src().StartupCost()
|
||||
v.rowCount = v.Src().RowCount()
|
||||
v.totalCost = v.Src().TotalCost()
|
||||
case *Sort:
|
||||
// Sort plan must retrieve all the rows before returns the first row.
|
||||
v.startupCost = v.Src().TotalCost() + v.Src().RowCount()*SortCost
|
||||
if v.limit == 0 {
|
||||
v.rowCount = v.Src().RowCount()
|
||||
} else {
|
||||
v.rowCount = math.Min(v.Src().RowCount(), v.limit)
|
||||
}
|
||||
v.totalCost = v.startupCost + v.rowCount*RowCost
|
||||
case *TableScan:
|
||||
c.tableScan(v)
|
||||
}
|
||||
return p, true
|
||||
}
|
||||
|
||||
func (c *costEstimator) tableScan(v *TableScan) {
|
||||
var rowCount float64 = FullRangeCount
|
||||
for _, con := range v.AccessConditions {
|
||||
rowCount *= guesstimateFilterRate(con)
|
||||
}
|
||||
v.startupCost = 0
|
||||
if v.limit == 0 {
|
||||
// limit is zero means no limit.
|
||||
v.rowCount = rowCount
|
||||
} else {
|
||||
v.rowCount = math.Min(rowCount, v.limit)
|
||||
}
|
||||
v.totalCost = v.rowCount * RowCost
|
||||
}
|
||||
|
||||
func (c *costEstimator) indexScan(v *IndexScan) {
|
||||
var rowCount float64 = FullRangeCount
|
||||
for _, con := range v.AccessConditions {
|
||||
rowCount *= guesstimateFilterRate(con)
|
||||
}
|
||||
v.startupCost = 0
|
||||
if v.limit == 0 {
|
||||
// limit is zero means no limit.
|
||||
v.rowCount = rowCount
|
||||
} else {
|
||||
v.rowCount = math.Min(rowCount, v.limit)
|
||||
}
|
||||
v.totalCost = v.rowCount * RowCost
|
||||
}
|
||||
|
||||
// EstimateCost estimates the cost of the plan.
|
||||
func EstimateCost(p Plan) float64 {
|
||||
var estimator costEstimator
|
||||
p.Accept(&estimator)
|
||||
return p.TotalCost()
|
||||
}
|
115
vendor/github.com/pingcap/tidb/optimizer/plan/filterrate.go
generated
vendored
Normal file
115
vendor/github.com/pingcap/tidb/optimizer/plan/filterrate.go
generated
vendored
Normal file
|
@ -0,0 +1,115 @@
|
|||
// 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/pingcap/tidb/ast"
|
||||
"github.com/pingcap/tidb/parser/opcode"
|
||||
)
|
||||
|
||||
const (
|
||||
rateFull float64 = 1
|
||||
rateEqual float64 = 0.01
|
||||
rateNotEqual float64 = 0.99
|
||||
rateBetween float64 = 0.1
|
||||
rateGreaterOrLess float64 = 0.33
|
||||
rateIsFalse float64 = 0.1
|
||||
rateIsNull float64 = 0.1
|
||||
rateLike float64 = 0.1
|
||||
)
|
||||
|
||||
// guesstimateFilterRate guesstimates the filter rate for an expression.
|
||||
// For example: a table has 100 rows, after filter expression 'a between 0 and 9',
|
||||
// 10 rows returned, then the filter rate is '0.1'.
|
||||
// It only depends on the expression type, not the expression value.
|
||||
// The expr parameter should contain only one column name.
|
||||
func guesstimateFilterRate(expr ast.ExprNode) float64 {
|
||||
switch x := expr.(type) {
|
||||
case *ast.BetweenExpr:
|
||||
return rateBetween
|
||||
case *ast.BinaryOperationExpr:
|
||||
return guesstimateBinop(x)
|
||||
case *ast.ColumnNameExpr:
|
||||
return rateFull
|
||||
case *ast.IsNullExpr:
|
||||
return guesstimateIsNull(x)
|
||||
case *ast.IsTruthExpr:
|
||||
return guesstimateIsTrue(x)
|
||||
case *ast.ParenthesesExpr:
|
||||
return guesstimateFilterRate(x.Expr)
|
||||
case *ast.PatternInExpr:
|
||||
return guesstimatePatternIn(x)
|
||||
case *ast.PatternLikeExpr:
|
||||
return guesstimatePatternLike(x)
|
||||
}
|
||||
return rateFull
|
||||
}
|
||||
|
||||
func guesstimateBinop(expr *ast.BinaryOperationExpr) float64 {
|
||||
switch expr.Op {
|
||||
case opcode.AndAnd:
|
||||
// P(A and B) = P(A) * P(B)
|
||||
return guesstimateFilterRate(expr.L) * guesstimateFilterRate(expr.R)
|
||||
case opcode.OrOr:
|
||||
// P(A or B) = P(A) + P(B) – P(A and B)
|
||||
rateL := guesstimateFilterRate(expr.L)
|
||||
rateR := guesstimateFilterRate(expr.R)
|
||||
return rateL + rateR - rateL*rateR
|
||||
case opcode.EQ:
|
||||
return rateEqual
|
||||
case opcode.GT, opcode.GE, opcode.LT, opcode.LE:
|
||||
return rateGreaterOrLess
|
||||
case opcode.NE:
|
||||
return rateNotEqual
|
||||
}
|
||||
return rateFull
|
||||
}
|
||||
|
||||
func guesstimateIsNull(expr *ast.IsNullExpr) float64 {
|
||||
if expr.Not {
|
||||
return rateFull - rateIsNull
|
||||
}
|
||||
return rateIsNull
|
||||
}
|
||||
|
||||
func guesstimateIsTrue(expr *ast.IsTruthExpr) float64 {
|
||||
if expr.True == 0 {
|
||||
if expr.Not {
|
||||
return rateFull - rateIsFalse
|
||||
}
|
||||
return rateIsFalse
|
||||
}
|
||||
if expr.Not {
|
||||
return rateIsFalse + rateIsNull
|
||||
}
|
||||
return rateFull - rateIsFalse - rateIsNull
|
||||
}
|
||||
|
||||
func guesstimatePatternIn(expr *ast.PatternInExpr) float64 {
|
||||
if len(expr.List) > 0 {
|
||||
rate := rateEqual * float64(len(expr.List))
|
||||
if expr.Not {
|
||||
return rateFull - rate
|
||||
}
|
||||
return rate
|
||||
}
|
||||
return rateFull
|
||||
}
|
||||
|
||||
func guesstimatePatternLike(expr *ast.PatternLikeExpr) float64 {
|
||||
if expr.Not {
|
||||
return rateFull - rateLike
|
||||
}
|
||||
return rateLike
|
||||
}
|
127
vendor/github.com/pingcap/tidb/optimizer/plan/plan.go
generated
vendored
Normal file
127
vendor/github.com/pingcap/tidb/optimizer/plan/plan.go
generated
vendored
Normal file
|
@ -0,0 +1,127 @@
|
|||
// 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 (
|
||||
"math"
|
||||
|
||||
"github.com/pingcap/tidb/ast"
|
||||
)
|
||||
|
||||
// Plan is a description of an execution flow.
|
||||
// It is created from ast.Node first, then optimized by optimizer,
|
||||
// then used by executor to create a Cursor which executes the statement.
|
||||
type Plan interface {
|
||||
// Accept a visitor, implementation should call Visitor.Enter first,
|
||||
// then call children Accept methods, finally call Visitor.Leave.
|
||||
Accept(v Visitor) (out Plan, ok bool)
|
||||
// Fields returns the result fields of the plan.
|
||||
Fields() []*ast.ResultField
|
||||
// SetFields sets the results fields of the plan.
|
||||
SetFields(fields []*ast.ResultField)
|
||||
// The cost before returning fhe first row.
|
||||
StartupCost() float64
|
||||
// The cost after returning all the rows.
|
||||
TotalCost() float64
|
||||
// The expected row count.
|
||||
RowCount() float64
|
||||
// SetLimit is used to push limit to upstream to estimate the cost.
|
||||
SetLimit(limit float64)
|
||||
}
|
||||
|
||||
// WithSrcPlan is a Plan has a source Plan.
|
||||
type WithSrcPlan interface {
|
||||
Plan
|
||||
Src() Plan
|
||||
SetSrc(src Plan)
|
||||
}
|
||||
|
||||
// Visitor visits a Plan.
|
||||
type Visitor interface {
|
||||
// Enter is called before visit children.
|
||||
// The out plan should be of exactly the same type as the in plan.
|
||||
// if skipChildren is true, the children should not be visited.
|
||||
Enter(in Plan) (out Plan, skipChildren bool)
|
||||
|
||||
// Leave is called after children has been visited, the out Plan can
|
||||
// be another type, this is different than ast.Visitor Leave, because
|
||||
// Plans only contain children plans as Plan interface type, so it is safe
|
||||
// to return a different type of plan.
|
||||
Leave(in Plan) (out Plan, ok bool)
|
||||
}
|
||||
|
||||
// basePlan implements base Plan interface.
|
||||
// Should be used as embedded struct in Plan implementations.
|
||||
type basePlan struct {
|
||||
fields []*ast.ResultField
|
||||
startupCost float64
|
||||
totalCost float64
|
||||
rowCount float64
|
||||
limit float64
|
||||
}
|
||||
|
||||
// StartupCost implements Plan StartupCost interface.
|
||||
func (p *basePlan) StartupCost() float64 {
|
||||
return p.startupCost
|
||||
}
|
||||
|
||||
// TotalCost implements Plan TotalCost interface.
|
||||
func (p *basePlan) TotalCost() float64 {
|
||||
return p.totalCost
|
||||
}
|
||||
|
||||
// RowCount implements Plan RowCount interface.
|
||||
func (p *basePlan) RowCount() float64 {
|
||||
if p.limit == 0 {
|
||||
return p.rowCount
|
||||
}
|
||||
return math.Min(p.rowCount, p.limit)
|
||||
}
|
||||
|
||||
// SetLimit implements Plan SetLimit interface.
|
||||
func (p *basePlan) SetLimit(limit float64) {
|
||||
p.limit = limit
|
||||
}
|
||||
|
||||
// Fields implements Plan Fields interface.
|
||||
func (p *basePlan) Fields() []*ast.ResultField {
|
||||
return p.fields
|
||||
}
|
||||
|
||||
// SetFields implements Plan SetFields interface.
|
||||
func (p *basePlan) SetFields(fields []*ast.ResultField) {
|
||||
p.fields = fields
|
||||
}
|
||||
|
||||
// srcPlan implements base PlanWithSrc interface.
|
||||
type planWithSrc struct {
|
||||
basePlan
|
||||
src Plan
|
||||
}
|
||||
|
||||
// Src implements PlanWithSrc interface.
|
||||
func (p *planWithSrc) Src() Plan {
|
||||
return p.src
|
||||
}
|
||||
|
||||
// SetSrc implements PlanWithSrc interface.
|
||||
func (p *planWithSrc) SetSrc(src Plan) {
|
||||
p.src = src
|
||||
}
|
||||
|
||||
// SetLimit implements Plan interface.
|
||||
func (p *planWithSrc) SetLimit(limit float64) {
|
||||
p.limit = limit
|
||||
p.src.SetLimit(limit)
|
||||
}
|
926
vendor/github.com/pingcap/tidb/optimizer/plan/planbuilder.go
generated
vendored
Normal file
926
vendor/github.com/pingcap/tidb/optimizer/plan/planbuilder.go
generated
vendored
Normal file
|
@ -0,0 +1,926 @@
|
|||
// 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
|
||||
}
|
795
vendor/github.com/pingcap/tidb/optimizer/plan/planbuilder_join.go
generated
vendored
Normal file
795
vendor/github.com/pingcap/tidb/optimizer/plan/planbuilder_join.go
generated
vendored
Normal file
|
@ -0,0 +1,795 @@
|
|||
// 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 (
|
||||
"strings"
|
||||
|
||||
"github.com/ngaut/log"
|
||||
"github.com/pingcap/tidb/ast"
|
||||
"github.com/pingcap/tidb/model"
|
||||
"github.com/pingcap/tidb/mysql"
|
||||
"github.com/pingcap/tidb/parser/opcode"
|
||||
)
|
||||
|
||||
// equalCond represents an equivalent join condition, like "t1.c1 = t2.c1".
|
||||
type equalCond struct {
|
||||
left *ast.ResultField
|
||||
leftIdx bool
|
||||
right *ast.ResultField
|
||||
rightIdx bool
|
||||
}
|
||||
|
||||
func newEqualCond(left, right *ast.ResultField) *equalCond {
|
||||
eq := &equalCond{left: left, right: right}
|
||||
eq.leftIdx = equivHasIndex(eq.left)
|
||||
eq.rightIdx = equivHasIndex(eq.right)
|
||||
return eq
|
||||
}
|
||||
|
||||
func equivHasIndex(rf *ast.ResultField) bool {
|
||||
if rf.Table.PKIsHandle && mysql.HasPriKeyFlag(rf.Column.Flag) {
|
||||
return true
|
||||
}
|
||||
for _, idx := range rf.Table.Indices {
|
||||
if len(idx.Columns) == 1 && idx.Columns[0].Name.L == rf.Column.Name.L {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// joinPath can be a single table path, inner join or outer join.
|
||||
type joinPath struct {
|
||||
// for table path
|
||||
table *ast.TableName
|
||||
totalFilterRate float64
|
||||
|
||||
// for subquery
|
||||
subquery ast.Node
|
||||
asName model.CIStr
|
||||
|
||||
neighborCount int // number of neighbor table.
|
||||
idxDepCount int // number of paths this table depends on.
|
||||
ordering *ast.ResultField
|
||||
orderingDesc bool
|
||||
|
||||
// for outer join path
|
||||
outer *joinPath
|
||||
inner *joinPath
|
||||
rightJoin bool
|
||||
|
||||
// for inner join path
|
||||
inners []*joinPath
|
||||
|
||||
// common
|
||||
parent *joinPath
|
||||
filterRate float64
|
||||
conditions []ast.ExprNode
|
||||
eqConds []*equalCond
|
||||
// The joinPaths that this path's index depends on.
|
||||
idxDeps map[*joinPath]bool
|
||||
neighbors map[*joinPath]bool
|
||||
}
|
||||
|
||||
// newTablePath creates a new table join path.
|
||||
func newTablePath(table *ast.TableName) *joinPath {
|
||||
return &joinPath{
|
||||
table: table,
|
||||
filterRate: rateFull,
|
||||
}
|
||||
}
|
||||
|
||||
// newSubqueryPath creates a new subquery join path.
|
||||
func newSubqueryPath(node ast.Node, asName model.CIStr) *joinPath {
|
||||
return &joinPath{
|
||||
subquery: node,
|
||||
asName: asName,
|
||||
filterRate: rateFull,
|
||||
}
|
||||
}
|
||||
|
||||
// newOuterJoinPath creates a new outer join path and pushes on condition to children paths.
|
||||
// The returned joinPath slice has one element.
|
||||
func newOuterJoinPath(isRightJoin bool, leftPath, rightPath *joinPath, on *ast.OnCondition) *joinPath {
|
||||
outerJoin := &joinPath{rightJoin: isRightJoin, outer: leftPath, inner: rightPath, filterRate: 1}
|
||||
leftPath.parent = outerJoin
|
||||
rightPath.parent = outerJoin
|
||||
if isRightJoin {
|
||||
outerJoin.outer, outerJoin.inner = outerJoin.inner, outerJoin.outer
|
||||
}
|
||||
if on != nil {
|
||||
conditions := splitWhere(on.Expr)
|
||||
availablePaths := []*joinPath{outerJoin.outer}
|
||||
for _, con := range conditions {
|
||||
if !outerJoin.inner.attachCondition(con, availablePaths) {
|
||||
log.Errorf("Inner failed to attach ON condition")
|
||||
}
|
||||
}
|
||||
}
|
||||
return outerJoin
|
||||
}
|
||||
|
||||
// newInnerJoinPath creates inner join path and pushes on condition to children paths.
|
||||
// If left path or right path is also inner join, it will be merged.
|
||||
func newInnerJoinPath(leftPath, rightPath *joinPath, on *ast.OnCondition) *joinPath {
|
||||
var innerJoin *joinPath
|
||||
if len(leftPath.inners) != 0 {
|
||||
innerJoin = leftPath
|
||||
} else {
|
||||
innerJoin = &joinPath{filterRate: leftPath.filterRate}
|
||||
innerJoin.inners = append(innerJoin.inners, leftPath)
|
||||
}
|
||||
if len(rightPath.inners) != 0 {
|
||||
innerJoin.inners = append(innerJoin.inners, rightPath.inners...)
|
||||
innerJoin.conditions = append(innerJoin.conditions, rightPath.conditions...)
|
||||
} else {
|
||||
innerJoin.inners = append(innerJoin.inners, rightPath)
|
||||
}
|
||||
innerJoin.filterRate *= rightPath.filterRate
|
||||
|
||||
for _, in := range innerJoin.inners {
|
||||
in.parent = innerJoin
|
||||
}
|
||||
|
||||
if on != nil {
|
||||
conditions := splitWhere(on.Expr)
|
||||
for _, con := range conditions {
|
||||
if !innerJoin.attachCondition(con, nil) {
|
||||
innerJoin.conditions = append(innerJoin.conditions, con)
|
||||
}
|
||||
}
|
||||
}
|
||||
return innerJoin
|
||||
}
|
||||
|
||||
func (p *joinPath) resultFields() []*ast.ResultField {
|
||||
if p.table != nil {
|
||||
return p.table.GetResultFields()
|
||||
}
|
||||
if p.outer != nil {
|
||||
if p.rightJoin {
|
||||
return append(p.inner.resultFields(), p.outer.resultFields()...)
|
||||
}
|
||||
return append(p.outer.resultFields(), p.inner.resultFields()...)
|
||||
}
|
||||
var rfs []*ast.ResultField
|
||||
for _, in := range p.inners {
|
||||
rfs = append(rfs, in.resultFields()...)
|
||||
}
|
||||
return rfs
|
||||
}
|
||||
|
||||
// attachCondition tries to attach a condition as deep as possible.
|
||||
// availablePaths are paths join before this path.
|
||||
func (p *joinPath) attachCondition(condition ast.ExprNode, availablePaths []*joinPath) (attached bool) {
|
||||
filterRate := guesstimateFilterRate(condition)
|
||||
// table
|
||||
if p.table != nil || p.subquery != nil {
|
||||
attacher := conditionAttachChecker{targetPath: p, availablePaths: availablePaths}
|
||||
condition.Accept(&attacher)
|
||||
if attacher.invalid {
|
||||
return false
|
||||
}
|
||||
p.conditions = append(p.conditions, condition)
|
||||
p.filterRate *= filterRate
|
||||
return true
|
||||
}
|
||||
// inner join
|
||||
if len(p.inners) > 0 {
|
||||
for _, in := range p.inners {
|
||||
if in.attachCondition(condition, availablePaths) {
|
||||
p.filterRate *= filterRate
|
||||
return true
|
||||
}
|
||||
}
|
||||
attacher := &conditionAttachChecker{targetPath: p, availablePaths: availablePaths}
|
||||
condition.Accept(attacher)
|
||||
if attacher.invalid {
|
||||
return false
|
||||
}
|
||||
p.conditions = append(p.conditions, condition)
|
||||
p.filterRate *= filterRate
|
||||
return true
|
||||
}
|
||||
|
||||
// outer join
|
||||
if p.outer.attachCondition(condition, availablePaths) {
|
||||
p.filterRate *= filterRate
|
||||
return true
|
||||
}
|
||||
if p.inner.attachCondition(condition, append(availablePaths, p.outer)) {
|
||||
p.filterRate *= filterRate
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *joinPath) containsTable(table *ast.TableName) bool {
|
||||
if p.table != nil {
|
||||
return p.table == table
|
||||
}
|
||||
if p.subquery != nil {
|
||||
return p.asName.L == table.Name.L
|
||||
}
|
||||
if len(p.inners) != 0 {
|
||||
for _, in := range p.inners {
|
||||
if in.containsTable(table) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
return p.outer.containsTable(table) || p.inner.containsTable(table)
|
||||
}
|
||||
|
||||
// attachEqualCond tries to attach an equalCond deep into a table path if applicable.
|
||||
func (p *joinPath) attachEqualCond(eqCon *equalCond, availablePaths []*joinPath) (attached bool) {
|
||||
// table
|
||||
if p.table != nil {
|
||||
var prevTable *ast.TableName
|
||||
var needSwap bool
|
||||
if eqCon.left.TableName == p.table {
|
||||
prevTable = eqCon.right.TableName
|
||||
} else if eqCon.right.TableName == p.table {
|
||||
prevTable = eqCon.left.TableName
|
||||
needSwap = true
|
||||
}
|
||||
if prevTable != nil {
|
||||
for _, prev := range availablePaths {
|
||||
if prev.containsTable(prevTable) {
|
||||
if needSwap {
|
||||
eqCon.left, eqCon.right = eqCon.right, eqCon.left
|
||||
eqCon.leftIdx, eqCon.rightIdx = eqCon.rightIdx, eqCon.leftIdx
|
||||
}
|
||||
p.eqConds = append(p.eqConds, eqCon)
|
||||
return true
|
||||
}
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// inner join
|
||||
if len(p.inners) > 0 {
|
||||
for _, in := range p.inners {
|
||||
if in.attachEqualCond(eqCon, availablePaths) {
|
||||
p.filterRate *= rateEqual
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
// outer join
|
||||
if p.outer.attachEqualCond(eqCon, availablePaths) {
|
||||
p.filterRate *= rateEqual
|
||||
return true
|
||||
}
|
||||
if p.inner.attachEqualCond(eqCon, append(availablePaths, p.outer)) {
|
||||
p.filterRate *= rateEqual
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *joinPath) extractEqualConditon() {
|
||||
var equivs []*equalCond
|
||||
var cons []ast.ExprNode
|
||||
for _, con := range p.conditions {
|
||||
eq := equivFromExpr(con)
|
||||
if eq != nil {
|
||||
equivs = append(equivs, eq)
|
||||
if p.table != nil {
|
||||
if eq.right.TableName == p.table {
|
||||
eq.left, eq.right = eq.right, eq.left
|
||||
eq.leftIdx, eq.rightIdx = eq.rightIdx, eq.leftIdx
|
||||
}
|
||||
}
|
||||
} else {
|
||||
cons = append(cons, con)
|
||||
}
|
||||
}
|
||||
p.eqConds = equivs
|
||||
p.conditions = cons
|
||||
for _, in := range p.inners {
|
||||
in.extractEqualConditon()
|
||||
}
|
||||
if p.outer != nil {
|
||||
p.outer.extractEqualConditon()
|
||||
p.inner.extractEqualConditon()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *joinPath) addIndexDependency() {
|
||||
if p.outer != nil {
|
||||
p.outer.addIndexDependency()
|
||||
p.inner.addIndexDependency()
|
||||
return
|
||||
}
|
||||
if p.table != nil {
|
||||
return
|
||||
}
|
||||
for _, eq := range p.eqConds {
|
||||
if !eq.leftIdx && !eq.rightIdx {
|
||||
continue
|
||||
}
|
||||
pathLeft := p.findInnerContains(eq.left.TableName)
|
||||
if pathLeft == nil {
|
||||
continue
|
||||
}
|
||||
pathRight := p.findInnerContains(eq.right.TableName)
|
||||
if pathRight == nil {
|
||||
continue
|
||||
}
|
||||
if eq.leftIdx && eq.rightIdx {
|
||||
pathLeft.addNeighbor(pathRight)
|
||||
pathRight.addNeighbor(pathLeft)
|
||||
} else if eq.leftIdx {
|
||||
if !pathLeft.hasOuterIdxEqualCond() {
|
||||
pathLeft.addIndexDep(pathRight)
|
||||
}
|
||||
} else if eq.rightIdx {
|
||||
if !pathRight.hasOuterIdxEqualCond() {
|
||||
pathRight.addIndexDep(pathLeft)
|
||||
}
|
||||
}
|
||||
}
|
||||
for _, in := range p.inners {
|
||||
in.removeIndexDepCycle(in)
|
||||
in.addIndexDependency()
|
||||
}
|
||||
}
|
||||
|
||||
func (p *joinPath) hasOuterIdxEqualCond() bool {
|
||||
if p.table != nil {
|
||||
for _, eq := range p.eqConds {
|
||||
if eq.leftIdx {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
if p.outer != nil {
|
||||
return p.outer.hasOuterIdxEqualCond()
|
||||
}
|
||||
for _, in := range p.inners {
|
||||
if in.hasOuterIdxEqualCond() {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (p *joinPath) findInnerContains(table *ast.TableName) *joinPath {
|
||||
for _, in := range p.inners {
|
||||
if in.containsTable(table) {
|
||||
return in
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (p *joinPath) addNeighbor(neighbor *joinPath) {
|
||||
if p.neighbors == nil {
|
||||
p.neighbors = map[*joinPath]bool{}
|
||||
}
|
||||
p.neighbors[neighbor] = true
|
||||
p.neighborCount++
|
||||
}
|
||||
|
||||
func (p *joinPath) addIndexDep(dep *joinPath) {
|
||||
if p.idxDeps == nil {
|
||||
p.idxDeps = map[*joinPath]bool{}
|
||||
}
|
||||
p.idxDeps[dep] = true
|
||||
p.idxDepCount++
|
||||
}
|
||||
|
||||
func (p *joinPath) removeIndexDepCycle(origin *joinPath) {
|
||||
if p.idxDeps == nil {
|
||||
return
|
||||
}
|
||||
for dep := range p.idxDeps {
|
||||
if dep == origin {
|
||||
delete(p.idxDeps, origin)
|
||||
continue
|
||||
}
|
||||
dep.removeIndexDepCycle(origin)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *joinPath) score() float64 {
|
||||
return 1 / p.filterRate
|
||||
}
|
||||
|
||||
func (p *joinPath) String() string {
|
||||
if p.table != nil {
|
||||
return p.table.TableInfo.Name.L
|
||||
}
|
||||
if p.outer != nil {
|
||||
return "outer{" + p.outer.String() + "," + p.inner.String() + "}"
|
||||
}
|
||||
var innerStrs []string
|
||||
for _, in := range p.inners {
|
||||
innerStrs = append(innerStrs, in.String())
|
||||
}
|
||||
return "inner{" + strings.Join(innerStrs, ",") + "}"
|
||||
}
|
||||
|
||||
func (p *joinPath) optimizeJoinOrder(availablePaths []*joinPath) {
|
||||
if p.table != nil {
|
||||
return
|
||||
}
|
||||
if p.outer != nil {
|
||||
p.outer.optimizeJoinOrder(availablePaths)
|
||||
p.inner.optimizeJoinOrder(append(availablePaths, p.outer))
|
||||
return
|
||||
}
|
||||
var ordered []*joinPath
|
||||
pathMap := map[*joinPath]bool{}
|
||||
for _, in := range p.inners {
|
||||
pathMap[in] = true
|
||||
}
|
||||
for len(pathMap) > 0 {
|
||||
next := p.nextPath(pathMap, availablePaths)
|
||||
next.optimizeJoinOrder(availablePaths)
|
||||
ordered = append(ordered, next)
|
||||
delete(pathMap, next)
|
||||
availablePaths = append(availablePaths, next)
|
||||
for path := range pathMap {
|
||||
if path.idxDeps != nil {
|
||||
delete(path.idxDeps, next)
|
||||
}
|
||||
if path.neighbors != nil {
|
||||
delete(path.neighbors, next)
|
||||
}
|
||||
}
|
||||
p.reattach(pathMap, availablePaths)
|
||||
}
|
||||
p.inners = ordered
|
||||
}
|
||||
|
||||
// reattach is called by inner joinPath to retry attach conditions to inner paths
|
||||
// after an inner path has been added to available paths.
|
||||
func (p *joinPath) reattach(pathMap map[*joinPath]bool, availablePaths []*joinPath) {
|
||||
if len(p.conditions) != 0 {
|
||||
remainedConds := make([]ast.ExprNode, 0, len(p.conditions))
|
||||
for _, con := range p.conditions {
|
||||
var attached bool
|
||||
for path := range pathMap {
|
||||
if path.attachCondition(con, availablePaths) {
|
||||
attached = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !attached {
|
||||
remainedConds = append(remainedConds, con)
|
||||
}
|
||||
}
|
||||
p.conditions = remainedConds
|
||||
}
|
||||
if len(p.eqConds) != 0 {
|
||||
remainedEqConds := make([]*equalCond, 0, len(p.eqConds))
|
||||
for _, eq := range p.eqConds {
|
||||
var attached bool
|
||||
for path := range pathMap {
|
||||
if path.attachEqualCond(eq, availablePaths) {
|
||||
attached = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !attached {
|
||||
remainedEqConds = append(remainedEqConds, eq)
|
||||
}
|
||||
}
|
||||
p.eqConds = remainedEqConds
|
||||
}
|
||||
}
|
||||
|
||||
func (p *joinPath) nextPath(pathMap map[*joinPath]bool, availablePaths []*joinPath) *joinPath {
|
||||
cans := p.candidates(pathMap)
|
||||
if len(cans) == 0 {
|
||||
var v *joinPath
|
||||
for v = range pathMap {
|
||||
log.Errorf("index dep %v, prevs %v\n", v.idxDeps, len(availablePaths))
|
||||
}
|
||||
return v
|
||||
}
|
||||
indexPath := p.nextIndexPath(cans)
|
||||
if indexPath != nil {
|
||||
return indexPath
|
||||
}
|
||||
return p.pickPath(cans)
|
||||
}
|
||||
|
||||
func (p *joinPath) candidates(pathMap map[*joinPath]bool) []*joinPath {
|
||||
var cans []*joinPath
|
||||
for t := range pathMap {
|
||||
if len(t.idxDeps) > 0 {
|
||||
continue
|
||||
}
|
||||
cans = append(cans, t)
|
||||
}
|
||||
return cans
|
||||
}
|
||||
|
||||
func (p *joinPath) nextIndexPath(candidates []*joinPath) *joinPath {
|
||||
var best *joinPath
|
||||
for _, can := range candidates {
|
||||
// Since we may not have equal conditions attached on the path, we
|
||||
// need to check neighborCount and idxDepCount to see if this path
|
||||
// can be joined with index.
|
||||
neighborIsAvailable := len(can.neighbors) < can.neighborCount
|
||||
idxDepIsAvailable := can.idxDepCount > 0
|
||||
if can.hasOuterIdxEqualCond() || neighborIsAvailable || idxDepIsAvailable {
|
||||
if best == nil {
|
||||
best = can
|
||||
}
|
||||
if can.score() > best.score() {
|
||||
best = can
|
||||
}
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
func (p *joinPath) pickPath(candidates []*joinPath) *joinPath {
|
||||
var best *joinPath
|
||||
for _, path := range candidates {
|
||||
if best == nil {
|
||||
best = path
|
||||
}
|
||||
if path.score() > best.score() {
|
||||
best = path
|
||||
}
|
||||
}
|
||||
return best
|
||||
}
|
||||
|
||||
// conditionAttachChecker checks if an expression is valid to
|
||||
// attach to a path. attach is valid only if all the referenced tables in the
|
||||
// expression are available.
|
||||
type conditionAttachChecker struct {
|
||||
targetPath *joinPath
|
||||
availablePaths []*joinPath
|
||||
invalid bool
|
||||
}
|
||||
|
||||
func (c *conditionAttachChecker) Enter(in ast.Node) (ast.Node, bool) {
|
||||
switch x := in.(type) {
|
||||
case *ast.ColumnNameExpr:
|
||||
table := x.Refer.TableName
|
||||
if c.targetPath.containsTable(table) {
|
||||
return in, false
|
||||
}
|
||||
c.invalid = true
|
||||
for _, path := range c.availablePaths {
|
||||
if path.containsTable(table) {
|
||||
c.invalid = false
|
||||
return in, false
|
||||
}
|
||||
}
|
||||
}
|
||||
return in, false
|
||||
}
|
||||
|
||||
func (c *conditionAttachChecker) Leave(in ast.Node) (ast.Node, bool) {
|
||||
return in, !c.invalid
|
||||
}
|
||||
|
||||
func (b *planBuilder) buildJoin(sel *ast.SelectStmt) Plan {
|
||||
nrfinder := &nullRejectFinder{nullRejectTables: map[*ast.TableName]bool{}}
|
||||
if sel.Where != nil {
|
||||
sel.Where.Accept(nrfinder)
|
||||
}
|
||||
path := b.buildBasicJoinPath(sel.From.TableRefs, nrfinder.nullRejectTables)
|
||||
rfs := path.resultFields()
|
||||
|
||||
whereConditions := splitWhere(sel.Where)
|
||||
for _, whereCond := range whereConditions {
|
||||
if !path.attachCondition(whereCond, nil) {
|
||||
// TODO: Find a better way to handle this condition.
|
||||
path.conditions = append(path.conditions, whereCond)
|
||||
log.Errorf("Failed to attach where condtion.")
|
||||
}
|
||||
}
|
||||
path.extractEqualConditon()
|
||||
path.addIndexDependency()
|
||||
path.optimizeJoinOrder(nil)
|
||||
p := b.buildPlanFromJoinPath(path)
|
||||
p.SetFields(rfs)
|
||||
return p
|
||||
}
|
||||
|
||||
type nullRejectFinder struct {
|
||||
nullRejectTables map[*ast.TableName]bool
|
||||
}
|
||||
|
||||
func (n *nullRejectFinder) Enter(in ast.Node) (ast.Node, bool) {
|
||||
switch x := in.(type) {
|
||||
case *ast.BinaryOperationExpr:
|
||||
if x.Op == opcode.NullEQ || x.Op == opcode.OrOr {
|
||||
return in, true
|
||||
}
|
||||
case *ast.IsNullExpr:
|
||||
if !x.Not {
|
||||
return in, true
|
||||
}
|
||||
case *ast.IsTruthExpr:
|
||||
if x.Not {
|
||||
return in, true
|
||||
}
|
||||
}
|
||||
return in, false
|
||||
}
|
||||
|
||||
func (n *nullRejectFinder) Leave(in ast.Node) (ast.Node, bool) {
|
||||
switch x := in.(type) {
|
||||
case *ast.ColumnNameExpr:
|
||||
n.nullRejectTables[x.Refer.TableName] = true
|
||||
}
|
||||
return in, true
|
||||
}
|
||||
|
||||
func (b *planBuilder) buildBasicJoinPath(node ast.ResultSetNode, nullRejectTables map[*ast.TableName]bool) *joinPath {
|
||||
switch x := node.(type) {
|
||||
case nil:
|
||||
return nil
|
||||
case *ast.Join:
|
||||
leftPath := b.buildBasicJoinPath(x.Left, nullRejectTables)
|
||||
if x.Right == nil {
|
||||
return leftPath
|
||||
}
|
||||
righPath := b.buildBasicJoinPath(x.Right, nullRejectTables)
|
||||
isOuter := b.isOuterJoin(x.Tp, leftPath, righPath, nullRejectTables)
|
||||
if isOuter {
|
||||
return newOuterJoinPath(x.Tp == ast.RightJoin, leftPath, righPath, x.On)
|
||||
}
|
||||
return newInnerJoinPath(leftPath, righPath, x.On)
|
||||
case *ast.TableSource:
|
||||
switch v := x.Source.(type) {
|
||||
case *ast.TableName:
|
||||
return newTablePath(v)
|
||||
case *ast.SelectStmt, *ast.UnionStmt:
|
||||
return newSubqueryPath(v, x.AsName)
|
||||
default:
|
||||
b.err = ErrUnsupportedType.Gen("unsupported table source type %T", x)
|
||||
return nil
|
||||
}
|
||||
default:
|
||||
b.err = ErrUnsupportedType.Gen("unsupported table source type %T", x)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func (b *planBuilder) isOuterJoin(tp ast.JoinType, leftPaths, rightPaths *joinPath,
|
||||
nullRejectTables map[*ast.TableName]bool) bool {
|
||||
var innerPath *joinPath
|
||||
switch tp {
|
||||
case ast.LeftJoin:
|
||||
innerPath = rightPaths
|
||||
case ast.RightJoin:
|
||||
innerPath = leftPaths
|
||||
default:
|
||||
return false
|
||||
}
|
||||
for table := range nullRejectTables {
|
||||
if innerPath.containsTable(table) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func equivFromExpr(expr ast.ExprNode) *equalCond {
|
||||
binop, ok := expr.(*ast.BinaryOperationExpr)
|
||||
if !ok || binop.Op != opcode.EQ {
|
||||
return nil
|
||||
}
|
||||
ln, lOK := binop.L.(*ast.ColumnNameExpr)
|
||||
rn, rOK := binop.R.(*ast.ColumnNameExpr)
|
||||
if !lOK || !rOK {
|
||||
return nil
|
||||
}
|
||||
if ln.Name.Table.L == "" || rn.Name.Table.L == "" {
|
||||
return nil
|
||||
}
|
||||
if ln.Name.Schema.L == rn.Name.Schema.L && ln.Name.Table.L == rn.Name.Table.L {
|
||||
return nil
|
||||
}
|
||||
return newEqualCond(ln.Refer, rn.Refer)
|
||||
}
|
||||
|
||||
func (b *planBuilder) buildPlanFromJoinPath(path *joinPath) Plan {
|
||||
if path.table != nil {
|
||||
return b.buildTablePlanFromJoinPath(path)
|
||||
}
|
||||
if path.subquery != nil {
|
||||
return b.buildSubqueryJoinPath(path)
|
||||
}
|
||||
if path.outer != nil {
|
||||
join := &JoinOuter{
|
||||
Outer: b.buildPlanFromJoinPath(path.outer),
|
||||
Inner: b.buildPlanFromJoinPath(path.inner),
|
||||
}
|
||||
if path.rightJoin {
|
||||
join.SetFields(append(join.Inner.Fields(), join.Outer.Fields()...))
|
||||
} else {
|
||||
join.SetFields(append(join.Outer.Fields(), join.Inner.Fields()...))
|
||||
}
|
||||
return join
|
||||
}
|
||||
join := &JoinInner{}
|
||||
for _, in := range path.inners {
|
||||
join.Inners = append(join.Inners, b.buildPlanFromJoinPath(in))
|
||||
join.fields = append(join.fields, in.resultFields()...)
|
||||
}
|
||||
join.Conditions = path.conditions
|
||||
for _, equiv := range path.eqConds {
|
||||
cond := &ast.BinaryOperationExpr{L: equiv.left.Expr, R: equiv.right.Expr, Op: opcode.EQ}
|
||||
join.Conditions = append(join.Conditions, cond)
|
||||
}
|
||||
return join
|
||||
}
|
||||
|
||||
func (b *planBuilder) buildTablePlanFromJoinPath(path *joinPath) Plan {
|
||||
for _, equiv := range path.eqConds {
|
||||
columnNameExpr := &ast.ColumnNameExpr{}
|
||||
columnNameExpr.Name = &ast.ColumnName{}
|
||||
columnNameExpr.Name.Name = equiv.left.Column.Name
|
||||
columnNameExpr.Name.Table = equiv.left.Table.Name
|
||||
columnNameExpr.Refer = equiv.left
|
||||
condition := &ast.BinaryOperationExpr{L: columnNameExpr, R: equiv.right.Expr, Op: opcode.EQ}
|
||||
ast.SetFlag(condition)
|
||||
path.conditions = append(path.conditions, condition)
|
||||
}
|
||||
candidates := b.buildAllAccessMethodsPlan(path)
|
||||
var p Plan
|
||||
var lowestCost float64
|
||||
for _, can := range candidates {
|
||||
cost := EstimateCost(can)
|
||||
if p == nil {
|
||||
p = can
|
||||
lowestCost = cost
|
||||
}
|
||||
if cost < lowestCost {
|
||||
p = can
|
||||
lowestCost = cost
|
||||
}
|
||||
}
|
||||
return p
|
||||
}
|
||||
|
||||
// Build subquery join path plan
|
||||
func (b *planBuilder) buildSubqueryJoinPath(path *joinPath) Plan {
|
||||
for _, equiv := range path.eqConds {
|
||||
columnNameExpr := &ast.ColumnNameExpr{}
|
||||
columnNameExpr.Name = &ast.ColumnName{}
|
||||
columnNameExpr.Name.Name = equiv.left.Column.Name
|
||||
columnNameExpr.Name.Table = equiv.left.Table.Name
|
||||
columnNameExpr.Refer = equiv.left
|
||||
condition := &ast.BinaryOperationExpr{L: columnNameExpr, R: equiv.right.Expr, Op: opcode.EQ}
|
||||
ast.SetFlag(condition)
|
||||
path.conditions = append(path.conditions, condition)
|
||||
}
|
||||
p := b.build(path.subquery)
|
||||
if len(path.conditions) == 0 {
|
||||
return p
|
||||
}
|
||||
filterPlan := &Filter{Conditions: path.conditions}
|
||||
filterPlan.SetSrc(p)
|
||||
filterPlan.SetFields(p.Fields())
|
||||
return filterPlan
|
||||
}
|
677
vendor/github.com/pingcap/tidb/optimizer/plan/plans.go
generated
vendored
Normal file
677
vendor/github.com/pingcap/tidb/optimizer/plan/plans.go
generated
vendored
Normal file
|
@ -0,0 +1,677 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"github.com/pingcap/tidb/ast"
|
||||
"github.com/pingcap/tidb/model"
|
||||
"github.com/pingcap/tidb/util/types"
|
||||
)
|
||||
|
||||
// TableRange represents a range of row handle.
|
||||
type TableRange struct {
|
||||
LowVal int64
|
||||
HighVal int64
|
||||
}
|
||||
|
||||
// TableScan represents a table scan plan.
|
||||
type TableScan struct {
|
||||
basePlan
|
||||
|
||||
Table *model.TableInfo
|
||||
Desc bool
|
||||
Ranges []TableRange
|
||||
|
||||
// RefAccess indicates it references a previous joined table, used in explain.
|
||||
RefAccess bool
|
||||
|
||||
// AccessConditions can be used to build index range.
|
||||
AccessConditions []ast.ExprNode
|
||||
|
||||
// FilterConditions can be used to filter result.
|
||||
FilterConditions []ast.ExprNode
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *TableScan) Accept(v Visitor) (Plan, bool) {
|
||||
np, _ := v.Enter(p)
|
||||
return v.Leave(np)
|
||||
}
|
||||
|
||||
// ShowDDL is for showing DDL information.
|
||||
type ShowDDL struct {
|
||||
basePlan
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *ShowDDL) Accept(v Visitor) (Plan, bool) {
|
||||
np, _ := v.Enter(p)
|
||||
return v.Leave(np)
|
||||
}
|
||||
|
||||
// CheckTable is for checking table data.
|
||||
type CheckTable struct {
|
||||
basePlan
|
||||
|
||||
Tables []*ast.TableName
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *CheckTable) Accept(v Visitor) (Plan, bool) {
|
||||
np, _ := v.Enter(p)
|
||||
return v.Leave(np)
|
||||
|
||||
}
|
||||
|
||||
// IndexRange represents an index range to be scanned.
|
||||
type IndexRange struct {
|
||||
LowVal []types.Datum
|
||||
LowExclude bool
|
||||
HighVal []types.Datum
|
||||
HighExclude bool
|
||||
}
|
||||
|
||||
// IsPoint returns if the index range is a point.
|
||||
func (ir *IndexRange) IsPoint() bool {
|
||||
if len(ir.LowVal) != len(ir.HighVal) {
|
||||
return false
|
||||
}
|
||||
for i := range ir.LowVal {
|
||||
a := ir.LowVal[i]
|
||||
b := ir.HighVal[i]
|
||||
if a.Kind() == types.KindMinNotNull || b.Kind() == types.KindMaxValue {
|
||||
return false
|
||||
}
|
||||
cmp, err := a.CompareDatum(b)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
if cmp != 0 {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return !ir.LowExclude && !ir.HighExclude
|
||||
}
|
||||
|
||||
// IndexScan represents an index scan plan.
|
||||
type IndexScan struct {
|
||||
basePlan
|
||||
|
||||
// The index used.
|
||||
Index *model.IndexInfo
|
||||
|
||||
// The table to lookup.
|
||||
Table *model.TableInfo
|
||||
|
||||
// Ordered and non-overlapping ranges to be scanned.
|
||||
Ranges []*IndexRange
|
||||
|
||||
// Desc indicates whether the index should be scanned in descending order.
|
||||
Desc bool
|
||||
|
||||
// RefAccess indicates it references a previous joined table, used in explain.
|
||||
RefAccess bool
|
||||
|
||||
// AccessConditions can be used to build index range.
|
||||
AccessConditions []ast.ExprNode
|
||||
|
||||
// Number of leading equal access condition.
|
||||
// The offset of each equal condition correspond to the offset of index column.
|
||||
// For example, an index has column (a, b, c), condition is 'a = 0 and b = 0 and c > 0'
|
||||
// AccessEqualCount would be 2.
|
||||
AccessEqualCount int
|
||||
|
||||
// FilterConditions can be used to filter result.
|
||||
FilterConditions []ast.ExprNode
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *IndexScan) Accept(v Visitor) (Plan, bool) {
|
||||
np, _ := v.Enter(p)
|
||||
return v.Leave(np)
|
||||
}
|
||||
|
||||
// JoinOuter represents outer join plan.
|
||||
type JoinOuter struct {
|
||||
basePlan
|
||||
|
||||
Outer Plan
|
||||
Inner Plan
|
||||
}
|
||||
|
||||
// Accept implements Plan interface.
|
||||
func (p *JoinOuter) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*JoinOuter)
|
||||
var ok bool
|
||||
p.Outer, ok = p.Outer.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
p.Inner, ok = p.Inner.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// JoinInner represents inner join plan.
|
||||
type JoinInner struct {
|
||||
basePlan
|
||||
|
||||
Inners []Plan
|
||||
Conditions []ast.ExprNode
|
||||
}
|
||||
|
||||
func (p *JoinInner) String() string {
|
||||
return fmt.Sprintf("JoinInner()")
|
||||
}
|
||||
|
||||
// Accept implements Plan interface.
|
||||
func (p *JoinInner) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*JoinInner)
|
||||
for i, in := range p.Inners {
|
||||
x, ok := in.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
p.Inners[i] = x
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// SelectLock represents a select lock plan.
|
||||
type SelectLock struct {
|
||||
planWithSrc
|
||||
|
||||
Lock ast.SelectLockType
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *SelectLock) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*SelectLock)
|
||||
var ok bool
|
||||
p.src, ok = p.src.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// SetLimit implements Plan SetLimit interface.
|
||||
func (p *SelectLock) SetLimit(limit float64) {
|
||||
p.limit = limit
|
||||
p.src.SetLimit(p.limit)
|
||||
}
|
||||
|
||||
// SelectFields represents a select fields plan.
|
||||
type SelectFields struct {
|
||||
planWithSrc
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *SelectFields) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*SelectFields)
|
||||
if p.src != nil {
|
||||
var ok bool
|
||||
p.src, ok = p.src.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// SetLimit implements Plan SetLimit interface.
|
||||
func (p *SelectFields) SetLimit(limit float64) {
|
||||
p.limit = limit
|
||||
if p.src != nil {
|
||||
p.src.SetLimit(limit)
|
||||
}
|
||||
}
|
||||
|
||||
// Sort represents a sorting plan.
|
||||
type Sort struct {
|
||||
planWithSrc
|
||||
|
||||
ByItems []*ast.ByItem
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Sort) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*Sort)
|
||||
var ok bool
|
||||
p.src, ok = p.src.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// SetLimit implements Plan SetLimit interface.
|
||||
// It set the Src limit only if it is bypassed.
|
||||
// Bypass has to be determined before this get called.
|
||||
func (p *Sort) SetLimit(limit float64) {
|
||||
p.limit = limit
|
||||
}
|
||||
|
||||
// Limit represents offset and limit plan.
|
||||
type Limit struct {
|
||||
planWithSrc
|
||||
|
||||
Offset uint64
|
||||
Count uint64
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Limit) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*Limit)
|
||||
var ok bool
|
||||
p.src, ok = p.src.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// SetLimit implements Plan SetLimit interface.
|
||||
// As Limit itself determine the real limit,
|
||||
// We just ignore the input, and set the real limit.
|
||||
func (p *Limit) SetLimit(limit float64) {
|
||||
p.limit = float64(p.Offset + p.Count)
|
||||
p.src.SetLimit(p.limit)
|
||||
}
|
||||
|
||||
// Union represents Union plan.
|
||||
type Union struct {
|
||||
basePlan
|
||||
|
||||
Selects []Plan
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Union) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(p)
|
||||
}
|
||||
p = np.(*Union)
|
||||
for i, sel := range p.Selects {
|
||||
var ok bool
|
||||
p.Selects[i], ok = sel.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// Distinct represents Distinct plan.
|
||||
type Distinct struct {
|
||||
planWithSrc
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Distinct) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(p)
|
||||
}
|
||||
p = np.(*Distinct)
|
||||
var ok bool
|
||||
p.src, ok = p.src.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// SetLimit implements Plan SetLimit interface.
|
||||
func (p *Distinct) SetLimit(limit float64) {
|
||||
p.limit = limit
|
||||
if p.src != nil {
|
||||
p.src.SetLimit(limit)
|
||||
}
|
||||
}
|
||||
|
||||
// Prepare represents prepare plan.
|
||||
type Prepare struct {
|
||||
basePlan
|
||||
|
||||
Name string
|
||||
SQLText string
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Prepare) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*Prepare)
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// Execute represents prepare plan.
|
||||
type Execute struct {
|
||||
basePlan
|
||||
|
||||
Name string
|
||||
UsingVars []ast.ExprNode
|
||||
ID uint32
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Execute) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*Execute)
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// Deallocate represents deallocate plan.
|
||||
type Deallocate struct {
|
||||
basePlan
|
||||
|
||||
Name string
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Deallocate) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*Deallocate)
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// Aggregate represents a select fields plan.
|
||||
type Aggregate struct {
|
||||
planWithSrc
|
||||
AggFuncs []*ast.AggregateFuncExpr
|
||||
GroupByItems []*ast.ByItem
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Aggregate) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*Aggregate)
|
||||
if p.src != nil {
|
||||
var ok bool
|
||||
p.src, ok = p.src.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// SetLimit implements Plan SetLimit interface.
|
||||
func (p *Aggregate) SetLimit(limit float64) {
|
||||
p.limit = limit
|
||||
if p.src != nil {
|
||||
p.src.SetLimit(limit)
|
||||
}
|
||||
}
|
||||
|
||||
// Having represents a having plan.
|
||||
// The having plan should after aggregate plan.
|
||||
type Having struct {
|
||||
planWithSrc
|
||||
|
||||
// Originally the WHERE or ON condition is parsed into a single expression,
|
||||
// but after we converted to CNF(Conjunctive normal form), it can be
|
||||
// split into a list of AND conditions.
|
||||
Conditions []ast.ExprNode
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Having) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*Having)
|
||||
var ok bool
|
||||
p.src, ok = p.src.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// SetLimit implements Plan SetLimit interface.
|
||||
func (p *Having) SetLimit(limit float64) {
|
||||
p.limit = limit
|
||||
// We assume 50% of the src row is filtered out.
|
||||
p.src.SetLimit(limit * 2)
|
||||
}
|
||||
|
||||
// Update represents an update plan.
|
||||
type Update struct {
|
||||
basePlan
|
||||
|
||||
OrderedList []*ast.Assignment // OrderedList has the same offset as TablePlan's result fields.
|
||||
SelectPlan Plan
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Update) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*Update)
|
||||
var ok bool
|
||||
p.SelectPlan, ok = p.SelectPlan.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// Delete represents a delete plan.
|
||||
type Delete struct {
|
||||
basePlan
|
||||
|
||||
SelectPlan Plan
|
||||
Tables []*ast.TableName
|
||||
IsMultiTable bool
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Delete) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*Delete)
|
||||
var ok bool
|
||||
p.SelectPlan, ok = p.SelectPlan.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// Filter represents a plan that filter srcplan result.
|
||||
type Filter struct {
|
||||
planWithSrc
|
||||
|
||||
// Originally the WHERE or ON condition is parsed into a single expression,
|
||||
// but after we converted to CNF(Conjunctive normal form), it can be
|
||||
// split into a list of AND conditions.
|
||||
Conditions []ast.ExprNode
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Filter) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*Filter)
|
||||
var ok bool
|
||||
p.src, ok = p.src.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// SetLimit implements Plan SetLimit interface.
|
||||
func (p *Filter) SetLimit(limit float64) {
|
||||
p.limit = limit
|
||||
// We assume 50% of the src row is filtered out.
|
||||
p.src.SetLimit(limit * 2)
|
||||
}
|
||||
|
||||
// Show represents a show plan.
|
||||
type Show struct {
|
||||
basePlan
|
||||
|
||||
Tp ast.ShowStmtType // Databases/Tables/Columns/....
|
||||
DBName string
|
||||
Table *ast.TableName // Used for showing columns.
|
||||
Column *ast.ColumnName // Used for `desc table column`.
|
||||
Flag int // Some flag parsed from sql, such as FULL.
|
||||
Full bool
|
||||
User string // Used for show grants.
|
||||
|
||||
// Used by show variables
|
||||
GlobalScope bool
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Show) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*Show)
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// Simple represents a simple statement plan which doesn't need any optimization.
|
||||
type Simple struct {
|
||||
basePlan
|
||||
|
||||
Statement ast.StmtNode
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Simple) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*Simple)
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// Insert represents an insert plan.
|
||||
type Insert struct {
|
||||
basePlan
|
||||
|
||||
Table *ast.TableRefsClause
|
||||
Columns []*ast.ColumnName
|
||||
Lists [][]ast.ExprNode
|
||||
Setlist []*ast.Assignment
|
||||
OnDuplicate []*ast.Assignment
|
||||
SelectPlan Plan
|
||||
|
||||
IsReplace bool
|
||||
Priority int
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Insert) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*Insert)
|
||||
if p.SelectPlan != nil {
|
||||
var ok bool
|
||||
p.SelectPlan, ok = p.SelectPlan.Accept(v)
|
||||
if !ok {
|
||||
return p, false
|
||||
}
|
||||
}
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// DDL represents a DDL statement plan.
|
||||
type DDL struct {
|
||||
basePlan
|
||||
|
||||
Statement ast.DDLNode
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *DDL) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
return v.Leave(np)
|
||||
}
|
||||
p = np.(*DDL)
|
||||
return v.Leave(p)
|
||||
}
|
||||
|
||||
// Explain represents a explain plan.
|
||||
type Explain struct {
|
||||
basePlan
|
||||
|
||||
StmtPlan Plan
|
||||
}
|
||||
|
||||
// Accept implements Plan Accept interface.
|
||||
func (p *Explain) Accept(v Visitor) (Plan, bool) {
|
||||
np, skip := v.Enter(p)
|
||||
if skip {
|
||||
v.Leave(np)
|
||||
}
|
||||
p = np.(*Explain)
|
||||
return v.Leave(p)
|
||||
}
|
505
vendor/github.com/pingcap/tidb/optimizer/plan/range.go
generated
vendored
Normal file
505
vendor/github.com/pingcap/tidb/optimizer/plan/range.go
generated
vendored
Normal file
|
@ -0,0 +1,505 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"math"
|
||||
"sort"
|
||||
|
||||
"github.com/juju/errors"
|
||||
"github.com/pingcap/tidb/ast"
|
||||
"github.com/pingcap/tidb/parser/opcode"
|
||||
"github.com/pingcap/tidb/util/types"
|
||||
)
|
||||
|
||||
type rangePoint struct {
|
||||
value types.Datum
|
||||
excl bool // exclude
|
||||
start bool
|
||||
}
|
||||
|
||||
func (rp rangePoint) String() string {
|
||||
val := rp.value.GetValue()
|
||||
if rp.value.Kind() == types.KindMinNotNull {
|
||||
val = "-inf"
|
||||
} else if rp.value.Kind() == types.KindMaxValue {
|
||||
val = "+inf"
|
||||
}
|
||||
if rp.start {
|
||||
symbol := "["
|
||||
if rp.excl {
|
||||
symbol = "("
|
||||
}
|
||||
return fmt.Sprintf("%s%v", symbol, val)
|
||||
}
|
||||
symbol := "]"
|
||||
if rp.excl {
|
||||
symbol = ")"
|
||||
}
|
||||
return fmt.Sprintf("%v%s", val, symbol)
|
||||
}
|
||||
|
||||
type rangePointSorter struct {
|
||||
points []rangePoint
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *rangePointSorter) Len() int {
|
||||
return len(r.points)
|
||||
}
|
||||
|
||||
func (r *rangePointSorter) Less(i, j int) bool {
|
||||
a := r.points[i]
|
||||
b := r.points[j]
|
||||
cmp, err := a.value.CompareDatum(b.value)
|
||||
if err != nil {
|
||||
r.err = err
|
||||
return true
|
||||
}
|
||||
if cmp == 0 {
|
||||
return r.equalValueLess(a, b)
|
||||
}
|
||||
return cmp < 0
|
||||
}
|
||||
|
||||
func (r *rangePointSorter) equalValueLess(a, b rangePoint) bool {
|
||||
if a.start && b.start {
|
||||
return !a.excl && b.excl
|
||||
} else if a.start {
|
||||
return !b.excl
|
||||
} else if b.start {
|
||||
return a.excl || b.excl
|
||||
}
|
||||
return a.excl && !b.excl
|
||||
}
|
||||
|
||||
func (r *rangePointSorter) Swap(i, j int) {
|
||||
r.points[i], r.points[j] = r.points[j], r.points[i]
|
||||
}
|
||||
|
||||
type rangeBuilder struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *rangeBuilder) build(expr ast.ExprNode) []rangePoint {
|
||||
switch x := expr.(type) {
|
||||
case *ast.BinaryOperationExpr:
|
||||
return r.buildFromBinop(x)
|
||||
case *ast.PatternInExpr:
|
||||
return r.buildFromIn(x)
|
||||
case *ast.ParenthesesExpr:
|
||||
return r.build(x.Expr)
|
||||
case *ast.BetweenExpr:
|
||||
return r.buildFromBetween(x)
|
||||
case *ast.IsNullExpr:
|
||||
return r.buildFromIsNull(x)
|
||||
case *ast.IsTruthExpr:
|
||||
return r.buildFromIsTruth(x)
|
||||
case *ast.PatternLikeExpr:
|
||||
rans := r.buildFromPatternLike(x)
|
||||
return rans
|
||||
case *ast.ColumnNameExpr:
|
||||
return r.buildFromColumnName(x)
|
||||
}
|
||||
return fullRange
|
||||
}
|
||||
|
||||
func (r *rangeBuilder) buildFromBinop(x *ast.BinaryOperationExpr) []rangePoint {
|
||||
if x.Op == opcode.OrOr {
|
||||
return r.union(r.build(x.L), r.build(x.R))
|
||||
} else if x.Op == opcode.AndAnd {
|
||||
return r.intersection(r.build(x.L), r.build(x.R))
|
||||
}
|
||||
// This has been checked that the binary operation is comparison operation, and one of
|
||||
// the operand is column name expression.
|
||||
var value types.Datum
|
||||
var op opcode.Op
|
||||
if _, ok := x.L.(*ast.ValueExpr); ok {
|
||||
value = types.NewDatum(x.L.GetValue())
|
||||
switch x.Op {
|
||||
case opcode.GE:
|
||||
op = opcode.LE
|
||||
case opcode.GT:
|
||||
op = opcode.LT
|
||||
case opcode.LT:
|
||||
op = opcode.GT
|
||||
case opcode.LE:
|
||||
op = opcode.GE
|
||||
default:
|
||||
op = x.Op
|
||||
}
|
||||
} else {
|
||||
value = types.NewDatum(x.R.GetValue())
|
||||
op = x.Op
|
||||
}
|
||||
if value.Kind() == types.KindNull {
|
||||
return nil
|
||||
}
|
||||
switch op {
|
||||
case opcode.EQ:
|
||||
startPoint := rangePoint{value: value, start: true}
|
||||
endPoint := rangePoint{value: value}
|
||||
return []rangePoint{startPoint, endPoint}
|
||||
case opcode.NE:
|
||||
startPoint1 := rangePoint{value: types.MinNotNullDatum(), start: true}
|
||||
endPoint1 := rangePoint{value: value, excl: true}
|
||||
startPoint2 := rangePoint{value: value, start: true, excl: true}
|
||||
endPoint2 := rangePoint{value: types.MaxValueDatum()}
|
||||
return []rangePoint{startPoint1, endPoint1, startPoint2, endPoint2}
|
||||
case opcode.LT:
|
||||
startPoint := rangePoint{value: types.MinNotNullDatum(), start: true}
|
||||
endPoint := rangePoint{value: value, excl: true}
|
||||
return []rangePoint{startPoint, endPoint}
|
||||
case opcode.LE:
|
||||
startPoint := rangePoint{value: types.MinNotNullDatum(), start: true}
|
||||
endPoint := rangePoint{value: value}
|
||||
return []rangePoint{startPoint, endPoint}
|
||||
case opcode.GT:
|
||||
startPoint := rangePoint{value: value, start: true, excl: true}
|
||||
endPoint := rangePoint{value: types.MaxValueDatum()}
|
||||
return []rangePoint{startPoint, endPoint}
|
||||
case opcode.GE:
|
||||
startPoint := rangePoint{value: value, start: true}
|
||||
endPoint := rangePoint{value: types.MaxValueDatum()}
|
||||
return []rangePoint{startPoint, endPoint}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *rangeBuilder) buildFromIn(x *ast.PatternInExpr) []rangePoint {
|
||||
if x.Not {
|
||||
r.err = ErrUnsupportedType.Gen("NOT IN is not supported")
|
||||
return fullRange
|
||||
}
|
||||
var rangePoints []rangePoint
|
||||
for _, v := range x.List {
|
||||
startPoint := rangePoint{value: types.NewDatum(v.GetValue()), start: true}
|
||||
endPoint := rangePoint{value: types.NewDatum(v.GetValue())}
|
||||
rangePoints = append(rangePoints, startPoint, endPoint)
|
||||
}
|
||||
sorter := rangePointSorter{points: rangePoints}
|
||||
sort.Sort(&sorter)
|
||||
if sorter.err != nil {
|
||||
r.err = sorter.err
|
||||
}
|
||||
// check duplicates
|
||||
hasDuplicate := false
|
||||
isStart := false
|
||||
for _, v := range rangePoints {
|
||||
if isStart == v.start {
|
||||
hasDuplicate = true
|
||||
break
|
||||
}
|
||||
isStart = v.start
|
||||
}
|
||||
if !hasDuplicate {
|
||||
return rangePoints
|
||||
}
|
||||
// remove duplicates
|
||||
distinctRangePoints := make([]rangePoint, 0, len(rangePoints))
|
||||
isStart = false
|
||||
for i := 0; i < len(rangePoints); i++ {
|
||||
current := rangePoints[i]
|
||||
if isStart == current.start {
|
||||
continue
|
||||
}
|
||||
distinctRangePoints = append(distinctRangePoints, current)
|
||||
isStart = current.start
|
||||
}
|
||||
return distinctRangePoints
|
||||
}
|
||||
|
||||
func (r *rangeBuilder) buildFromBetween(x *ast.BetweenExpr) []rangePoint {
|
||||
if x.Not {
|
||||
binop1 := &ast.BinaryOperationExpr{Op: opcode.LT, L: x.Expr, R: x.Left}
|
||||
binop2 := &ast.BinaryOperationExpr{Op: opcode.GT, L: x.Expr, R: x.Right}
|
||||
range1 := r.buildFromBinop(binop1)
|
||||
range2 := r.buildFromBinop(binop2)
|
||||
return r.union(range1, range2)
|
||||
}
|
||||
binop1 := &ast.BinaryOperationExpr{Op: opcode.GE, L: x.Expr, R: x.Left}
|
||||
binop2 := &ast.BinaryOperationExpr{Op: opcode.LE, L: x.Expr, R: x.Right}
|
||||
range1 := r.buildFromBinop(binop1)
|
||||
range2 := r.buildFromBinop(binop2)
|
||||
return r.intersection(range1, range2)
|
||||
}
|
||||
|
||||
func (r *rangeBuilder) buildFromIsNull(x *ast.IsNullExpr) []rangePoint {
|
||||
if x.Not {
|
||||
startPoint := rangePoint{value: types.MinNotNullDatum(), start: true}
|
||||
endPoint := rangePoint{value: types.MaxValueDatum()}
|
||||
return []rangePoint{startPoint, endPoint}
|
||||
}
|
||||
startPoint := rangePoint{start: true}
|
||||
endPoint := rangePoint{}
|
||||
return []rangePoint{startPoint, endPoint}
|
||||
}
|
||||
|
||||
func (r *rangeBuilder) buildFromIsTruth(x *ast.IsTruthExpr) []rangePoint {
|
||||
if x.True != 0 {
|
||||
if x.Not {
|
||||
// NOT TRUE range is {[null null] [0, 0]}
|
||||
startPoint1 := rangePoint{start: true}
|
||||
endPoint1 := rangePoint{}
|
||||
startPoint2 := rangePoint{start: true}
|
||||
startPoint2.value.SetInt64(0)
|
||||
endPoint2 := rangePoint{}
|
||||
endPoint2.value.SetInt64(0)
|
||||
return []rangePoint{startPoint1, endPoint1, startPoint2, endPoint2}
|
||||
}
|
||||
// TRUE range is {[-inf 0) (0 +inf]}
|
||||
startPoint1 := rangePoint{value: types.MinNotNullDatum(), start: true}
|
||||
endPoint1 := rangePoint{excl: true}
|
||||
endPoint1.value.SetInt64(0)
|
||||
startPoint2 := rangePoint{excl: true, start: true}
|
||||
startPoint2.value.SetInt64(0)
|
||||
endPoint2 := rangePoint{value: types.MaxValueDatum()}
|
||||
return []rangePoint{startPoint1, endPoint1, startPoint2, endPoint2}
|
||||
}
|
||||
if x.Not {
|
||||
startPoint1 := rangePoint{start: true}
|
||||
endPoint1 := rangePoint{excl: true}
|
||||
endPoint1.value.SetInt64(0)
|
||||
startPoint2 := rangePoint{start: true, excl: true}
|
||||
startPoint2.value.SetInt64(0)
|
||||
endPoint2 := rangePoint{value: types.MaxValueDatum()}
|
||||
return []rangePoint{startPoint1, endPoint1, startPoint2, endPoint2}
|
||||
}
|
||||
startPoint := rangePoint{start: true}
|
||||
startPoint.value.SetInt64(0)
|
||||
endPoint := rangePoint{}
|
||||
endPoint.value.SetInt64(0)
|
||||
return []rangePoint{startPoint, endPoint}
|
||||
}
|
||||
|
||||
func (r *rangeBuilder) buildFromPatternLike(x *ast.PatternLikeExpr) []rangePoint {
|
||||
if x.Not {
|
||||
// Pattern not like is not supported.
|
||||
r.err = ErrUnsupportedType.Gen("NOT LIKE is not supported.")
|
||||
return fullRange
|
||||
}
|
||||
pattern, err := types.ToString(x.Pattern.GetValue())
|
||||
if err != nil {
|
||||
r.err = errors.Trace(err)
|
||||
return fullRange
|
||||
}
|
||||
lowValue := make([]byte, 0, len(pattern))
|
||||
// unscape the pattern
|
||||
var exclude bool
|
||||
for i := 0; i < len(pattern); i++ {
|
||||
if pattern[i] == x.Escape {
|
||||
i++
|
||||
if i < len(pattern) {
|
||||
lowValue = append(lowValue, pattern[i])
|
||||
} else {
|
||||
lowValue = append(lowValue, x.Escape)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if pattern[i] == '%' {
|
||||
break
|
||||
} else if pattern[i] == '_' {
|
||||
exclude = true
|
||||
break
|
||||
}
|
||||
lowValue = append(lowValue, pattern[i])
|
||||
}
|
||||
if len(lowValue) == 0 {
|
||||
return []rangePoint{{value: types.MinNotNullDatum(), start: true}, {value: types.MaxValueDatum()}}
|
||||
}
|
||||
startPoint := rangePoint{start: true, excl: exclude}
|
||||
startPoint.value.SetBytesAsString(lowValue)
|
||||
highValue := make([]byte, len(lowValue))
|
||||
copy(highValue, lowValue)
|
||||
endPoint := rangePoint{excl: true}
|
||||
for i := len(highValue) - 1; i >= 0; i-- {
|
||||
highValue[i]++
|
||||
if highValue[i] != 0 {
|
||||
endPoint.value.SetBytesAsString(highValue)
|
||||
break
|
||||
}
|
||||
if i == 0 {
|
||||
endPoint.value = types.MaxValueDatum()
|
||||
break
|
||||
}
|
||||
}
|
||||
ranges := make([]rangePoint, 2)
|
||||
ranges[0] = startPoint
|
||||
ranges[1] = endPoint
|
||||
return ranges
|
||||
}
|
||||
|
||||
func (r *rangeBuilder) buildFromColumnName(x *ast.ColumnNameExpr) []rangePoint {
|
||||
// column name expression is equivalent to column name is true.
|
||||
startPoint1 := rangePoint{value: types.MinNotNullDatum(), start: true}
|
||||
endPoint1 := rangePoint{excl: true}
|
||||
endPoint1.value.SetInt64(0)
|
||||
startPoint2 := rangePoint{excl: true, start: true}
|
||||
startPoint2.value.SetInt64(0)
|
||||
endPoint2 := rangePoint{value: types.MaxValueDatum()}
|
||||
return []rangePoint{startPoint1, endPoint1, startPoint2, endPoint2}
|
||||
}
|
||||
|
||||
func (r *rangeBuilder) intersection(a, b []rangePoint) []rangePoint {
|
||||
return r.merge(a, b, false)
|
||||
}
|
||||
|
||||
func (r *rangeBuilder) union(a, b []rangePoint) []rangePoint {
|
||||
return r.merge(a, b, true)
|
||||
}
|
||||
|
||||
func (r *rangeBuilder) merge(a, b []rangePoint, union bool) []rangePoint {
|
||||
sorter := rangePointSorter{points: append(a, b...)}
|
||||
sort.Sort(&sorter)
|
||||
if sorter.err != nil {
|
||||
r.err = sorter.err
|
||||
return nil
|
||||
}
|
||||
var (
|
||||
merged []rangePoint
|
||||
inRangeCount int
|
||||
requiredInRangeCount int
|
||||
)
|
||||
if union {
|
||||
requiredInRangeCount = 1
|
||||
} else {
|
||||
requiredInRangeCount = 2
|
||||
}
|
||||
for _, val := range sorter.points {
|
||||
if val.start {
|
||||
inRangeCount++
|
||||
if inRangeCount == requiredInRangeCount {
|
||||
// just reached the required in range count, a new range started.
|
||||
merged = append(merged, val)
|
||||
}
|
||||
} else {
|
||||
if inRangeCount == requiredInRangeCount {
|
||||
// just about to leave the required in range count, the range is ended.
|
||||
merged = append(merged, val)
|
||||
}
|
||||
inRangeCount--
|
||||
}
|
||||
}
|
||||
return merged
|
||||
}
|
||||
|
||||
// buildIndexRanges build index ranges from range points.
|
||||
// Only the first column in the index is built, extra column ranges will be appended by
|
||||
// appendIndexRanges.
|
||||
func (r *rangeBuilder) buildIndexRanges(rangePoints []rangePoint) []*IndexRange {
|
||||
indexRanges := make([]*IndexRange, 0, len(rangePoints)/2)
|
||||
for i := 0; i < len(rangePoints); i += 2 {
|
||||
startPoint := rangePoints[i]
|
||||
endPoint := rangePoints[i+1]
|
||||
ir := &IndexRange{
|
||||
LowVal: []types.Datum{startPoint.value},
|
||||
LowExclude: startPoint.excl,
|
||||
HighVal: []types.Datum{endPoint.value},
|
||||
HighExclude: endPoint.excl,
|
||||
}
|
||||
indexRanges = append(indexRanges, ir)
|
||||
}
|
||||
return indexRanges
|
||||
}
|
||||
|
||||
// appendIndexRanges appends additional column ranges for multi-column index.
|
||||
// The additional column ranges can only be appended to point ranges.
|
||||
// for example we have an index (a, b), if the condition is (a > 1 and b = 2)
|
||||
// then we can not build a conjunctive ranges for this index.
|
||||
func (r *rangeBuilder) appendIndexRanges(origin []*IndexRange, rangePoints []rangePoint) []*IndexRange {
|
||||
var newIndexRanges []*IndexRange
|
||||
for i := 0; i < len(origin); i++ {
|
||||
oRange := origin[i]
|
||||
if !oRange.IsPoint() {
|
||||
newIndexRanges = append(newIndexRanges, oRange)
|
||||
} else {
|
||||
newIndexRanges = append(newIndexRanges, r.appendIndexRange(oRange, rangePoints)...)
|
||||
}
|
||||
}
|
||||
return newIndexRanges
|
||||
}
|
||||
|
||||
func (r *rangeBuilder) appendIndexRange(origin *IndexRange, rangePoints []rangePoint) []*IndexRange {
|
||||
newRanges := make([]*IndexRange, 0, len(rangePoints)/2)
|
||||
for i := 0; i < len(rangePoints); i += 2 {
|
||||
startPoint := rangePoints[i]
|
||||
lowVal := make([]types.Datum, len(origin.LowVal)+1)
|
||||
copy(lowVal, origin.LowVal)
|
||||
lowVal[len(origin.LowVal)] = startPoint.value
|
||||
|
||||
endPoint := rangePoints[i+1]
|
||||
highVal := make([]types.Datum, len(origin.HighVal)+1)
|
||||
copy(highVal, origin.HighVal)
|
||||
highVal[len(origin.HighVal)] = endPoint.value
|
||||
|
||||
ir := &IndexRange{
|
||||
LowVal: lowVal,
|
||||
LowExclude: startPoint.excl,
|
||||
HighVal: highVal,
|
||||
HighExclude: endPoint.excl,
|
||||
}
|
||||
newRanges = append(newRanges, ir)
|
||||
}
|
||||
return newRanges
|
||||
}
|
||||
|
||||
func (r *rangeBuilder) buildTableRanges(rangePoints []rangePoint) []TableRange {
|
||||
tableRanges := make([]TableRange, 0, len(rangePoints)/2)
|
||||
for i := 0; i < len(rangePoints); i += 2 {
|
||||
startPoint := rangePoints[i]
|
||||
if startPoint.value.Kind() == types.KindNull || startPoint.value.Kind() == types.KindMinNotNull {
|
||||
startPoint.value.SetInt64(math.MinInt64)
|
||||
}
|
||||
startInt, err := types.ToInt64(startPoint.value.GetValue())
|
||||
if err != nil {
|
||||
r.err = errors.Trace(err)
|
||||
return tableRanges
|
||||
}
|
||||
startDatum := types.NewDatum(startInt)
|
||||
cmp, err := startDatum.CompareDatum(startPoint.value)
|
||||
if err != nil {
|
||||
r.err = errors.Trace(err)
|
||||
return tableRanges
|
||||
}
|
||||
if cmp < 0 || (cmp == 0 && startPoint.excl) {
|
||||
startInt++
|
||||
}
|
||||
endPoint := rangePoints[i+1]
|
||||
if endPoint.value.Kind() == types.KindNull {
|
||||
endPoint.value.SetInt64(math.MinInt64)
|
||||
} else if endPoint.value.Kind() == types.KindMaxValue {
|
||||
endPoint.value.SetInt64(math.MaxInt64)
|
||||
}
|
||||
endInt, err := types.ToInt64(endPoint.value.GetValue())
|
||||
if err != nil {
|
||||
r.err = errors.Trace(err)
|
||||
return tableRanges
|
||||
}
|
||||
endDatum := types.NewDatum(endInt)
|
||||
cmp, err = endDatum.CompareDatum(endPoint.value)
|
||||
if err != nil {
|
||||
r.err = errors.Trace(err)
|
||||
return tableRanges
|
||||
}
|
||||
if cmp > 0 || (cmp == 0 && endPoint.excl) {
|
||||
endInt--
|
||||
}
|
||||
if startInt > endInt {
|
||||
continue
|
||||
}
|
||||
tableRanges = append(tableRanges, TableRange{LowVal: startInt, HighVal: endInt})
|
||||
}
|
||||
return tableRanges
|
||||
}
|
193
vendor/github.com/pingcap/tidb/optimizer/plan/refiner.go
generated
vendored
Normal file
193
vendor/github.com/pingcap/tidb/optimizer/plan/refiner.go
generated
vendored
Normal file
|
@ -0,0 +1,193 @@
|
|||
// 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 (
|
||||
"math"
|
||||
|
||||
"github.com/pingcap/tidb/ast"
|
||||
"github.com/pingcap/tidb/model"
|
||||
"github.com/pingcap/tidb/parser/opcode"
|
||||
"github.com/pingcap/tidb/util/types"
|
||||
)
|
||||
|
||||
// Refine tries to build index or table range.
|
||||
func Refine(p Plan) error {
|
||||
r := refiner{}
|
||||
p.Accept(&r)
|
||||
return r.err
|
||||
}
|
||||
|
||||
type refiner struct {
|
||||
err error
|
||||
}
|
||||
|
||||
func (r *refiner) Enter(in Plan) (Plan, bool) {
|
||||
return in, false
|
||||
}
|
||||
|
||||
func (r *refiner) Leave(in Plan) (Plan, bool) {
|
||||
switch x := in.(type) {
|
||||
case *IndexScan:
|
||||
r.buildIndexRange(x)
|
||||
case *Limit:
|
||||
x.SetLimit(0)
|
||||
case *TableScan:
|
||||
r.buildTableRange(x)
|
||||
}
|
||||
return in, r.err == nil
|
||||
}
|
||||
|
||||
var fullRange = []rangePoint{
|
||||
{start: true},
|
||||
{value: types.MaxValueDatum()},
|
||||
}
|
||||
|
||||
func (r *refiner) buildIndexRange(p *IndexScan) {
|
||||
rb := rangeBuilder{}
|
||||
if p.AccessEqualCount > 0 {
|
||||
// Build ranges for equal access conditions.
|
||||
point := rb.build(p.AccessConditions[0])
|
||||
p.Ranges = rb.buildIndexRanges(point)
|
||||
for i := 1; i < p.AccessEqualCount; i++ {
|
||||
point = rb.build(p.AccessConditions[i])
|
||||
p.Ranges = rb.appendIndexRanges(p.Ranges, point)
|
||||
}
|
||||
}
|
||||
rangePoints := fullRange
|
||||
// Build rangePoints for non-equal access condtions.
|
||||
for i := p.AccessEqualCount; i < len(p.AccessConditions); i++ {
|
||||
rangePoints = rb.intersection(rangePoints, rb.build(p.AccessConditions[i]))
|
||||
}
|
||||
if p.AccessEqualCount == 0 {
|
||||
p.Ranges = rb.buildIndexRanges(rangePoints)
|
||||
} else if p.AccessEqualCount < len(p.AccessConditions) {
|
||||
p.Ranges = rb.appendIndexRanges(p.Ranges, rangePoints)
|
||||
}
|
||||
r.err = rb.err
|
||||
return
|
||||
}
|
||||
|
||||
func (r *refiner) buildTableRange(p *TableScan) {
|
||||
if len(p.AccessConditions) == 0 {
|
||||
p.Ranges = []TableRange{{math.MinInt64, math.MaxInt64}}
|
||||
return
|
||||
}
|
||||
rb := rangeBuilder{}
|
||||
rangePoints := fullRange
|
||||
for _, cond := range p.AccessConditions {
|
||||
rangePoints = rb.intersection(rangePoints, rb.build(cond))
|
||||
}
|
||||
p.Ranges = rb.buildTableRanges(rangePoints)
|
||||
r.err = rb.err
|
||||
}
|
||||
|
||||
// conditionChecker checks if this condition can be pushed to index plan.
|
||||
type conditionChecker struct {
|
||||
tableName model.CIStr
|
||||
idx *model.IndexInfo
|
||||
// the offset of the indexed column to be checked.
|
||||
columnOffset int
|
||||
pkName model.CIStr
|
||||
}
|
||||
|
||||
func (c *conditionChecker) check(condition ast.ExprNode) bool {
|
||||
switch x := condition.(type) {
|
||||
case *ast.BinaryOperationExpr:
|
||||
return c.checkBinaryOperation(x)
|
||||
case *ast.BetweenExpr:
|
||||
if ast.IsPreEvaluable(x.Left) && ast.IsPreEvaluable(x.Right) && c.checkColumnExpr(x.Expr) {
|
||||
return true
|
||||
}
|
||||
case *ast.ColumnNameExpr:
|
||||
return c.checkColumnExpr(x)
|
||||
case *ast.IsNullExpr:
|
||||
if c.checkColumnExpr(x.Expr) {
|
||||
return true
|
||||
}
|
||||
case *ast.IsTruthExpr:
|
||||
if c.checkColumnExpr(x.Expr) {
|
||||
return true
|
||||
}
|
||||
case *ast.ParenthesesExpr:
|
||||
return c.check(x.Expr)
|
||||
case *ast.PatternInExpr:
|
||||
if x.Sel != nil || x.Not {
|
||||
return false
|
||||
}
|
||||
if !c.checkColumnExpr(x.Expr) {
|
||||
return false
|
||||
}
|
||||
for _, val := range x.List {
|
||||
if !ast.IsPreEvaluable(val) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
case *ast.PatternLikeExpr:
|
||||
if x.Not {
|
||||
return false
|
||||
}
|
||||
if !c.checkColumnExpr(x.Expr) {
|
||||
return false
|
||||
}
|
||||
if !ast.IsPreEvaluable(x.Pattern) {
|
||||
return false
|
||||
}
|
||||
patternVal := x.Pattern.GetValue()
|
||||
if patternVal == nil {
|
||||
return false
|
||||
}
|
||||
patternStr, err := types.ToString(patternVal)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
firstChar := patternStr[0]
|
||||
return firstChar != '%' && firstChar != '.'
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *conditionChecker) checkBinaryOperation(b *ast.BinaryOperationExpr) bool {
|
||||
switch b.Op {
|
||||
case opcode.OrOr:
|
||||
return c.check(b.L) && c.check(b.R)
|
||||
case opcode.AndAnd:
|
||||
return c.check(b.L) && c.check(b.R)
|
||||
case opcode.EQ, opcode.NE, opcode.GE, opcode.GT, opcode.LE, opcode.LT:
|
||||
if ast.IsPreEvaluable(b.L) {
|
||||
return c.checkColumnExpr(b.R)
|
||||
} else if ast.IsPreEvaluable(b.R) {
|
||||
return c.checkColumnExpr(b.L)
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func (c *conditionChecker) checkColumnExpr(expr ast.ExprNode) bool {
|
||||
cn, ok := expr.(*ast.ColumnNameExpr)
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
if cn.Refer.Table.Name.L != c.tableName.L {
|
||||
return false
|
||||
}
|
||||
if c.pkName.L != "" {
|
||||
return c.pkName.L == cn.Refer.Column.Name.L
|
||||
}
|
||||
if c.idx != nil {
|
||||
return cn.Refer.Column.Name.L == c.idx.Columns[c.columnOffset].Name.L
|
||||
}
|
||||
return true
|
||||
}
|
89
vendor/github.com/pingcap/tidb/optimizer/plan/stringer.go
generated
vendored
Normal file
89
vendor/github.com/pingcap/tidb/optimizer/plan/stringer.go
generated
vendored
Normal file
|
@ -0,0 +1,89 @@
|
|||
// 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 (
|
||||
"fmt"
|
||||
"math"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// ToString explains a Plan, returns description string.
|
||||
func ToString(p Plan) string {
|
||||
var e stringer
|
||||
p.Accept(&e)
|
||||
return strings.Join(e.strs, "->")
|
||||
}
|
||||
|
||||
type stringer struct {
|
||||
strs []string
|
||||
idxs []int
|
||||
}
|
||||
|
||||
func (e *stringer) Enter(in Plan) (Plan, bool) {
|
||||
switch in.(type) {
|
||||
case *JoinOuter, *JoinInner:
|
||||
e.idxs = append(e.idxs, len(e.strs))
|
||||
}
|
||||
return in, false
|
||||
}
|
||||
|
||||
func (e *stringer) Leave(in Plan) (Plan, bool) {
|
||||
var str string
|
||||
switch x := in.(type) {
|
||||
case *CheckTable:
|
||||
str = "CheckTable"
|
||||
case *IndexScan:
|
||||
str = fmt.Sprintf("Index(%s.%s)", x.Table.Name.L, x.Index.Name.L)
|
||||
case *Limit:
|
||||
str = "Limit"
|
||||
case *SelectFields:
|
||||
str = "Fields"
|
||||
case *SelectLock:
|
||||
str = "Lock"
|
||||
case *ShowDDL:
|
||||
str = "ShowDDL"
|
||||
case *Sort:
|
||||
str = "Sort"
|
||||
case *TableScan:
|
||||
if len(x.Ranges) > 0 {
|
||||
ran := x.Ranges[0]
|
||||
if ran.LowVal != math.MinInt64 || ran.HighVal != math.MaxInt64 {
|
||||
str = fmt.Sprintf("Range(%s)", x.Table.Name.L)
|
||||
} else {
|
||||
str = fmt.Sprintf("Table(%s)", x.Table.Name.L)
|
||||
}
|
||||
} else {
|
||||
str = fmt.Sprintf("Table(%s)", x.Table.Name.L)
|
||||
}
|
||||
case *JoinOuter:
|
||||
last := len(e.idxs) - 1
|
||||
idx := e.idxs[last]
|
||||
chilrden := e.strs[idx:]
|
||||
e.strs = e.strs[:idx]
|
||||
str = "OuterJoin{" + strings.Join(chilrden, "->") + "}"
|
||||
e.idxs = e.idxs[:last]
|
||||
case *JoinInner:
|
||||
last := len(e.idxs) - 1
|
||||
idx := e.idxs[last]
|
||||
chilrden := e.strs[idx:]
|
||||
e.strs = e.strs[:idx]
|
||||
str = "InnerJoin{" + strings.Join(chilrden, "->") + "}"
|
||||
e.idxs = e.idxs[:last]
|
||||
default:
|
||||
str = fmt.Sprintf("%T", in)
|
||||
}
|
||||
e.strs = append(e.strs, str)
|
||||
return in, true
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue