vitess.io/vitess@v0.16.2/go/vt/vtgate/planbuilder/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 planbuilder
    18  
    19  import (
    20  	"fmt"
    21  	"strconv"
    22  	"strings"
    23  
    24  	"vitess.io/vitess/go/vt/sysvars"
    25  	"vitess.io/vitess/go/vt/vtgate/planbuilder/plancontext"
    26  
    27  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    28  
    29  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    30  
    31  	"vitess.io/vitess/go/vt/vterrors"
    32  
    33  	"vitess.io/vitess/go/vt/key"
    34  	"vitess.io/vitess/go/vt/sqlparser"
    35  	"vitess.io/vitess/go/vt/vtgate/engine"
    36  )
    37  
    38  type (
    39  	planFunc = func(expr *sqlparser.SetExpr, vschema plancontext.VSchema, ec *expressionConverter) (engine.SetOp, error)
    40  
    41  	setting struct {
    42  		name         string
    43  		boolean      bool
    44  		defaultValue evalengine.Expr
    45  
    46  		// this allows identifiers (a.k.a. ColName) from the AST to be handled as if they are strings.
    47  		// SET transaction_mode = two_pc => SET transaction_mode = 'two_pc'
    48  		identifierAsString bool
    49  		supportSetVar      bool
    50  		storageCase        sysvars.StorageCase
    51  	}
    52  )
    53  
    54  func buildSetPlan(stmt *sqlparser.Set, vschema plancontext.VSchema) (*planResult, error) {
    55  	var setOps []engine.SetOp
    56  	var err error
    57  
    58  	ec := new(expressionConverter)
    59  
    60  	for _, expr := range stmt.Exprs {
    61  		// AST struct has been prepared before getting here, so no scope here means that
    62  		// we have a UDV. If the original query didn't explicitly specify the scope, it
    63  		// would have been explictly set to sqlparser.SessionStr before reaching this
    64  		// phase of planning
    65  		switch expr.Var.Scope {
    66  		case sqlparser.GlobalScope:
    67  			setOp, err := planSysVarCheckIgnore(expr, vschema, true)
    68  			if err != nil {
    69  				return nil, err
    70  			}
    71  			setOps = append(setOps, setOp)
    72  		case sqlparser.VariableScope:
    73  			evalExpr, err := ec.convert(expr.Expr /*boolean*/, false /*identifierAsString*/, false)
    74  			if err != nil {
    75  				return nil, err
    76  			}
    77  			setOp := &engine.UserDefinedVariable{
    78  				Name: expr.Var.Name.Lowered(),
    79  				Expr: evalExpr,
    80  			}
    81  			setOps = append(setOps, setOp)
    82  		case sqlparser.NextTxScope, sqlparser.SessionScope:
    83  			planFunc, err := sysvarPlanningFuncs.Get(expr)
    84  			if err != nil {
    85  				return nil, err
    86  			}
    87  			setOp, err := planFunc(expr, vschema, ec)
    88  			if err != nil {
    89  				return nil, err
    90  			}
    91  			setOps = append(setOps, setOp)
    92  			if expr.Var.Scope == sqlparser.NextTxScope {
    93  				// This is to keep the backward compatibility.
    94  				// 'transaction_isolation' was added as a reserved connection system variable, so it used to change the setting at session level already.
    95  				// logging warning now to
    96  				vschema.PlannerWarning("converted 'next transaction' scope to 'session' scope")
    97  			}
    98  		case sqlparser.VitessMetadataScope:
    99  			value, err := getValueFor(expr)
   100  			if err != nil {
   101  				return nil, err
   102  			}
   103  			val, ok := value.(string)
   104  			if !ok {
   105  				return nil, vterrors.VT03009(expr.Var.Name, value)
   106  			}
   107  
   108  			setOps = append(setOps,
   109  				&engine.VitessMetadata{Name: expr.Var.Name.Lowered(), Value: val})
   110  		default:
   111  			return nil, vterrors.VT13001(fmt.Sprintf("undefined set type: %v", expr.Var.Scope.ToString()))
   112  		}
   113  	}
   114  
   115  	input, err := ec.source(vschema)
   116  	if err != nil {
   117  		return nil, err
   118  	}
   119  
   120  	return newPlanResult(&engine.Set{
   121  		Ops:   setOps,
   122  		Input: input,
   123  	}), nil
   124  }
   125  
   126  func buildSetOpReadOnly(setting) planFunc {
   127  	return func(expr *sqlparser.SetExpr, schema plancontext.VSchema, _ *expressionConverter) (engine.SetOp, error) {
   128  		return nil, vterrors.VT03010(expr.Var.Name)
   129  	}
   130  }
   131  
   132  func buildNotSupported(setting) planFunc {
   133  	return func(expr *sqlparser.SetExpr, schema plancontext.VSchema, _ *expressionConverter) (engine.SetOp, error) {
   134  		return nil, vterrors.VT12001(fmt.Sprintf("system setting: %s", expr.Var.Name))
   135  	}
   136  }
   137  
   138  func buildSetOpIgnore(s setting) planFunc {
   139  	return func(expr *sqlparser.SetExpr, vschema plancontext.VSchema, _ *expressionConverter) (engine.SetOp, error) {
   140  		value, err := extractValue(expr, s.boolean)
   141  		if err != nil {
   142  			return nil, err
   143  		}
   144  		return &engine.SysVarIgnore{
   145  			Name: expr.Var.Name.Lowered(),
   146  			Expr: value,
   147  		}, nil
   148  	}
   149  }
   150  
   151  func buildSetOpCheckAndIgnore(s setting) planFunc {
   152  	return func(expr *sqlparser.SetExpr, schema plancontext.VSchema, _ *expressionConverter) (engine.SetOp, error) {
   153  		return planSysVarCheckIgnore(expr, schema, s.boolean)
   154  	}
   155  }
   156  
   157  func planSysVarCheckIgnore(expr *sqlparser.SetExpr, schema plancontext.VSchema, boolean bool) (engine.SetOp, error) {
   158  	keyspace, dest, err := resolveDestination(schema)
   159  	if err != nil {
   160  		return nil, err
   161  	}
   162  	value, err := extractValue(expr, boolean)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  
   167  	return &engine.SysVarCheckAndIgnore{
   168  		Name:              expr.Var.Name.Lowered(),
   169  		Keyspace:          keyspace,
   170  		TargetDestination: dest,
   171  		Expr:              value,
   172  	}, nil
   173  }
   174  
   175  func buildSetOpReservedConn(s setting) planFunc {
   176  	return func(expr *sqlparser.SetExpr, vschema plancontext.VSchema, _ *expressionConverter) (engine.SetOp, error) {
   177  		if !vschema.SysVarSetEnabled() {
   178  			return planSysVarCheckIgnore(expr, vschema, s.boolean)
   179  		}
   180  		ks, err := vschema.AnyKeyspace()
   181  		if err != nil {
   182  			return nil, err
   183  		}
   184  		value, err := extractValue(expr, s.boolean)
   185  		if err != nil {
   186  			return nil, err
   187  		}
   188  
   189  		value = provideAppliedCase(value, s.storageCase)
   190  
   191  		return &engine.SysVarReservedConn{
   192  			Name:              expr.Var.Name.Lowered(),
   193  			Keyspace:          ks,
   194  			TargetDestination: vschema.Destination(),
   195  			Expr:              value,
   196  			SupportSetVar:     s.supportSetVar,
   197  		}, nil
   198  	}
   199  }
   200  
   201  func provideAppliedCase(value string, storageCase sysvars.StorageCase) string {
   202  	switch storageCase {
   203  	case sysvars.SCUpper:
   204  		return strings.ToUpper(value)
   205  	case sysvars.SCLower:
   206  		return strings.ToLower(value)
   207  	}
   208  	return value
   209  }
   210  
   211  const defaultNotSupportedErrFmt = "DEFAULT for @@%s"
   212  
   213  func buildSetOpVitessAware(s setting) planFunc {
   214  	return func(astExpr *sqlparser.SetExpr, vschema plancontext.VSchema, ec *expressionConverter) (engine.SetOp, error) {
   215  		var err error
   216  		var runtimeExpr evalengine.Expr
   217  
   218  		_, isDefault := astExpr.Expr.(*sqlparser.Default)
   219  		if isDefault {
   220  			if s.defaultValue == nil {
   221  				return nil, vterrors.VT12001(fmt.Sprintf(defaultNotSupportedErrFmt, astExpr.Var.Name))
   222  			}
   223  			runtimeExpr = s.defaultValue
   224  		} else {
   225  			runtimeExpr, err = ec.convert(astExpr.Expr, s.boolean, s.identifierAsString)
   226  			if err != nil {
   227  				return nil, err
   228  			}
   229  		}
   230  
   231  		return &engine.SysVarSetAware{
   232  			Name: astExpr.Var.Name.Lowered(),
   233  			Expr: runtimeExpr,
   234  		}, nil
   235  	}
   236  }
   237  
   238  func resolveDestination(vschema plancontext.VSchema) (*vindexes.Keyspace, key.Destination, error) {
   239  	keyspace, err := vschema.AnyKeyspace()
   240  	if err != nil {
   241  		return nil, nil, err
   242  	}
   243  
   244  	dest := vschema.Destination()
   245  	if dest == nil {
   246  		dest = key.DestinationAnyShard{}
   247  	}
   248  	return keyspace, dest, nil
   249  }
   250  
   251  func extractValue(expr *sqlparser.SetExpr, boolean bool) (string, error) {
   252  	switch node := expr.Expr.(type) {
   253  	case *sqlparser.Literal:
   254  		if node.Type == sqlparser.StrVal && boolean {
   255  			switch strings.ToLower(node.Val) {
   256  			case "on":
   257  				return "1", nil
   258  			case "off":
   259  				return "0", nil
   260  			}
   261  		}
   262  	case *sqlparser.ColName:
   263  		// this is a little of a hack. it's used when the setting is not a normal expression, but rather
   264  		// an enumeration, such as utf8, utf8mb4, etc
   265  		switch node.Name.Lowered() {
   266  		case "on":
   267  			return "1", nil
   268  		case "off":
   269  			return "0", nil
   270  		}
   271  		return fmt.Sprintf("'%s'", sqlparser.String(expr.Expr)), nil
   272  
   273  	case *sqlparser.Default:
   274  		return "", vterrors.VT12001(defaultNotSupportedErrFmt, expr.Var.Name)
   275  	}
   276  
   277  	return sqlparser.String(expr.Expr), nil
   278  }
   279  
   280  func getValueFor(expr *sqlparser.SetExpr) (any, error) {
   281  	switch expr := expr.Expr.(type) {
   282  	case *sqlparser.Literal:
   283  		switch expr.Type {
   284  		case sqlparser.StrVal:
   285  			return strings.ToLower(expr.Val), nil
   286  		case sqlparser.IntVal:
   287  			num, err := strconv.ParseInt(expr.Val, 0, 64)
   288  			if err != nil {
   289  				return nil, err
   290  			}
   291  			return num, nil
   292  		case sqlparser.FloatVal, sqlparser.DecimalVal:
   293  			num, err := strconv.ParseFloat(expr.Val, 64)
   294  			if err != nil {
   295  				return nil, err
   296  			}
   297  			return num, nil
   298  		default:
   299  			return nil, vterrors.VT03011(sqlparser.String(expr))
   300  		}
   301  	case sqlparser.BoolVal:
   302  		var val int64
   303  		if expr {
   304  			val = 1
   305  		}
   306  		return val, nil
   307  	case *sqlparser.NullVal:
   308  		return nil, nil
   309  	case *sqlparser.ColName:
   310  		return expr.Name.String(), nil
   311  	case *sqlparser.Default:
   312  		return "default", nil
   313  	default:
   314  		return nil, vterrors.VT03012(sqlparser.String(expr))
   315  	}
   316  }