diff --git a/planner/cascades/expr_iterator.go b/planner/cascades/expr_iterator.go new file mode 100644 index 0000000000000..a93ab8924b100 --- /dev/null +++ b/planner/cascades/expr_iterator.go @@ -0,0 +1,176 @@ +// Copyright 2018 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 cascades + +import ( + "container/list" +) + +// ExprIter enumerates all the equivalent expressions in the group according to +// the expression pattern. +type ExprIter struct { + // group and element solely identify a group expression. + group *Group + element *list.Element + + // matched indicates whether the current group expression binded by the + // iterator matches the pattern after the creation or iteration. + matched bool + + // operand is the node of the pattern tree. The operand type of the group + // expression must be matched with it. + operand Operand + + // children is used to iterate the child expressions. + children []*ExprIter +} + +// Next returns the next group expression matches the pattern. +func (iter *ExprIter) Next() (found bool) { + defer func() { + iter.matched = found + }() + + // Iterate child firstly. + for i := len(iter.children) - 1; i >= 0; i-- { + if !iter.children[i].Next() { + continue + } + + for j := i + 1; j < len(iter.children); j++ { + iter.children[j].Reset() + } + return true + } + + // It's root node. + if iter.group == nil { + return false + } + + // Otherwise, iterate itself to find more matched equivalent expressions. + for elem := iter.element.Next(); elem != nil; elem = elem.Next() { + expr := elem.Value.(*GroupExpr) + exprOperand := GetOperand(expr.exprNode) + + if !iter.operand.match(exprOperand) { + // All the equivalents which have the same operand are continuously + // stored in the list. Once the current equivalent can not match + // the operand, the rest can not, either. + return false + } + + if len(iter.children) != len(expr.children) { + continue + } + + allMatched := true + for i := range iter.children { + iter.children[i].group = expr.children[i] + if !iter.children[i].Reset() { + allMatched = false + break + } + } + + if allMatched { + iter.element = elem + return true + } + } + return false +} + +// Matched returns whether the iterator founds a group expression matches the +// pattern. +func (iter *ExprIter) Matched() bool { + return iter.matched +} + +// Reset resets the iterator to the first matched group expression. +func (iter *ExprIter) Reset() (findMatch bool) { + defer func() { iter.matched = findMatch }() + + for elem := iter.group.GetFirstElem(iter.operand); elem != nil; elem = elem.Next() { + expr := elem.Value.(*GroupExpr) + exprOperand := GetOperand(expr.exprNode) + if !iter.operand.match(exprOperand) { + break + } + + if len(expr.children) != len(iter.children) { + continue + } + + allMatched := true + for i := range iter.children { + iter.children[i].group = expr.children[i] + if !iter.children[i].Reset() { + allMatched = false + break + } + } + if allMatched { + iter.element = elem + return true + } + } + return false +} + +// NewExprIterFromGroupElem creates the iterator on the group element. +func NewExprIterFromGroupElem(elem *list.Element, p *Pattern) *ExprIter { + expr := elem.Value.(*GroupExpr) + if !p.operand.match(GetOperand(expr.exprNode)) { + return nil + } + iter := newExprIterFromGroupExpr(expr, p) + if iter != nil { + iter.element = elem + } + return iter +} + +// newExprIterFromGroupExpr creates the iterator on the group expression. +func newExprIterFromGroupExpr(expr *GroupExpr, p *Pattern) *ExprIter { + if len(p.children) != len(expr.children) { + return nil + } + + iter := &ExprIter{operand: p.operand, matched: true} + for i := range p.children { + childIter := newExprIterFromGroup(expr.children[i], p.children[i]) + if childIter == nil { + return nil + } + iter.children = append(iter.children, childIter) + } + return iter +} + +// newExprIterFromGroup creates the iterator on the group. +func newExprIterFromGroup(g *Group, p *Pattern) *ExprIter { + for elem := g.GetFirstElem(p.operand); elem != nil; elem = elem.Next() { + expr := elem.Value.(*GroupExpr) + if !p.operand.match(GetOperand(expr.exprNode)) { + return nil + } + iter := newExprIterFromGroupExpr(expr, p) + if iter != nil { + iter.group, iter.element = g, elem + return iter + } + } + return nil +} diff --git a/planner/cascades/expr_iterator_test.go b/planner/cascades/expr_iterator_test.go new file mode 100644 index 0000000000000..1a43d69f127ad --- /dev/null +++ b/planner/cascades/expr_iterator_test.go @@ -0,0 +1,171 @@ +// Copyright 2018 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 cascades + +import ( + . "github.com/pingcap/check" + plannercore "github.com/pingcap/tidb/planner/core" +) + +func (s *testCascadesSuite) TestNewExprIterFromGroupElem(c *C) { + g0 := NewGroup(NewGroupExpr(plannercore.LogicalSelection{}.Init(s.sctx))) + g0.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + g0.Insert(NewGroupExpr(plannercore.LogicalProjection{}.Init(s.sctx))) + g0.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + + g1 := NewGroup(NewGroupExpr(plannercore.LogicalSelection{}.Init(s.sctx))) + g1.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + g1.Insert(NewGroupExpr(plannercore.LogicalProjection{}.Init(s.sctx))) + g1.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + + expr := NewGroupExpr(plannercore.LogicalJoin{}.Init(s.sctx)) + expr.children = append(expr.children, g0) + expr.children = append(expr.children, g1) + g2 := NewGroup(expr) + + pattern := BuildPattern(OperandJoin, BuildPattern(OperandProjection), BuildPattern(OperandSelection)) + iter := NewExprIterFromGroupElem(g2.equivalents.Front(), pattern) + + c.Assert(iter, NotNil) + c.Assert(iter.group, IsNil) + c.Assert(iter.element, Equals, g2.equivalents.Front()) + c.Assert(iter.matched, Equals, true) + c.Assert(iter.operand, Equals, OperandJoin) + c.Assert(len(iter.children), Equals, 2) + + c.Assert(iter.children[0].group, Equals, g0) + c.Assert(iter.children[0].element, Equals, g0.GetFirstElem(OperandProjection)) + c.Assert(iter.children[0].matched, Equals, true) + c.Assert(iter.children[0].operand, Equals, OperandProjection) + c.Assert(len(iter.children[0].children), Equals, 0) + + c.Assert(iter.children[1].group, Equals, g1) + c.Assert(iter.children[1].element, Equals, g1.GetFirstElem(OperandSelection)) + c.Assert(iter.children[1].matched, Equals, true) + c.Assert(iter.children[1].operand, Equals, OperandSelection) + c.Assert(len(iter.children[0].children), Equals, 0) +} + +func (s *testCascadesSuite) TestExprIterNext(c *C) { + g0 := NewGroup(NewGroupExpr(plannercore.LogicalProjection{}.Init(s.sctx))) + g0.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + g0.Insert(NewGroupExpr(plannercore.LogicalProjection{}.Init(s.sctx))) + g0.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + g0.Insert(NewGroupExpr(plannercore.LogicalProjection{}.Init(s.sctx))) + + g1 := NewGroup(NewGroupExpr(plannercore.LogicalSelection{}.Init(s.sctx))) + g1.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + g1.Insert(NewGroupExpr(plannercore.LogicalSelection{}.Init(s.sctx))) + g1.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + g1.Insert(NewGroupExpr(plannercore.LogicalSelection{}.Init(s.sctx))) + + expr := NewGroupExpr(plannercore.LogicalJoin{}.Init(s.sctx)) + expr.children = append(expr.children, g0) + expr.children = append(expr.children, g1) + g2 := NewGroup(expr) + + pattern := BuildPattern(OperandJoin, BuildPattern(OperandProjection), BuildPattern(OperandSelection)) + iter := NewExprIterFromGroupElem(g2.equivalents.Front(), pattern) + c.Assert(iter, NotNil) + + count := 0 + for ; iter.Matched(); iter.Next() { + count++ + c.Assert(iter.group, IsNil) + c.Assert(iter.matched, Equals, true) + c.Assert(iter.operand, Equals, OperandJoin) + c.Assert(len(iter.children), Equals, 2) + + c.Assert(iter.children[0].group, Equals, g0) + c.Assert(iter.children[0].matched, Equals, true) + c.Assert(iter.children[0].operand, Equals, OperandProjection) + c.Assert(len(iter.children[0].children), Equals, 0) + + c.Assert(iter.children[1].group, Equals, g1) + c.Assert(iter.children[1].matched, Equals, true) + c.Assert(iter.children[1].operand, Equals, OperandSelection) + c.Assert(len(iter.children[1].children), Equals, 0) + } + + c.Assert(count, Equals, 9) +} + +func (s *testCascadesSuite) TestExprIterReset(c *C) { + g0 := NewGroup(NewGroupExpr(plannercore.LogicalProjection{}.Init(s.sctx))) + g0.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + g0.Insert(NewGroupExpr(plannercore.LogicalProjection{}.Init(s.sctx))) + g0.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + g0.Insert(NewGroupExpr(plannercore.LogicalProjection{}.Init(s.sctx))) + + sel1 := NewGroupExpr(plannercore.LogicalSelection{}.Init(s.sctx)) + sel2 := NewGroupExpr(plannercore.LogicalSelection{}.Init(s.sctx)) + sel3 := NewGroupExpr(plannercore.LogicalSelection{}.Init(s.sctx)) + g1 := NewGroup(sel1) + g1.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + g1.Insert(sel2) + g1.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + g1.Insert(sel3) + + g2 := NewGroup(NewGroupExpr(plannercore.LogicalSelection{}.Init(s.sctx))) + g2.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + g2.Insert(NewGroupExpr(plannercore.LogicalSelection{}.Init(s.sctx))) + g2.Insert(NewGroupExpr(plannercore.LogicalLimit{}.Init(s.sctx))) + g2.Insert(NewGroupExpr(plannercore.LogicalSelection{}.Init(s.sctx))) + + // link join with group 0 and 1 + expr := NewGroupExpr(plannercore.LogicalJoin{}.Init(s.sctx)) + expr.children = append(expr.children, g0) + expr.children = append(expr.children, g1) + g3 := NewGroup(expr) + + // link sel 1~3 with group 2 + sel1.children = append(sel1.children, g2) + sel2.children = append(sel2.children, g2) + sel3.children = append(sel3.children, g2) + + // create a pattern: join(proj, sel(limit)) + lhsPattern := BuildPattern(OperandProjection) + rhsPattern := BuildPattern(OperandSelection, BuildPattern(OperandLimit)) + pattern := BuildPattern(OperandJoin, lhsPattern, rhsPattern) + + // create expression iterator for the pattern on join + iter := NewExprIterFromGroupElem(g3.equivalents.Front(), pattern) + c.Assert(iter, NotNil) + + count := 0 + for ; iter.Matched(); iter.Next() { + count++ + c.Assert(iter.group, IsNil) + c.Assert(iter.matched, Equals, true) + c.Assert(iter.operand, Equals, OperandJoin) + c.Assert(len(iter.children), Equals, 2) + + c.Assert(iter.children[0].group, Equals, g0) + c.Assert(iter.children[0].matched, Equals, true) + c.Assert(iter.children[0].operand, Equals, OperandProjection) + c.Assert(len(iter.children[0].children), Equals, 0) + + c.Assert(iter.children[1].group, Equals, g1) + c.Assert(iter.children[1].matched, Equals, true) + c.Assert(iter.children[1].operand, Equals, OperandSelection) + c.Assert(len(iter.children[1].children), Equals, 1) + + c.Assert(iter.children[1].children[0].group, Equals, g2) + c.Assert(iter.children[1].children[0].matched, Equals, true) + c.Assert(iter.children[1].children[0].operand, Equals, OperandLimit) + c.Assert(len(iter.children[1].children[0].children), Equals, 0) + } + + c.Assert(count, Equals, 18) +} diff --git a/planner/cascades/optimize.go b/planner/cascades/optimize.go index 49e76964ab9e2..d79218591770b 100644 --- a/planner/cascades/optimize.go +++ b/planner/cascades/optimize.go @@ -14,6 +14,8 @@ package cascades import ( + "container/list" + "github.com/pingcap/errors" plannercore "github.com/pingcap/tidb/planner/core" "github.com/pingcap/tidb/sessionctx" @@ -45,7 +47,82 @@ func convert2Group(node plannercore.LogicalPlan) *Group { } func onPhaseExploration(sctx sessionctx.Context, g *Group) error { - return errors.New("the onPhaseExploration() of the cascades planner is not implemented") + for !g.explored { + err := exploreGroup(g) + if err != nil { + return err + } + } + return nil +} + +func exploreGroup(g *Group) error { + if g.explored { + return nil + } + + g.explored = true + for elem := g.equivalents.Front(); elem != nil; elem.Next() { + curExpr := elem.Value.(*GroupExpr) + if curExpr.explored { + continue + } + + // Explore child groups firstly. + curExpr.explored = true + for _, childGroup := range curExpr.children { + exploreGroup(childGroup) + curExpr.explored = curExpr.explored && childGroup.explored + } + + eraseCur, err := findMoreEquiv(g, elem) + if err != nil { + return err + } + if eraseCur { + g.Delete(curExpr) + } + + g.explored = g.explored && curExpr.explored + } + return nil +} + +// findMoreEquiv finds and applies the matched transformation rules. +func findMoreEquiv(g *Group, elem *list.Element) (eraseCur bool, err error) { + expr := elem.Value.(*GroupExpr) + for _, rule := range GetTransformationRules(expr.exprNode) { + pattern := rule.GetPattern() + if !pattern.operand.match(GetOperand(expr.exprNode)) { + continue + } + // Create a binding of the current group expression and the pattern of + // the transformation rule to enumerate all the possible expressions. + iter := NewExprIterFromGroupElem(elem, pattern) + for ; iter != nil && iter.Matched(); iter.Next() { + if !rule.Match(iter) { + continue + } + + newExpr, erase, err := rule.OnTransform(iter) + if err != nil { + return false, err + } + + eraseCur = eraseCur || erase + if !g.Insert(newExpr) { + continue + } + + // If the new group expression is successfully inserted into the + // current group, we mark the group expression and the group as + // unexplored to enable the exploration on the new group expression + // and all the antecedent groups. + newExpr.explored = false + g.explored = false + } + } + return eraseCur, nil } func onPhaseImplementation(sctx sessionctx.Context, g *Group) (plannercore.Plan, error) { diff --git a/planner/cascades/transformation_rules.go b/planner/cascades/transformation_rules.go new file mode 100644 index 0000000000000..5d4e0cfa97e90 --- /dev/null +++ b/planner/cascades/transformation_rules.go @@ -0,0 +1,42 @@ +// Copyright 2018 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 cascades + +import ( + plannercore "github.com/pingcap/tidb/planner/core" +) + +// Transformation defines the interface for the transformation rules. +type Transformation interface { + GetPattern() *Pattern + Match(expr *ExprIter) (matched bool) + OnTransform(old *ExprIter) (new *GroupExpr, eraseOld bool, err error) +} + +// GetTransformationRules gets the all the candidate transformation rules based +// on the logical plan node. +func GetTransformationRules(node plannercore.LogicalPlan) []Transformation { + return transformationMap[GetOperand(node)] +} + +var transformationMap = map[Operand][]Transformation{ + /** + operandSelect: []Transformation{ + nil, + }, + operandProject: []Transformation{ + nil, + }, + */ +}