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  }