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 }