vitess.io/vitess@v0.16.2/go/vt/vtgate/vtgate.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 vtgate provides query routing rpc services
    18  // for vttablets.
    19  package vtgate
    20  
    21  import (
    22  	"context"
    23  	"errors"
    24  	"fmt"
    25  	"net/http"
    26  	"os"
    27  	"regexp"
    28  	"strings"
    29  	"time"
    30  
    31  	"github.com/spf13/pflag"
    32  
    33  	"vitess.io/vitess/go/acl"
    34  	"vitess.io/vitess/go/cache"
    35  	"vitess.io/vitess/go/sqltypes"
    36  	"vitess.io/vitess/go/stats"
    37  	"vitess.io/vitess/go/tb"
    38  	"vitess.io/vitess/go/vt/discovery"
    39  	"vitess.io/vitess/go/vt/key"
    40  	"vitess.io/vitess/go/vt/log"
    41  	"vitess.io/vitess/go/vt/logutil"
    42  	"vitess.io/vitess/go/vt/schema"
    43  	"vitess.io/vitess/go/vt/servenv"
    44  	"vitess.io/vitess/go/vt/sqlparser"
    45  	"vitess.io/vitess/go/vt/srvtopo"
    46  	"vitess.io/vitess/go/vt/topo/topoproto"
    47  	"vitess.io/vitess/go/vt/vterrors"
    48  	"vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext"
    49  	"vitess.io/vitess/go/vt/vtgate/vtgateservice"
    50  
    51  	binlogdatapb "vitess.io/vitess/go/vt/proto/binlogdata"
    52  	querypb "vitess.io/vitess/go/vt/proto/query"
    53  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    54  	vtgatepb "vitess.io/vitess/go/vt/proto/vtgate"
    55  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    56  	vtschema "vitess.io/vitess/go/vt/vtgate/schema"
    57  )
    58  
    59  var (
    60  	transactionMode  = "MULTI"
    61  	normalizeQueries = true
    62  	streamBufferSize = 32 * 1024
    63  
    64  	terseErrors bool
    65  
    66  	// plan cache related flag
    67  	queryPlanCacheSize   = cache.DefaultConfig.MaxEntries
    68  	queryPlanCacheMemory = cache.DefaultConfig.MaxMemoryUsage
    69  	queryPlanCacheLFU    bool
    70  
    71  	maxMemoryRows   = 300000
    72  	warnMemoryRows  = 30000
    73  	maxPayloadSize  int
    74  	warnPayloadSize int
    75  
    76  	noScatter          bool
    77  	enableShardRouting bool
    78  
    79  	// TODO(deepthi): change these two vars to unexported and move to healthcheck.go when LegacyHealthcheck is removed
    80  
    81  	// healthCheckRetryDelay is the time to wait before retrying healthcheck
    82  	healthCheckRetryDelay = 2 * time.Millisecond
    83  	// healthCheckTimeout is the timeout on the RPC call to tablets
    84  	healthCheckTimeout = time.Minute
    85  
    86  	// System settings related flags
    87  	sysVarSetEnabled = true
    88  	setVarEnabled    = true
    89  
    90  	// lockHeartbeatTime is used to set the next heartbeat time.
    91  	lockHeartbeatTime = 5 * time.Second
    92  	warnShardedOnly   bool
    93  
    94  	// ddl related flags
    95  	foreignKeyMode     = "allow"
    96  	dbDDLPlugin        = "fail"
    97  	defaultDDLStrategy = string(schema.DDLStrategyDirect)
    98  	enableOnlineDDL    = true
    99  	enableDirectDDL    = true
   100  
   101  	// vtgate schema tracking flags
   102  	enableSchemaChangeSignal = true
   103  	schemaChangeUser         string
   104  	queryTimeout             int
   105  
   106  	// vtgate views flags
   107  	enableViews bool
   108  
   109  	// queryLogToFile controls whether query logs are sent to a file
   110  	queryLogToFile string
   111  	// queryLogBufferSize controls how many query logs will be buffered before dropping them if logging is not fast enough
   112  	queryLogBufferSize = 10
   113  
   114  	messageStreamGracePeriod = 30 * time.Second
   115  )
   116  
   117  func registerFlags(fs *pflag.FlagSet) {
   118  	fs.StringVar(&transactionMode, "transaction_mode", transactionMode, "SINGLE: disallow multi-db transactions, MULTI: allow multi-db transactions with best effort commit, TWOPC: allow multi-db transactions with 2pc commit")
   119  	fs.BoolVar(&normalizeQueries, "normalize_queries", normalizeQueries, "Rewrite queries with bind vars. Turn this off if the app itself sends normalized queries with bind vars.")
   120  	fs.BoolVar(&terseErrors, "vtgate-config-terse-errors", terseErrors, "prevent bind vars from escaping in returned errors")
   121  	fs.IntVar(&streamBufferSize, "stream_buffer_size", streamBufferSize, "the number of bytes sent from vtgate for each stream call. It's recommended to keep this value in sync with vttablet's query-server-config-stream-buffer-size.")
   122  	fs.Int64Var(&queryPlanCacheSize, "gate_query_cache_size", queryPlanCacheSize, "gate server query cache size, maximum number of queries to be cached. vtgate analyzes every incoming query and generate a query plan, these plans are being cached in a cache. This config controls the expected amount of unique entries in the cache.")
   123  	fs.Int64Var(&queryPlanCacheMemory, "gate_query_cache_memory", queryPlanCacheMemory, "gate server query cache size in bytes, maximum amount of memory to be cached. vtgate analyzes every incoming query and generate a query plan, these plans are being cached in a lru cache. This config controls the capacity of the lru cache.")
   124  	fs.BoolVar(&queryPlanCacheLFU, "gate_query_cache_lfu", cache.DefaultConfig.LFU, "gate server cache algorithm. when set to true, a new cache algorithm based on a TinyLFU admission policy will be used to improve cache behavior and prevent pollution from sparse queries")
   125  	fs.IntVar(&maxMemoryRows, "max_memory_rows", maxMemoryRows, "Maximum number of rows that will be held in memory for intermediate results as well as the final result.")
   126  	fs.IntVar(&warnMemoryRows, "warn_memory_rows", warnMemoryRows, "Warning threshold for in-memory results. A row count higher than this amount will cause the VtGateWarnings.ResultsExceeded counter to be incremented.")
   127  	fs.StringVar(&defaultDDLStrategy, "ddl_strategy", defaultDDLStrategy, "Set default strategy for DDL statements. Override with @@ddl_strategy session variable")
   128  	fs.StringVar(&dbDDLPlugin, "dbddl_plugin", dbDDLPlugin, "controls how to handle CREATE/DROP DATABASE. use it if you are using your own database provisioning service")
   129  	fs.BoolVar(&noScatter, "no_scatter", noScatter, "when set to true, the planner will fail instead of producing a plan that includes scatter queries")
   130  	fs.BoolVar(&enableShardRouting, "enable-partial-keyspace-migration", enableShardRouting, "(Experimental) Follow shard routing rules: enable only while migrating a keyspace shard by shard. See documentation on Partial MoveTables for more. (default false)")
   131  	fs.DurationVar(&healthCheckRetryDelay, "healthcheck_retry_delay", healthCheckRetryDelay, "health check retry delay")
   132  	fs.DurationVar(&healthCheckTimeout, "healthcheck_timeout", healthCheckTimeout, "the health check timeout period")
   133  	fs.IntVar(&maxPayloadSize, "max_payload_size", maxPayloadSize, "The threshold for query payloads in bytes. A payload greater than this threshold will result in a failure to handle the query.")
   134  	fs.IntVar(&warnPayloadSize, "warn_payload_size", warnPayloadSize, "The warning threshold for query payloads in bytes. A payload greater than this threshold will cause the VtGateWarnings.WarnPayloadSizeExceeded counter to be incremented.")
   135  	fs.BoolVar(&sysVarSetEnabled, "enable_system_settings", sysVarSetEnabled, "This will enable the system settings to be changed per session at the database connection level")
   136  	fs.BoolVar(&setVarEnabled, "enable_set_var", setVarEnabled, "This will enable the use of MySQL's SET_VAR query hint for certain system variables instead of using reserved connections")
   137  	fs.DurationVar(&lockHeartbeatTime, "lock_heartbeat_time", lockHeartbeatTime, "If there is lock function used. This will keep the lock connection active by using this heartbeat")
   138  	fs.BoolVar(&warnShardedOnly, "warn_sharded_only", warnShardedOnly, "If any features that are only available in unsharded mode are used, query execution warnings will be added to the session")
   139  	fs.StringVar(&foreignKeyMode, "foreign_key_mode", foreignKeyMode, "This is to provide how to handle foreign key constraint in create/alter table. Valid values are: allow, disallow")
   140  	fs.BoolVar(&enableOnlineDDL, "enable_online_ddl", enableOnlineDDL, "Allow users to submit, review and control Online DDL")
   141  	fs.BoolVar(&enableDirectDDL, "enable_direct_ddl", enableDirectDDL, "Allow users to submit direct DDL statements")
   142  	fs.BoolVar(&enableSchemaChangeSignal, "schema_change_signal", enableSchemaChangeSignal, "Enable the schema tracker; requires queryserver-config-schema-change-signal to be enabled on the underlying vttablets for this to work")
   143  	fs.StringVar(&schemaChangeUser, "schema_change_signal_user", schemaChangeUser, "User to be used to send down query to vttablet to retrieve schema changes")
   144  	fs.IntVar(&queryTimeout, "query-timeout", queryTimeout, "Sets the default query timeout (in ms). Can be overridden by session variable (query_timeout) or comment directive (QUERY_TIMEOUT_MS)")
   145  	fs.StringVar(&queryLogToFile, "log_queries_to_file", queryLogToFile, "Enable query logging to the specified file")
   146  	fs.IntVar(&queryLogBufferSize, "querylog-buffer-size", queryLogBufferSize, "Maximum number of buffered query logs before throttling log output")
   147  	fs.DurationVar(&messageStreamGracePeriod, "message_stream_grace_period", messageStreamGracePeriod, "the amount of time to give for a vttablet to resume if it ends a message stream, usually because of a reparent.")
   148  	fs.BoolVar(&enableViews, "enable-views", enableViews, "Enable views support in vtgate.")
   149  }
   150  func init() {
   151  	servenv.OnParseFor("vtgate", registerFlags)
   152  	servenv.OnParseFor("vtcombo", registerFlags)
   153  }
   154  
   155  func getTxMode() vtgatepb.TransactionMode {
   156  	switch strings.ToLower(transactionMode) {
   157  	case "single":
   158  		log.Infof("Transaction mode: '%s'", transactionMode)
   159  		return vtgatepb.TransactionMode_SINGLE
   160  	case "multi":
   161  		log.Infof("Transaction mode: '%s'", transactionMode)
   162  		return vtgatepb.TransactionMode_MULTI
   163  	case "twopc":
   164  		log.Infof("Transaction mode: '%s'", transactionMode)
   165  		return vtgatepb.TransactionMode_TWOPC
   166  	default:
   167  		fmt.Printf("Invalid option: %v\n", transactionMode)
   168  		fmt.Println("Usage: -transaction_mode {SINGLE | MULTI | TWOPC}")
   169  		os.Exit(1)
   170  		return -1
   171  	}
   172  }
   173  
   174  var (
   175  	rpcVTGate *VTGate
   176  
   177  	// vschemaCounters needs to be initialized before planner to
   178  	// catch the initial load stats.
   179  	vschemaCounters = stats.NewCountersWithSingleLabel("VtgateVSchemaCounts", "Vtgate vschema counts", "changes")
   180  
   181  	// Error counters should be global so they can be set from anywhere
   182  	errorCounts = stats.NewCountersWithMultiLabels("VtgateApiErrorCounts", "Vtgate API error counts per error type", []string{"Operation", "Keyspace", "DbType", "Code"})
   183  
   184  	warnings = stats.NewCountersWithSingleLabel("VtGateWarnings", "Vtgate warnings", "type", "IgnoredSet", "ResultsExceeded", "WarnPayloadSizeExceeded")
   185  
   186  	vstreamSkewDelayCount = stats.NewCounter("VStreamEventsDelayedBySkewAlignment",
   187  		"Number of events that had to wait because the skew across shards was too high")
   188  )
   189  
   190  // VTGate is the rpc interface to vtgate. Only one instance
   191  // can be created. It implements vtgateservice.VTGateService
   192  // VTGate exposes multiple generations of interfaces.
   193  type VTGate struct {
   194  	// Dependency: executor->resolver->scatterConn->txConn->gateway.
   195  	executor *Executor
   196  	resolver *Resolver
   197  	vsm      *vstreamManager
   198  	txConn   *TxConn
   199  	gw       *TabletGateway
   200  
   201  	// stats objects.
   202  	// TODO(sougou): This needs to be cleaned up. There
   203  	// are global vars that depend on this member var.
   204  	timings      *stats.MultiTimings
   205  	rowsReturned *stats.CountersWithMultiLabels
   206  	rowsAffected *stats.CountersWithMultiLabels
   207  
   208  	// the throttled loggers for all errors, one per API entry
   209  	logExecute       *logutil.ThrottledLogger
   210  	logStreamExecute *logutil.ThrottledLogger
   211  }
   212  
   213  // RegisterVTGate defines the type of registration mechanism.
   214  type RegisterVTGate func(vtgateservice.VTGateService)
   215  
   216  // RegisterVTGates stores register funcs for VTGate server.
   217  var RegisterVTGates []RegisterVTGate
   218  
   219  // Init initializes VTGate server.
   220  func Init(
   221  	ctx context.Context,
   222  	hc discovery.HealthCheck,
   223  	serv srvtopo.Server,
   224  	cell string,
   225  	tabletTypesToWait []topodatapb.TabletType,
   226  	pv plancontext.PlannerVersion,
   227  ) *VTGate {
   228  	if rpcVTGate != nil {
   229  		log.Fatalf("VTGate already initialized")
   230  	}
   231  
   232  	// Build objects from low to high level.
   233  	// Start with the gateway. If we can't reach the topology service,
   234  	// we can't go on much further, so we log.Fatal out.
   235  	// TabletGateway can create it's own healthcheck
   236  	gw := NewTabletGateway(ctx, hc, serv, cell)
   237  	gw.RegisterStats()
   238  	if err := gw.WaitForTablets(tabletTypesToWait); err != nil {
   239  		log.Fatalf("tabletGateway.WaitForTablets failed: %v", err)
   240  	}
   241  
   242  	// If we want to filter keyspaces replace the srvtopo.Server with a
   243  	// filtering server
   244  	if discovery.FilteringKeyspaces() {
   245  		log.Infof("Keyspace filtering enabled, selecting %v", discovery.KeyspacesToWatch)
   246  		var err error
   247  		serv, err = srvtopo.NewKeyspaceFilteringServer(serv, discovery.KeyspacesToWatch)
   248  		if err != nil {
   249  			log.Fatalf("Unable to construct SrvTopo server: %v", err.Error())
   250  		}
   251  	}
   252  
   253  	if _, err := schema.ParseDDLStrategy(defaultDDLStrategy); err != nil {
   254  		log.Fatalf("Invalid value for -ddl_strategy: %v", err.Error())
   255  	}
   256  	tc := NewTxConn(gw, getTxMode())
   257  	// ScatterConn depends on TxConn to perform forced rollbacks.
   258  	sc := NewScatterConn("VttabletCall", tc, gw)
   259  	srvResolver := srvtopo.NewResolver(serv, gw, cell)
   260  	resolver := NewResolver(srvResolver, serv, cell, sc)
   261  	vsm := newVStreamManager(srvResolver, serv, cell)
   262  
   263  	var si SchemaInfo // default nil
   264  	var st *vtschema.Tracker
   265  	if enableSchemaChangeSignal {
   266  		st = vtschema.NewTracker(gw.hc.Subscribe(), schemaChangeUser, enableViews)
   267  		addKeyspaceToTracker(ctx, srvResolver, st, gw)
   268  		si = st
   269  	}
   270  
   271  	cacheCfg := &cache.Config{
   272  		MaxEntries:     queryPlanCacheSize,
   273  		MaxMemoryUsage: queryPlanCacheMemory,
   274  		LFU:            queryPlanCacheLFU,
   275  	}
   276  
   277  	executor := NewExecutor(
   278  		ctx,
   279  		serv,
   280  		cell,
   281  		resolver,
   282  		normalizeQueries,
   283  		warnShardedOnly,
   284  		streamBufferSize,
   285  		cacheCfg,
   286  		si,
   287  		noScatter,
   288  		pv,
   289  	)
   290  
   291  	// connect the schema tracker with the vschema manager
   292  	if enableSchemaChangeSignal {
   293  		st.RegisterSignalReceiver(executor.vm.Rebuild)
   294  	}
   295  
   296  	// TODO: call serv.WatchSrvVSchema here
   297  
   298  	rpcVTGate = &VTGate{
   299  		executor: executor,
   300  		resolver: resolver,
   301  		vsm:      vsm,
   302  		txConn:   tc,
   303  		gw:       gw,
   304  		timings: stats.NewMultiTimings(
   305  			"VtgateApi",
   306  			"VtgateApi timings",
   307  			[]string{"Operation", "Keyspace", "DbType"}),
   308  		rowsReturned: stats.NewCountersWithMultiLabels(
   309  			"VtgateApiRowsReturned",
   310  			"Rows returned through the VTgate API",
   311  			[]string{"Operation", "Keyspace", "DbType"}),
   312  		rowsAffected: stats.NewCountersWithMultiLabels(
   313  			"VtgateApiRowsAffected",
   314  			"Rows affected by a write (DML) operation through the VTgate API",
   315  			[]string{"Operation", "Keyspace", "DbType"}),
   316  
   317  		logExecute:       logutil.NewThrottledLogger("Execute", 5*time.Second),
   318  		logStreamExecute: logutil.NewThrottledLogger("StreamExecute", 5*time.Second),
   319  	}
   320  
   321  	_ = stats.NewRates("QPSByOperation", stats.CounterForDimension(rpcVTGate.timings, "Operation"), 15, 1*time.Minute)
   322  	_ = stats.NewRates("QPSByKeyspace", stats.CounterForDimension(rpcVTGate.timings, "Keyspace"), 15, 1*time.Minute)
   323  	_ = stats.NewRates("QPSByDbType", stats.CounterForDimension(rpcVTGate.timings, "DbType"), 15*60/5, 5*time.Second)
   324  
   325  	_ = stats.NewRates("ErrorsByOperation", stats.CounterForDimension(errorCounts, "Operation"), 15, 1*time.Minute)
   326  	_ = stats.NewRates("ErrorsByKeyspace", stats.CounterForDimension(errorCounts, "Keyspace"), 15, 1*time.Minute)
   327  	_ = stats.NewRates("ErrorsByDbType", stats.CounterForDimension(errorCounts, "DbType"), 15, 1*time.Minute)
   328  	_ = stats.NewRates("ErrorsByCode", stats.CounterForDimension(errorCounts, "Code"), 15, 1*time.Minute)
   329  
   330  	servenv.OnRun(func() {
   331  		for _, f := range RegisterVTGates {
   332  			f(rpcVTGate)
   333  		}
   334  		if st != nil && enableSchemaChangeSignal {
   335  			st.Start()
   336  		}
   337  	})
   338  	servenv.OnTerm(func() {
   339  		if st != nil && enableSchemaChangeSignal {
   340  			st.Stop()
   341  		}
   342  	})
   343  	rpcVTGate.registerDebugHealthHandler()
   344  	rpcVTGate.registerDebugEnvHandler()
   345  	err := initQueryLogger(rpcVTGate)
   346  	if err != nil {
   347  		log.Fatalf("error initializing query logger: %v", err)
   348  	}
   349  
   350  	initAPI(gw.hc)
   351  	return rpcVTGate
   352  }
   353  
   354  func addKeyspaceToTracker(ctx context.Context, srvResolver *srvtopo.Resolver, st *vtschema.Tracker, gw *TabletGateway) {
   355  	keyspaces, err := srvResolver.GetAllKeyspaces(ctx)
   356  	if err != nil {
   357  		log.Warningf("Unable to get all keyspaces: %v", err)
   358  		return
   359  	}
   360  	if len(keyspaces) == 0 {
   361  		log.Infof("No keyspace to load")
   362  	}
   363  	for _, keyspace := range keyspaces {
   364  		resolveAndLoadKeyspace(ctx, srvResolver, st, gw, keyspace)
   365  	}
   366  }
   367  
   368  func resolveAndLoadKeyspace(ctx context.Context, srvResolver *srvtopo.Resolver, st *vtschema.Tracker, gw *TabletGateway, keyspace string) {
   369  	dest, err := srvResolver.ResolveDestination(ctx, keyspace, topodatapb.TabletType_PRIMARY, key.DestinationAllShards{})
   370  	if err != nil {
   371  		log.Warningf("Unable to resolve destination: %v", err)
   372  		return
   373  	}
   374  
   375  	timeout := time.After(5 * time.Second)
   376  	for {
   377  		select {
   378  		case <-timeout:
   379  			log.Warningf("Unable to get initial schema reload for keyspace: %s", keyspace)
   380  			return
   381  		case <-time.After(500 * time.Millisecond):
   382  			for _, shard := range dest {
   383  				err := st.AddNewKeyspace(gw, shard.Target)
   384  				if err == nil {
   385  					return
   386  				}
   387  			}
   388  		}
   389  	}
   390  }
   391  
   392  func (vtg *VTGate) registerDebugEnvHandler() {
   393  	http.HandleFunc("/debug/env", func(w http.ResponseWriter, r *http.Request) {
   394  		debugEnvHandler(vtg, w, r)
   395  	})
   396  }
   397  
   398  func (vtg *VTGate) registerDebugHealthHandler() {
   399  	http.HandleFunc("/debug/health", func(w http.ResponseWriter, r *http.Request) {
   400  		if err := acl.CheckAccessHTTP(r, acl.MONITORING); err != nil {
   401  			acl.SendError(w, err)
   402  			return
   403  		}
   404  		w.Header().Set("Content-Type", "text/plain")
   405  		if err := vtg.IsHealthy(); err != nil {
   406  			w.Write([]byte("not ok"))
   407  			return
   408  		}
   409  		w.Write([]byte("ok"))
   410  	})
   411  }
   412  
   413  // IsHealthy returns nil if server is healthy.
   414  // Otherwise, it returns an error indicating the reason.
   415  func (vtg *VTGate) IsHealthy() error {
   416  	return nil
   417  }
   418  
   419  // Gateway returns the current gateway implementation. Mostly used for tests.
   420  func (vtg *VTGate) Gateway() *TabletGateway {
   421  	return vtg.gw
   422  }
   423  
   424  // Execute executes a non-streaming query. This is a V3 function.
   425  func (vtg *VTGate) Execute(ctx context.Context, session *vtgatepb.Session, sql string, bindVariables map[string]*querypb.BindVariable) (newSession *vtgatepb.Session, qr *sqltypes.Result, err error) {
   426  	// In this context, we don't care if we can't fully parse destination
   427  	destKeyspace, destTabletType, _, _ := vtg.executor.ParseDestinationTarget(session.TargetString)
   428  	statsKey := []string{"Execute", destKeyspace, topoproto.TabletTypeLString(destTabletType)}
   429  	defer vtg.timings.Record(statsKey, time.Now())
   430  
   431  	if bvErr := sqltypes.ValidateBindVariables(bindVariables); bvErr != nil {
   432  		err = vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "%v", bvErr)
   433  	} else {
   434  		safeSession := NewSafeSession(session)
   435  		qr, err = vtg.executor.Execute(ctx, "Execute", safeSession, sql, bindVariables)
   436  		safeSession.RemoveInternalSavepoint()
   437  	}
   438  	if err == nil {
   439  		vtg.rowsReturned.Add(statsKey, int64(len(qr.Rows)))
   440  		vtg.rowsAffected.Add(statsKey, int64(qr.RowsAffected))
   441  		return session, qr, nil
   442  	}
   443  
   444  	query := map[string]any{
   445  		"Sql":           sql,
   446  		"BindVariables": bindVariables,
   447  		"Session":       session,
   448  	}
   449  	err = recordAndAnnotateError(err, statsKey, query, vtg.logExecute)
   450  	return session, nil, err
   451  }
   452  
   453  // ExecuteBatch executes a batch of queries. This is a V3 function.
   454  func (vtg *VTGate) ExecuteBatch(ctx context.Context, session *vtgatepb.Session, sqlList []string, bindVariablesList []map[string]*querypb.BindVariable) (*vtgatepb.Session, []sqltypes.QueryResponse, error) {
   455  	// In this context, we don't care if we can't fully parse destination
   456  	destKeyspace, destTabletType, _, _ := vtg.executor.ParseDestinationTarget(session.TargetString)
   457  	statsKey := []string{"ExecuteBatch", destKeyspace, topoproto.TabletTypeLString(destTabletType)}
   458  	defer vtg.timings.Record(statsKey, time.Now())
   459  
   460  	for _, bindVariables := range bindVariablesList {
   461  		if bvErr := sqltypes.ValidateBindVariables(bindVariables); bvErr != nil {
   462  			return session, nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "%v", bvErr)
   463  		}
   464  	}
   465  
   466  	qrl := make([]sqltypes.QueryResponse, len(sqlList))
   467  	for i, sql := range sqlList {
   468  		var bv map[string]*querypb.BindVariable
   469  		if len(bindVariablesList) != 0 {
   470  			bv = bindVariablesList[i]
   471  		}
   472  		session, qrl[i].QueryResult, qrl[i].QueryError = vtg.Execute(ctx, session, sql, bv)
   473  		if qr := qrl[i].QueryResult; qr != nil {
   474  			vtg.rowsReturned.Add(statsKey, int64(len(qr.Rows)))
   475  			vtg.rowsAffected.Add(statsKey, int64(qr.RowsAffected))
   476  		}
   477  	}
   478  	return session, qrl, nil
   479  }
   480  
   481  // StreamExecute executes a streaming query. This is a V3 function.
   482  // Note we guarantee the callback will not be called concurrently
   483  // by multiple go routines.
   484  func (vtg *VTGate) StreamExecute(ctx context.Context, session *vtgatepb.Session, sql string, bindVariables map[string]*querypb.BindVariable, callback func(*sqltypes.Result) error) error {
   485  	// In this context, we don't care if we can't fully parse destination
   486  	destKeyspace, destTabletType, _, _ := vtg.executor.ParseDestinationTarget(session.TargetString)
   487  	statsKey := []string{"StreamExecute", destKeyspace, topoproto.TabletTypeLString(destTabletType)}
   488  
   489  	defer vtg.timings.Record(statsKey, time.Now())
   490  
   491  	var err error
   492  	if bvErr := sqltypes.ValidateBindVariables(bindVariables); bvErr != nil {
   493  		err = vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "%v", bvErr)
   494  	} else {
   495  		safeSession := NewSafeSession(session)
   496  		err = vtg.executor.StreamExecute(
   497  			ctx,
   498  			"StreamExecute",
   499  			safeSession,
   500  			sql,
   501  			bindVariables,
   502  			func(reply *sqltypes.Result) error {
   503  				vtg.rowsReturned.Add(statsKey, int64(len(reply.Rows)))
   504  				vtg.rowsAffected.Add(statsKey, int64(reply.RowsAffected))
   505  				return callback(reply)
   506  			})
   507  		safeSession.RemoveInternalSavepoint()
   508  	}
   509  	if err != nil {
   510  		query := map[string]any{
   511  			"Sql":           sql,
   512  			"BindVariables": bindVariables,
   513  			"Session":       session,
   514  		}
   515  		return recordAndAnnotateError(err, statsKey, query, vtg.logStreamExecute)
   516  	}
   517  	return nil
   518  }
   519  
   520  // CloseSession closes the session, rolling back any implicit transactions. This has the
   521  // same effect as if a "rollback" statement was executed, but does not affect the query
   522  // statistics.
   523  func (vtg *VTGate) CloseSession(ctx context.Context, session *vtgatepb.Session) error {
   524  	return vtg.executor.CloseSession(ctx, NewSafeSession(session))
   525  }
   526  
   527  // ResolveTransaction resolves the specified 2PC transaction.
   528  func (vtg *VTGate) ResolveTransaction(ctx context.Context, dtid string) error {
   529  	return formatError(vtg.txConn.Resolve(ctx, dtid))
   530  }
   531  
   532  // Prepare supports non-streaming prepare statement query with multi shards
   533  func (vtg *VTGate) Prepare(ctx context.Context, session *vtgatepb.Session, sql string, bindVariables map[string]*querypb.BindVariable) (newSession *vtgatepb.Session, fld []*querypb.Field, err error) {
   534  	// In this context, we don't care if we can't fully parse destination
   535  	destKeyspace, destTabletType, _, _ := vtg.executor.ParseDestinationTarget(session.TargetString)
   536  	statsKey := []string{"Execute", destKeyspace, topoproto.TabletTypeLString(destTabletType)}
   537  	defer vtg.timings.Record(statsKey, time.Now())
   538  
   539  	if bvErr := sqltypes.ValidateBindVariables(bindVariables); bvErr != nil {
   540  		err = vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "%v", bvErr)
   541  		goto handleError
   542  	}
   543  
   544  	fld, err = vtg.executor.Prepare(ctx, "Prepare", NewSafeSession(session), sql, bindVariables)
   545  	if err == nil {
   546  		return session, fld, nil
   547  	}
   548  
   549  handleError:
   550  	query := map[string]any{
   551  		"Sql":           sql,
   552  		"BindVariables": bindVariables,
   553  		"Session":       session,
   554  	}
   555  	err = recordAndAnnotateError(err, statsKey, query, vtg.logExecute)
   556  	return session, nil, err
   557  }
   558  
   559  // VStream streams binlog events.
   560  func (vtg *VTGate) VStream(ctx context.Context, tabletType topodatapb.TabletType, vgtid *binlogdatapb.VGtid, filter *binlogdatapb.Filter, flags *vtgatepb.VStreamFlags, send func([]*binlogdatapb.VEvent) error) error {
   561  	return vtg.vsm.VStream(ctx, tabletType, vgtid, filter, flags, send)
   562  }
   563  
   564  // GetGatewayCacheStatus returns a displayable version of the Gateway cache.
   565  func (vtg *VTGate) GetGatewayCacheStatus() TabletCacheStatusList {
   566  	return vtg.resolver.GetGatewayCacheStatus()
   567  }
   568  
   569  // VSchemaStats returns the loaded vschema stats.
   570  func (vtg *VTGate) VSchemaStats() *VSchemaStats {
   571  	return vtg.executor.VSchemaStats()
   572  }
   573  
   574  func truncateErrorStrings(data map[string]any) map[string]any {
   575  	ret := map[string]any{}
   576  	if terseErrors {
   577  		// request might have PII information. Return an empty map
   578  		return ret
   579  	}
   580  	for key, val := range data {
   581  		mapVal, ok := val.(map[string]any)
   582  		if ok {
   583  			ret[key] = truncateErrorStrings(mapVal)
   584  		} else {
   585  			strVal := fmt.Sprintf("%v", val)
   586  			ret[key] = sqlparser.TruncateForLog(strVal)
   587  		}
   588  	}
   589  	return ret
   590  }
   591  
   592  func recordAndAnnotateError(err error, statsKey []string, request map[string]any, logger *logutil.ThrottledLogger) error {
   593  	ec := vterrors.Code(err)
   594  	fullKey := []string{
   595  		statsKey[0],
   596  		statsKey[1],
   597  		statsKey[2],
   598  		ec.String(),
   599  	}
   600  
   601  	if terseErrors {
   602  		regexpBv := regexp.MustCompile(`BindVars: \{.*\}`)
   603  		str := regexpBv.ReplaceAllString(err.Error(), "BindVars: {REDACTED}")
   604  		err = errors.New(str)
   605  	}
   606  
   607  	// Traverse the request structure and truncate any long values
   608  	request = truncateErrorStrings(request)
   609  
   610  	errorCounts.Add(fullKey, 1)
   611  
   612  	// Most errors are not logged by vtgate because they're either too spammy or logged elsewhere.
   613  	switch ec {
   614  	case vtrpcpb.Code_UNKNOWN, vtrpcpb.Code_INTERNAL, vtrpcpb.Code_DATA_LOSS:
   615  		logger.Errorf("%v, request: %+v", err, request)
   616  	case vtrpcpb.Code_UNAVAILABLE:
   617  		logger.Infof("%v, request: %+v", err, request)
   618  	case vtrpcpb.Code_UNIMPLEMENTED:
   619  		sql, exists := request["Sql"]
   620  		if !exists {
   621  			return err
   622  		}
   623  		piiSafeSQL, err2 := sqlparser.RedactSQLQuery(sql.(string))
   624  		if err2 != nil {
   625  			return err
   626  		}
   627  		// log only if sql query present and able to successfully redact the PII.
   628  		logger.Infof("unsupported query: %q", piiSafeSQL)
   629  	}
   630  	return err
   631  }
   632  
   633  func formatError(err error) error {
   634  	if err == nil {
   635  		return nil
   636  	}
   637  	return err
   638  }
   639  
   640  // HandlePanic recovers from panics, and logs / increment counters
   641  func (vtg *VTGate) HandlePanic(err *error) {
   642  	if x := recover(); x != nil {
   643  		log.Errorf("Uncaught panic:\n%v\n%s", x, tb.Stack(4))
   644  		*err = fmt.Errorf("uncaught panic: %v, vtgate: %v", x, servenv.ListeningURL.String())
   645  		errorCounts.Add([]string{"Panic", "Unknown", "Unknown", vtrpcpb.Code_INTERNAL.String()}, 1)
   646  	}
   647  }