vitess.io/vitess@v0.16.2/go/vt/vtgate/engine/set.go (about) 1 /* 2 Copyright 2020 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 engine 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "fmt" 24 "strings" 25 26 "vitess.io/vitess/go/vt/sysvars" 27 28 vtgatepb "vitess.io/vitess/go/vt/proto/vtgate" 29 30 "vitess.io/vitess/go/vt/log" 31 32 "vitess.io/vitess/go/vt/srvtopo" 33 34 "vitess.io/vitess/go/vt/vtgate/evalengine" 35 36 "vitess.io/vitess/go/sqltypes" 37 "vitess.io/vitess/go/vt/key" 38 querypb "vitess.io/vitess/go/vt/proto/query" 39 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 40 "vitess.io/vitess/go/vt/schema" 41 "vitess.io/vitess/go/vt/vterrors" 42 "vitess.io/vitess/go/vt/vtgate/vindexes" 43 ) 44 45 type ( 46 // Set contains the instructions to perform set. 47 Set struct { 48 Ops []SetOp 49 Input Primitive 50 51 noTxNeeded 52 } 53 54 // SetOp is an interface that different type of set operations implements. 55 SetOp interface { 56 Execute(ctx context.Context, vcursor VCursor, env *evalengine.ExpressionEnv) error 57 VariableName() string 58 } 59 60 // UserDefinedVariable implements the SetOp interface to execute user defined variables. 61 UserDefinedVariable struct { 62 Name string 63 Expr evalengine.Expr 64 } 65 66 // SysVarIgnore implements the SetOp interface to ignore the settings. 67 SysVarIgnore struct { 68 Name string 69 Expr string 70 } 71 72 // SysVarCheckAndIgnore implements the SetOp interface to check underlying setting and ignore if same. 73 SysVarCheckAndIgnore struct { 74 Name string 75 Keyspace *vindexes.Keyspace 76 TargetDestination key.Destination `json:",omitempty"` 77 Expr string 78 } 79 80 // SysVarReservedConn implements the SetOp interface and will write the changes variable into the session 81 SysVarReservedConn struct { 82 Name string 83 Keyspace *vindexes.Keyspace 84 TargetDestination key.Destination `json:",omitempty"` 85 Expr string 86 SupportSetVar bool 87 } 88 89 // SysVarSetAware implements the SetOp interface and will write the changes variable into the session 90 // The special part is that these settings change the sessions behaviour in different ways 91 SysVarSetAware struct { 92 Name string 93 Expr evalengine.Expr 94 } 95 96 // VitessMetadata implements the SetOp interface and will write the changes variable into the topo server 97 VitessMetadata struct { 98 Name, Value string 99 } 100 ) 101 102 var unsupportedSQLModes = []string{"ANSI_QUOTES", "NO_BACKSLASH_ESCAPES", "PIPES_AS_CONCAT", "REAL_AS_FLOAT"} 103 104 var _ Primitive = (*Set)(nil) 105 106 // RouteType implements the Primitive interface method. 107 func (s *Set) RouteType() string { 108 return "Set" 109 } 110 111 // GetKeyspaceName implements the Primitive interface method. 112 func (s *Set) GetKeyspaceName() string { 113 return "" 114 } 115 116 // GetTableName implements the Primitive interface method. 117 func (s *Set) GetTableName() string { 118 return "" 119 } 120 121 // TryExecute implements the Primitive interface method. 122 func (s *Set) TryExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool) (*sqltypes.Result, error) { 123 input, err := vcursor.ExecutePrimitive(ctx, s.Input, bindVars, false) 124 if err != nil { 125 return nil, err 126 } 127 if len(input.Rows) != 1 { 128 return nil, vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "should get a single row") 129 } 130 env := evalengine.EnvWithBindVars(bindVars, vcursor.ConnCollation()) 131 env.Row = input.Rows[0] 132 env.Fields = input.Fields 133 for _, setOp := range s.Ops { 134 err := setOp.Execute(ctx, vcursor, env) 135 if err != nil { 136 return nil, err 137 } 138 } 139 return &sqltypes.Result{}, nil 140 } 141 142 // TryStreamExecute implements the Primitive interface method. 143 func (s *Set) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error { 144 result, err := s.TryExecute(ctx, vcursor, bindVars, wantfields) 145 if err != nil { 146 return err 147 } 148 return callback(result) 149 } 150 151 // GetFields implements the Primitive interface method. 152 func (s *Set) GetFields(context.Context, VCursor, map[string]*querypb.BindVariable) (*sqltypes.Result, error) { 153 return &sqltypes.Result{}, nil 154 } 155 156 // Inputs implements the Primitive interface 157 func (s *Set) Inputs() []Primitive { 158 return []Primitive{s.Input} 159 } 160 161 func (s *Set) description() PrimitiveDescription { 162 other := map[string]any{ 163 "Ops": s.Ops, 164 } 165 return PrimitiveDescription{ 166 OperatorType: "Set", 167 Other: other, 168 } 169 } 170 171 var _ SetOp = (*UserDefinedVariable)(nil) 172 173 // MarshalJSON provides the type to SetOp for plan json 174 func (u *UserDefinedVariable) MarshalJSON() ([]byte, error) { 175 return json.Marshal(struct { 176 Type string 177 Name string 178 Expr string 179 }{ 180 Type: "UserDefinedVariable", 181 Name: u.Name, 182 Expr: evalengine.FormatExpr(u.Expr), 183 }) 184 185 } 186 187 // VariableName implements the SetOp interface method. 188 func (u *UserDefinedVariable) VariableName() string { 189 return u.Name 190 } 191 192 // Execute implements the SetOp interface method. 193 func (u *UserDefinedVariable) Execute(ctx context.Context, vcursor VCursor, env *evalengine.ExpressionEnv) error { 194 value, err := env.Evaluate(u.Expr) 195 if err != nil { 196 return err 197 } 198 return vcursor.Session().SetUDV(u.Name, value.Value()) 199 } 200 201 var _ SetOp = (*SysVarIgnore)(nil) 202 203 // MarshalJSON provides the type to SetOp for plan json 204 func (svi *SysVarIgnore) MarshalJSON() ([]byte, error) { 205 return json.Marshal(struct { 206 Type string 207 SysVarIgnore 208 }{ 209 Type: "SysVarIgnore", 210 SysVarIgnore: *svi, 211 }) 212 213 } 214 215 // VariableName implements the SetOp interface method. 216 func (svi *SysVarIgnore) VariableName() string { 217 return svi.Name 218 } 219 220 // Execute implements the SetOp interface method. 221 func (svi *SysVarIgnore) Execute(context.Context, VCursor, *evalengine.ExpressionEnv) error { 222 log.Infof("Ignored inapplicable SET %v = %v", svi.Name, svi.Expr) 223 return nil 224 } 225 226 var _ SetOp = (*SysVarCheckAndIgnore)(nil) 227 228 // MarshalJSON provides the type to SetOp for plan json 229 func (svci *SysVarCheckAndIgnore) MarshalJSON() ([]byte, error) { 230 return json.Marshal(struct { 231 Type string 232 SysVarCheckAndIgnore 233 }{ 234 Type: "SysVarCheckAndIgnore", 235 SysVarCheckAndIgnore: *svci, 236 }) 237 238 } 239 240 // VariableName implements the SetOp interface method 241 func (svci *SysVarCheckAndIgnore) VariableName() string { 242 return svci.Name 243 } 244 245 // Execute implements the SetOp interface method 246 func (svci *SysVarCheckAndIgnore) Execute(ctx context.Context, vcursor VCursor, env *evalengine.ExpressionEnv) error { 247 rss, _, err := vcursor.ResolveDestinations(ctx, svci.Keyspace.Name, nil, []key.Destination{svci.TargetDestination}) 248 if err != nil { 249 return err 250 } 251 252 if len(rss) != 1 { 253 return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "Unexpected error, DestinationKeyspaceID mapping to multiple shards: %v", svci.TargetDestination) 254 } 255 checkSysVarQuery := fmt.Sprintf("select 1 from dual where @@%s = %s", svci.Name, svci.Expr) 256 result, err := execShard(ctx, nil, vcursor, checkSysVarQuery, env.BindVars, rss[0], false /* rollbackOnError */, false /* canAutocommit */) 257 if err != nil { 258 // Rather than returning the error, we will just log the error 259 // as the intention for executing the query it to validate the current setting and eventually ignore it anyways. 260 // There is no benefit of returning the error back to client. 261 log.Warningf("unable to validate the current settings for '%s': %s", svci.Name, err.Error()) 262 return nil 263 } 264 if len(result.Rows) == 0 { 265 log.Infof("Ignored inapplicable SET %v = %v", svci.Name, svci.Expr) 266 } 267 return nil 268 } 269 270 var _ SetOp = (*SysVarReservedConn)(nil) 271 272 // MarshalJSON provides the type to SetOp for plan json 273 func (svs *SysVarReservedConn) MarshalJSON() ([]byte, error) { 274 return json.Marshal(struct { 275 Type string 276 SysVarReservedConn 277 }{ 278 Type: "SysVarSet", 279 SysVarReservedConn: *svs, 280 }) 281 282 } 283 284 // VariableName implements the SetOp interface method 285 func (svs *SysVarReservedConn) VariableName() string { 286 return svs.Name 287 } 288 289 // Execute implements the SetOp interface method 290 func (svs *SysVarReservedConn) Execute(ctx context.Context, vcursor VCursor, env *evalengine.ExpressionEnv) error { 291 // For those running on advanced vitess settings. 292 if svs.TargetDestination != nil { 293 rss, _, err := vcursor.ResolveDestinations(ctx, svs.Keyspace.Name, nil, []key.Destination{svs.TargetDestination}) 294 if err != nil { 295 return err 296 } 297 vcursor.Session().NeedsReservedConn() 298 return svs.execSetStatement(ctx, vcursor, rss, env) 299 } 300 needReservedConn, err := svs.checkAndUpdateSysVar(ctx, vcursor, env) 301 if err != nil { 302 return err 303 } 304 if !needReservedConn { 305 // setting ignored, same as underlying datastore 306 return nil 307 } 308 // Update existing shard session with new system variable settings. 309 rss := vcursor.Session().ShardSession() 310 if len(rss) == 0 { 311 return nil 312 } 313 queries := make([]*querypb.BoundQuery, len(rss)) 314 for i := 0; i < len(rss); i++ { 315 queries[i] = &querypb.BoundQuery{ 316 Sql: fmt.Sprintf("set %s = %s", svs.Name, svs.Expr), 317 BindVariables: env.BindVars, 318 } 319 } 320 _, errs := vcursor.ExecuteMultiShard(ctx, nil, rss, queries, false /* rollbackOnError */, false /* canAutocommit */) 321 return vterrors.Aggregate(errs) 322 } 323 324 func (svs *SysVarReservedConn) execSetStatement(ctx context.Context, vcursor VCursor, rss []*srvtopo.ResolvedShard, env *evalengine.ExpressionEnv) error { 325 queries := make([]*querypb.BoundQuery, len(rss)) 326 for i := 0; i < len(rss); i++ { 327 queries[i] = &querypb.BoundQuery{ 328 Sql: fmt.Sprintf("set @@%s = %s", svs.Name, svs.Expr), 329 BindVariables: env.BindVars, 330 } 331 } 332 _, errs := vcursor.ExecuteMultiShard(ctx, nil, rss, queries, false /* rollbackOnError */, false /* canAutocommit */) 333 return vterrors.Aggregate(errs) 334 } 335 336 func (svs *SysVarReservedConn) checkAndUpdateSysVar(ctx context.Context, vcursor VCursor, res *evalengine.ExpressionEnv) (bool, error) { 337 sysVarExprValidationQuery := fmt.Sprintf("select %s from dual where @@%s != %s", svs.Expr, svs.Name, svs.Expr) 338 if svs.Name == "sql_mode" { 339 sysVarExprValidationQuery = fmt.Sprintf("select @@%s orig, %s new", svs.Name, svs.Expr) 340 } 341 rss, _, err := vcursor.ResolveDestinations(ctx, svs.Keyspace.Name, nil, []key.Destination{key.DestinationKeyspaceID{0}}) 342 if err != nil { 343 return false, err 344 } 345 qr, err := execShard(ctx, nil, vcursor, sysVarExprValidationQuery, res.BindVars, rss[0], false /* rollbackOnError */, false /* canAutocommit */) 346 if err != nil { 347 return false, err 348 } 349 changed := len(qr.Rows) > 0 350 if !changed { 351 return false, nil 352 } 353 354 var value sqltypes.Value 355 if svs.Name == "sql_mode" { 356 changed, value, err = sqlModeChangedValue(qr) 357 if err != nil { 358 return false, err 359 } 360 if !changed { 361 return false, nil 362 } 363 } else { 364 value = qr.Rows[0][0] 365 } 366 buf := new(bytes.Buffer) 367 value.EncodeSQL(buf) 368 s := buf.String() 369 vcursor.Session().SetSysVar(svs.Name, s) 370 371 // If the condition below is true, we want to use reserved connection instead of SET_VAR query hint. 372 // MySQL supports SET_VAR only in MySQL80 and for a limited set of system variables. 373 if !svs.SupportSetVar || s == "''" || !vcursor.CanUseSetVar() { 374 vcursor.Session().NeedsReservedConn() 375 return true, nil 376 } 377 return false, nil 378 } 379 380 func sqlModeChangedValue(qr *sqltypes.Result) (bool, sqltypes.Value, error) { 381 if len(qr.Fields) != 2 { 382 return false, sqltypes.Value{}, nil 383 } 384 if len(qr.Rows[0]) != 2 { 385 return false, sqltypes.Value{}, nil 386 } 387 orig := qr.Rows[0][0].ToString() 388 newVal := qr.Rows[0][1].ToString() 389 390 origArr := strings.Split(orig, ",") 391 // Keep track of if the value is seen or not. 392 origMap := map[string]bool{} 393 for _, oVal := range origArr { 394 // Default is not seen. 395 origMap[strings.ToUpper(oVal)] = true 396 } 397 uniqOrigVal := len(origMap) 398 origValSeen := 0 399 400 changed := false 401 newValArr := strings.Split(newVal, ",") 402 unsupportedMode := "" 403 for _, nVal := range newValArr { 404 nVal = strings.ToUpper(nVal) 405 for _, mode := range unsupportedSQLModes { 406 if mode == nVal { 407 unsupportedMode = nVal 408 break 409 } 410 } 411 notSeen, exists := origMap[nVal] 412 if !exists { 413 changed = true 414 break 415 } 416 if exists && notSeen { 417 // Value seen. Turn it off 418 origMap[nVal] = false 419 origValSeen++ 420 } 421 } 422 if !changed && uniqOrigVal != origValSeen { 423 changed = true 424 } 425 if changed && unsupportedMode != "" { 426 return false, sqltypes.Value{}, vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "setting the %s sql_mode is unsupported", unsupportedMode) 427 } 428 429 return changed, qr.Rows[0][1], nil 430 } 431 432 var _ SetOp = (*SysVarSetAware)(nil) 433 434 // MarshalJSON marshals all the json 435 func (svss *SysVarSetAware) MarshalJSON() ([]byte, error) { 436 return json.Marshal(struct { 437 Type string 438 Name string 439 Expr string 440 }{ 441 Type: "SysVarAware", 442 Name: svss.Name, 443 Expr: evalengine.FormatExpr(svss.Expr), 444 }) 445 } 446 447 // Execute implements the SetOp interface method 448 func (svss *SysVarSetAware) Execute(ctx context.Context, vcursor VCursor, env *evalengine.ExpressionEnv) error { 449 var err error 450 switch svss.Name { 451 case sysvars.Autocommit.Name: 452 err = svss.setBoolSysVar(ctx, env, vcursor.Session().SetAutocommit) 453 case sysvars.ClientFoundRows.Name: 454 err = svss.setBoolSysVar(ctx, env, vcursor.Session().SetClientFoundRows) 455 case sysvars.SkipQueryPlanCache.Name: 456 err = svss.setBoolSysVar(ctx, env, vcursor.Session().SetSkipQueryPlanCache) 457 case sysvars.TxReadOnly.Name, 458 sysvars.TransactionReadOnly.Name: 459 // TODO (4127): This is a dangerous NOP. 460 noop := func(context.Context, bool) error { return nil } 461 err = svss.setBoolSysVar(ctx, env, noop) 462 case sysvars.SQLSelectLimit.Name: 463 intValue, err := svss.evalAsInt64(env) 464 if err != nil { 465 return err 466 } 467 vcursor.Session().SetSQLSelectLimit(intValue) // nolint:errcheck 468 case sysvars.TransactionMode.Name: 469 str, err := svss.evalAsString(env) 470 if err != nil { 471 return err 472 } 473 out, ok := vtgatepb.TransactionMode_value[strings.ToUpper(str)] 474 if !ok { 475 return vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValueForVar, "invalid transaction_mode: %s", str) 476 } 477 vcursor.Session().SetTransactionMode(vtgatepb.TransactionMode(out)) 478 case sysvars.Workload.Name: 479 str, err := svss.evalAsString(env) 480 if err != nil { 481 return err 482 } 483 out, ok := querypb.ExecuteOptions_Workload_value[strings.ToUpper(str)] 484 if !ok { 485 return vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValueForVar, "invalid workload: %s", str) 486 } 487 vcursor.Session().SetWorkload(querypb.ExecuteOptions_Workload(out)) 488 case sysvars.DDLStrategy.Name: 489 str, err := svss.evalAsString(env) 490 if err != nil { 491 return err 492 } 493 if _, err := schema.ParseDDLStrategy(str); err != nil { 494 return vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValueForVar, "invalid DDL strategy: %s", str) 495 } 496 vcursor.Session().SetDDLStrategy(str) 497 case sysvars.QueryTimeout.Name: 498 queryTimeout, err := svss.evalAsInt64(env) 499 if err != nil { 500 return err 501 } 502 vcursor.Session().SetQueryTimeout(queryTimeout) 503 case sysvars.SessionEnableSystemSettings.Name: 504 err = svss.setBoolSysVar(ctx, env, vcursor.Session().SetSessionEnableSystemSettings) 505 case sysvars.Charset.Name, sysvars.Names.Name: 506 str, err := svss.evalAsString(env) 507 if err != nil { 508 return err 509 } 510 switch strings.ToLower(str) { 511 case "", "utf8", "utf8mb4", "latin1", "default": 512 // do nothing 513 break 514 default: 515 return vterrors.Errorf(vtrpcpb.Code_UNIMPLEMENTED, "charset/name %v is not supported", str) 516 } 517 case sysvars.ReadAfterWriteGTID.Name: 518 str, err := svss.evalAsString(env) 519 if err != nil { 520 return err 521 } 522 vcursor.Session().SetReadAfterWriteGTID(str) 523 case sysvars.ReadAfterWriteTimeOut.Name: 524 val, err := svss.evalAsFloat(env) 525 if err != nil { 526 return err 527 } 528 vcursor.Session().SetReadAfterWriteTimeout(val) 529 case sysvars.SessionTrackGTIDs.Name: 530 str, err := svss.evalAsString(env) 531 if err != nil { 532 return err 533 } 534 switch strings.ToLower(str) { 535 case "off": 536 vcursor.Session().SetSessionTrackGTIDs(false) 537 case "own_gtid": 538 vcursor.Session().SetSessionTrackGTIDs(true) 539 default: 540 return vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValueForVar, "variable 'session_track_gtids' can't be set to the value of '%s'", str) 541 } 542 default: 543 return vterrors.NewErrorf(vtrpcpb.Code_NOT_FOUND, vterrors.UnknownSystemVariable, "unknown system variable '%s'", svss.Name) 544 } 545 546 return err 547 } 548 549 func (svss *SysVarSetAware) evalAsInt64(env *evalengine.ExpressionEnv) (int64, error) { 550 value, err := env.Evaluate(svss.Expr) 551 if err != nil { 552 return 0, err 553 } 554 555 v := value.Value() 556 if !v.IsIntegral() { 557 return 0, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongTypeForVar, "incorrect argument type to variable '%s': %s", svss.Name, value.Value().Type().String()) 558 } 559 intValue, err := v.ToInt64() 560 if err != nil { 561 return 0, err 562 } 563 return intValue, nil 564 } 565 566 func (svss *SysVarSetAware) evalAsFloat(env *evalengine.ExpressionEnv) (float64, error) { 567 value, err := env.Evaluate(svss.Expr) 568 if err != nil { 569 return 0, err 570 } 571 572 v := value.Value() 573 floatValue, err := v.ToFloat64() 574 if err != nil { 575 return 0, vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongTypeForVar, "incorrect argument type to variable '%s': %s", svss.Name, value.Value().Type().String()) 576 } 577 return floatValue, nil 578 } 579 580 func (svss *SysVarSetAware) evalAsString(env *evalengine.ExpressionEnv) (string, error) { 581 value, err := env.Evaluate(svss.Expr) 582 if err != nil { 583 return "", err 584 } 585 v := value.Value() 586 if !v.IsText() && !v.IsBinary() { 587 return "", vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongTypeForVar, "incorrect argument type to variable '%s': %s", svss.Name, value.Value().Type().String()) 588 } 589 590 return v.ToString(), nil 591 } 592 593 func (svss *SysVarSetAware) setBoolSysVar(ctx context.Context, env *evalengine.ExpressionEnv, setter func(context.Context, bool) error) error { 594 value, err := env.Evaluate(svss.Expr) 595 if err != nil { 596 return err 597 } 598 boolValue, err := value.ToBooleanStrict() 599 if err != nil { 600 return vterrors.NewErrorf(vtrpcpb.Code_INVALID_ARGUMENT, vterrors.WrongValueForVar, "variable '%s' can't be set to the value: %s", svss.Name, err.Error()) 601 } 602 return setter(ctx, boolValue) 603 } 604 605 // VariableName implements the SetOp interface method 606 func (svss *SysVarSetAware) VariableName() string { 607 return svss.Name 608 } 609 610 var _ SetOp = (*VitessMetadata)(nil) 611 612 func (v *VitessMetadata) Execute(ctx context.Context, vcursor VCursor, env *evalengine.ExpressionEnv) error { 613 return vcursor.SetExec(ctx, v.Name, v.Value) 614 } 615 616 func (v *VitessMetadata) VariableName() string { 617 return v.Name 618 }