github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/sql/opt/testutils/opttester/opt_steps.go (about) 1 // Copyright 2018 The Cockroach Authors. 2 // 3 // Use of this software is governed by the Business Source License 4 // included in the file licenses/BSL.txt. 5 // 6 // As of the Change Date specified in that file, in accordance with 7 // the Business Source License, use of this software will be governed 8 // by the Apache License, Version 2.0, included in the file 9 // licenses/APL.txt. 10 11 package opttester 12 13 import ( 14 "github.com/cockroachdb/cockroach/pkg/sql/opt" 15 "github.com/cockroachdb/cockroach/pkg/sql/opt/memo" 16 ) 17 18 // optSteps implements the stepping algorithm used by the OptTester's OptSteps 19 // command. See the OptTester.OptSteps comment for more details on the command. 20 // 21 // The algorithm works as follows: 22 // 1. The first time optSteps.next() is called, optSteps returns the starting 23 // expression tree, with no transformations applied to it. 24 // 25 // 2. Each optSteps.next() call after that will perform N+1 transformations, 26 // where N is the the number of steps performed during the previous call 27 // (starting at 0 with the first call). 28 // 29 // 3. Each optSteps.next() call will build the expression tree from scratch 30 // and re-run all transformations that were run in the previous call, plus 31 // one additional transformation (N+1). Therefore, the output expression 32 // tree from each call will differ from the previous call only by the last 33 // transformation's changes. 34 // 35 // 4. optSteps hooks the optimizer's MatchedRule event in order to limit the 36 // number of transformations that can be applied, as well as to record the 37 // name of the last rule that was applied, for later output. 38 // 39 // 5. While this works well for normalization rules, exploration rules are 40 // more difficult. This is because exploration rules are not guaranteed to 41 // produce a lower cost tree. Unless extra measures are taken, the returned 42 // Expr would not include the changed portion of the Memo, since Expr only 43 // shows the lowest cost path through the Memo. 44 // 45 // 6. To address this issue, optSteps hooks the optimizer's AppliedRule event 46 // and records the expression(s) that the last transformation has affected. 47 // It then re-runs the optimizer, but this time using a special Coster 48 // implementation that fools the optimizer into thinking that the new 49 // expression(s) have the lowest cost. The coster does this by assigning an 50 // infinite cost to all other expressions in the same group as the new 51 // expression(s), as well as in all ancestor groups. 52 // 53 type optSteps struct { 54 tester *OptTester 55 56 fo *forcingOptimizer 57 58 // steps is the maximum number of rules that can be applied by the optimizer 59 // during the current iteration. 60 steps int 61 62 // expr is the expression tree produced by the most recent optSteps iteration. 63 expr opt.Expr 64 65 // better is true if expr is lower cost than the expression tree produced by 66 // the previous iteration of optSteps. 67 better bool 68 69 // best is the textual representation of the most recent expression tree that 70 // was an improvement over the previous best tree. 71 best string 72 } 73 74 func newOptSteps(tester *OptTester) *optSteps { 75 return &optSteps{tester: tester} 76 } 77 78 // Root returns the node tree produced by the most recent optSteps iteration. 79 func (os *optSteps) Root() opt.Expr { 80 return os.expr 81 } 82 83 // LastRuleName returns the name of the rule that was most recently matched by 84 // the optimizer. 85 func (os *optSteps) LastRuleName() opt.RuleName { 86 return os.fo.lastMatched 87 } 88 89 // IsBetter returns true if root is lower cost than the expression tree 90 // produced by the previous iteration of optSteps. 91 func (os *optSteps) IsBetter() bool { 92 return os.better 93 } 94 95 // Done returns true if there are no more rules to apply. Further calls to the 96 // next method will result in a panic. 97 func (os *optSteps) Done() bool { 98 // remaining starts out equal to steps, and is decremented each time a rule 99 // is applied. If it never reaches zero, then all possible rules were 100 // already applied, and optimization is complete. 101 return os.fo != nil && os.fo.remaining != 0 102 } 103 104 // Next triggers the next iteration of optSteps. If there is no error, then 105 // results of the iteration can be accessed via the Root, LastRuleName, and 106 // IsBetter methods. 107 func (os *optSteps) Next() error { 108 if os.Done() { 109 panic("iteration already complete") 110 } 111 112 fo, err := newForcingOptimizer(os.tester, os.steps, false /* ignoreNormRules */) 113 if err != nil { 114 return err 115 } 116 117 os.fo = fo 118 os.expr = fo.Optimize() 119 text := os.expr.String() 120 121 // If the expression text changes, then it must have gotten better. 122 os.better = text != os.best 123 if os.better { 124 os.best = text 125 } else if !os.Done() { 126 // The expression is not better, so suppress the lowest cost expressions 127 // so that the changed portions of the tree will be part of the output. 128 fo2, err := newForcingOptimizer(os.tester, os.steps, false /* ignoreNormRules */) 129 if err != nil { 130 return err 131 } 132 133 if fo.lastAppliedSource == nil { 134 // This was a normalization that created a new memo group. 135 fo2.RestrictToExpr(fo.LookupPath(fo.lastAppliedTarget)) 136 } else if fo.lastAppliedTarget == nil { 137 // This was an exploration rule that didn't add any expressions to the 138 // group, so only ancestor groups need to be suppressed. 139 path := fo.LookupPath(fo.lastAppliedSource) 140 fo2.RestrictToExpr(path[:len(path)-1]) 141 } else { 142 // This was an exploration rule that added one or more expressions to 143 // an existing group. Suppress all other members of the group. 144 member := fo.lastAppliedTarget.(memo.RelExpr) 145 for member != nil { 146 fo2.RestrictToExpr(fo.LookupPath(member)) 147 member = member.NextExpr() 148 } 149 } 150 os.expr = fo2.Optimize() 151 } 152 153 os.steps++ 154 return nil 155 }