vitess.io/vitess@v0.16.2/go/vt/vtgate/planbuilder/gen4_compare_v3_planner.go (about) 1 /* 2 Copyright 2021 The Vitess Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package planbuilder 18 19 import ( 20 "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" 21 22 "vitess.io/vitess/go/vt/sqlparser" 23 "vitess.io/vitess/go/vt/vtgate/engine" 24 ) 25 26 func gen4CompareV3Planner(query string) func(sqlparser.Statement, *sqlparser.ReservedVars, plancontext.VSchema) (*planResult, error) { 27 return func(statement sqlparser.Statement, vars *sqlparser.ReservedVars, ctxVSchema plancontext.VSchema) (*planResult, error) { 28 // we will be switching the planner version to Gen4 and V3 in order to 29 // create instructions using them, thus we make sure to switch back to 30 // the Gen4CompareV3 planner before exiting this method. 31 defer ctxVSchema.SetPlannerVersion(Gen4CompareV3) 32 switch statement.(type) { 33 case *sqlparser.Select, *sqlparser.Union: 34 // These we can compare. Everything else we'll just use the Gen4 planner 35 default: 36 return planWithPlannerVersion(statement, vars, ctxVSchema, query, Gen4) 37 } 38 39 // preliminary checks on the given statement 40 onlyGen4, hasOrderBy, err := preliminaryChecks(statement) 41 if err != nil { 42 return nil, err 43 } 44 45 // plan statement using Gen4 46 gen4Primitive, gen4Err := planWithPlannerVersion(statement, vars, ctxVSchema, query, Gen4) 47 48 // if onlyGen4 is set to true or Gen4's instruction contain a lock primitive, 49 // we use only Gen4's primitive and exit early without using V3's. 50 // since lock primitives can imply the creation or deletion of locks, 51 // we want to execute them once using Gen4 to avoid the duplicated locks 52 // or double lock-releases. 53 if onlyGen4 || (gen4Primitive != nil && hasLockPrimitive(gen4Primitive.primitive)) { 54 return gen4Primitive, gen4Err 55 } 56 57 // get V3's plan 58 v3Primitive, v3Err := planWithPlannerVersion(statement, vars, ctxVSchema, query, V3) 59 60 // check potential errors from Gen4 and V3 61 err = engine.CompareErrors(v3Err, gen4Err, "v3", "Gen4") 62 if err != nil { 63 return nil, err 64 } 65 66 primitive := &engine.Gen4CompareV3{ 67 V3: v3Primitive.primitive, 68 Gen4: gen4Primitive.primitive, 69 HasOrderBy: hasOrderBy, 70 } 71 72 return newPlanResult(primitive, gen4Primitive.tables...), nil 73 } 74 } 75 76 func preliminaryChecks(statement sqlparser.Statement) (bool, bool, error) { 77 var onlyGen4, hasOrderBy bool 78 switch s := statement.(type) { 79 case *sqlparser.Union: 80 hasOrderBy = len(s.OrderBy) > 0 81 82 // walk through the union and search for select statements that have 83 // a next val select expression, in which case we need to only use 84 // the Gen4 planner instead of using both Gen4 and V3 to avoid unintended 85 // double-incrementation of sequence. 86 err := sqlparser.Walk(func(node sqlparser.SQLNode) (kontinue bool, err error) { 87 if _, isNextVal := node.(*sqlparser.Nextval); isNextVal { 88 onlyGen4 = true 89 return false, nil 90 } 91 return true, nil 92 }, s) 93 if err != nil { 94 return false, false, err 95 } 96 case *sqlparser.Select: 97 hasOrderBy = len(s.OrderBy) > 0 98 99 for _, expr := range s.SelectExprs { 100 // we are not executing the plan a second time if the query is a select next val, 101 // since the first execution might increment the `next` value, results will almost 102 // always be different between v3 and Gen4. 103 if _, nextVal := expr.(*sqlparser.Nextval); nextVal { 104 onlyGen4 = true 105 break 106 } 107 } 108 } 109 return onlyGen4, hasOrderBy, nil 110 } 111 112 func planWithPlannerVersion(statement sqlparser.Statement, vars *sqlparser.ReservedVars, ctxVSchema plancontext.VSchema, query string, version plancontext.PlannerVersion) (*planResult, error) { 113 ctxVSchema.SetPlannerVersion(version) 114 stmt := sqlparser.CloneStatement(statement) 115 return createInstructionFor(query, stmt, vars, ctxVSchema, false, false) 116 } 117 118 // hasLockPrimitive recursively walks through the given primitive and its children 119 // to see if there are any engine.Lock primitive. 120 func hasLockPrimitive(primitive engine.Primitive) bool { 121 switch primitive.(type) { 122 case *engine.Lock: 123 return true 124 default: 125 for _, p := range primitive.Inputs() { 126 if hasLockPrimitive(p) { 127 return true 128 } 129 } 130 } 131 return false 132 }