vitess.io/vitess@v0.16.2/go/cmd/vtexplain/vtexplain.go (about) 1 /* 2 Copyright 2019 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 main 18 19 import ( 20 "fmt" 21 "os" 22 23 "vitess.io/vitess/go/acl" 24 "vitess.io/vitess/go/exit" 25 "vitess.io/vitess/go/vt/log" 26 "vitess.io/vitess/go/vt/logutil" 27 "vitess.io/vitess/go/vt/servenv" 28 "vitess.io/vitess/go/vt/vtexplain" 29 "vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext" 30 31 "github.com/spf13/pflag" 32 33 querypb "vitess.io/vitess/go/vt/proto/query" 34 ) 35 36 var ( 37 sqlFlag string 38 sqlFileFlag string 39 schemaFlag string 40 schemaFileFlag string 41 vschemaFlag string 42 vschemaFileFlag string 43 ksShardMapFlag string 44 ksShardMapFileFlag string 45 normalize bool 46 dbName string 47 plannerVersionStr string 48 49 numShards = 2 50 replicationMode = "ROW" 51 executionMode = "multi" 52 outputMode = "text" 53 ) 54 55 func registerFlags(fs *pflag.FlagSet) { 56 fs.StringVar(&sqlFlag, "sql", sqlFlag, "A list of semicolon-delimited SQL commands to analyze") 57 fs.StringVar(&sqlFileFlag, "sql-file", sqlFileFlag, "Identifies the file that contains the SQL commands to analyze") 58 fs.StringVar(&schemaFlag, "schema", schemaFlag, "The SQL table schema") 59 fs.StringVar(&schemaFileFlag, "schema-file", schemaFileFlag, "Identifies the file that contains the SQL table schema") 60 fs.StringVar(&vschemaFlag, "vschema", vschemaFlag, "Identifies the VTGate routing schema") 61 fs.StringVar(&vschemaFileFlag, "vschema-file", vschemaFileFlag, "Identifies the VTGate routing schema file") 62 fs.StringVar(&ksShardMapFlag, "ks-shard-map", ksShardMapFlag, "JSON map of keyspace name -> shard name -> ShardReference object. The inner map is the same as the output of FindAllShardsInKeyspace") 63 fs.StringVar(&ksShardMapFileFlag, "ks-shard-map-file", ksShardMapFileFlag, "File containing json blob of keyspace name -> shard name -> ShardReference object") 64 fs.StringVar(&replicationMode, "replication-mode", replicationMode, "The replication mode to simulate -- must be set to either ROW or STATEMENT") 65 fs.BoolVar(&normalize, "normalize", normalize, "Whether to enable vtgate normalization") 66 fs.StringVar(&dbName, "dbname", dbName, "Optional database target to override normal routing") 67 fs.StringVar(&plannerVersionStr, "planner-version", plannerVersionStr, "Sets the query planner version to use when generating the explain output. Valid values are V3 and Gen4. An empty value will use VTGate's default planner") 68 fs.IntVar(&numShards, "shards", numShards, "Number of shards per keyspace. Passing --ks-shard-map/--ks-shard-map-file causes this flag to be ignored.") 69 fs.StringVar(&executionMode, "execution-mode", executionMode, "The execution mode to simulate -- must be set to multi, legacy-autocommit, or twopc") 70 fs.StringVar(&outputMode, "output-mode", outputMode, "Output in human-friendly text or json") 71 72 acl.RegisterFlags(fs) 73 } 74 75 func init() { 76 servenv.OnParse(registerFlags) 77 } 78 79 // getFileParam returns a string containing either flag is not "", 80 // or the content of the file named flagFile 81 func getFileParam(flag, flagFile, name string, required bool) (string, error) { 82 if flag != "" { 83 if flagFile != "" { 84 return "", fmt.Errorf("action requires only one of %v or %v-file", name, name) 85 } 86 return flag, nil 87 } 88 89 if flagFile == "" { 90 if required { 91 return "", fmt.Errorf("action requires one of %v or %v-file", name, name) 92 } 93 94 return "", nil 95 } 96 data, err := os.ReadFile(flagFile) 97 if err != nil { 98 return "", fmt.Errorf("cannot read file %v: %v", flagFile, err) 99 } 100 return string(data), nil 101 } 102 103 func main() { 104 defer exit.RecoverAll() 105 defer logutil.Flush() 106 107 servenv.ParseFlags("vtexplain") 108 err := parseAndRun() 109 if err != nil { 110 fmt.Printf("ERROR: %s\n", err) 111 exit.Return(1) 112 } 113 } 114 115 func parseAndRun() error { 116 plannerVersion, _ := plancontext.PlannerNameToVersion(plannerVersionStr) 117 if plannerVersionStr != "" && plannerVersion != querypb.ExecuteOptions_V3 && plannerVersion != querypb.ExecuteOptions_Gen4 { 118 return fmt.Errorf("invalid value specified for planner-version of '%s' -- valid values are V3 and Gen4 or an empty value to use the default planner", plannerVersionStr) 119 } 120 121 sql, err := getFileParam(sqlFlag, sqlFileFlag, "sql", true) 122 if err != nil { 123 return err 124 } 125 126 schema, err := getFileParam(schemaFlag, schemaFileFlag, "schema", true) 127 if err != nil { 128 return err 129 } 130 131 vschema, err := getFileParam(vschemaFlag, vschemaFileFlag, "vschema", true) 132 if err != nil { 133 return err 134 } 135 136 ksShardMap, err := getFileParam(ksShardMapFlag, ksShardMapFileFlag, "ks-shard-map", false) 137 if err != nil { 138 return err 139 } 140 141 opts := &vtexplain.Options{ 142 ExecutionMode: executionMode, 143 PlannerVersion: plannerVersion, 144 ReplicationMode: replicationMode, 145 NumShards: numShards, 146 Normalize: normalize, 147 Target: dbName, 148 } 149 150 log.V(100).Infof("sql %s\n", sql) 151 log.V(100).Infof("schema %s\n", schema) 152 log.V(100).Infof("vschema %s\n", vschema) 153 154 vte, err := vtexplain.Init(vschema, schema, ksShardMap, opts) 155 if err != nil { 156 return err 157 } 158 defer vte.Stop() 159 160 plans, err := vte.Run(sql) 161 if err != nil { 162 return err 163 } 164 165 if outputMode == "text" { 166 fmt.Print(vte.ExplainsAsText(plans)) 167 } else { 168 fmt.Print(vtexplain.ExplainsAsJSON(plans)) 169 } 170 171 return nil 172 }