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  }