mirror of
https://github.com/go-gitea/gitea
synced 2024-11-03 16:54:25 +00:00
796 lines
20 KiB
Go
796 lines
20 KiB
Go
|
// Copyright 2015 PingCAP, Inc.
|
||
|
//
|
||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||
|
// you may not use this file except in compliance with the License.
|
||
|
// You may obtain a copy of the License at
|
||
|
//
|
||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||
|
//
|
||
|
// Unless required by applicable law or agreed to in writing, software
|
||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||
|
// See the License for the specific language governing permissions and
|
||
|
// limitations under the License.
|
||
|
|
||
|
package plan
|
||
|
|
||
|
import (
|
||
|
"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
|
||
|
}
|