vitess.io/vitess@v0.16.2/go/vt/vtgate/engine/lock.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  	"context"
    21  	"fmt"
    22  
    23  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    24  
    25  	"vitess.io/vitess/go/vt/srvtopo"
    26  
    27  	querypb "vitess.io/vitess/go/vt/proto/query"
    28  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    29  
    30  	"vitess.io/vitess/go/sqltypes"
    31  	"vitess.io/vitess/go/vt/key"
    32  	"vitess.io/vitess/go/vt/sqlparser"
    33  	"vitess.io/vitess/go/vt/vterrors"
    34  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    35  )
    36  
    37  var _ Primitive = (*Lock)(nil)
    38  
    39  // Lock primitive will execute sql containing lock functions
    40  type Lock struct {
    41  	// Keyspace specifies the keyspace to send the query to.
    42  	Keyspace *vindexes.Keyspace
    43  
    44  	// TargetDestination specifies an explicit target destination to send the query to.
    45  	TargetDestination key.Destination
    46  
    47  	FieldQuery string
    48  
    49  	LockFunctions []*LockFunc
    50  
    51  	noInputs
    52  
    53  	noTxNeeded
    54  }
    55  
    56  type LockFunc struct {
    57  	Typ  *sqlparser.LockingFunc
    58  	Name evalengine.Expr
    59  }
    60  
    61  // RouteType is part of the Primitive interface
    62  func (l *Lock) RouteType() string {
    63  	return "lock"
    64  }
    65  
    66  // GetKeyspaceName is part of the Primitive interface
    67  func (l *Lock) GetKeyspaceName() string {
    68  	return l.Keyspace.Name
    69  }
    70  
    71  // GetTableName is part of the Primitive interface
    72  func (l *Lock) GetTableName() string {
    73  	return "dual"
    74  }
    75  
    76  // TryExecute is part of the Primitive interface
    77  func (l *Lock) TryExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool) (*sqltypes.Result, error) {
    78  	return l.execLock(ctx, vcursor, bindVars)
    79  }
    80  
    81  func (l *Lock) execLock(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) (*sqltypes.Result, error) {
    82  	rss, _, err := vcursor.ResolveDestinations(ctx, l.Keyspace.Name, nil, []key.Destination{l.TargetDestination})
    83  	if err != nil {
    84  		return nil, err
    85  	}
    86  	if len(rss) != 1 {
    87  		return nil, vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "lock query can be routed to single shard only: %v", rss)
    88  	}
    89  
    90  	env := &evalengine.ExpressionEnv{BindVars: bindVars}
    91  	var fields []*querypb.Field
    92  	var rrow sqltypes.Row
    93  	for _, lf := range l.LockFunctions {
    94  		var lName string
    95  		if lf.Name != nil {
    96  			er, err := env.Evaluate(lf.Name)
    97  			if err != nil {
    98  				return nil, err
    99  			}
   100  			lName = er.Value().ToString()
   101  		}
   102  		qr, err := lf.execLock(ctx, vcursor, bindVars, rss[0])
   103  		if err != nil {
   104  			return nil, err
   105  		}
   106  		fields = append(fields, qr.Fields...)
   107  		lockRes := qr.Rows[0]
   108  		rrow = append(rrow, lockRes...)
   109  
   110  		switch lf.Typ.Type {
   111  		case sqlparser.IsFreeLock, sqlparser.IsUsedLock:
   112  		case sqlparser.GetLock:
   113  			if lockRes[0].ToString() == "1" {
   114  				vcursor.Session().AddAdvisoryLock(lName)
   115  			}
   116  		case sqlparser.ReleaseAllLocks:
   117  			err = vcursor.ReleaseLock(ctx)
   118  			if err != nil {
   119  				return nil, err
   120  			}
   121  		case sqlparser.ReleaseLock:
   122  			// TODO: do not execute if lock not taken.
   123  			if lockRes[0].ToString() == "1" {
   124  				vcursor.Session().RemoveAdvisoryLock(lName)
   125  			}
   126  			if !vcursor.Session().AnyAdvisoryLockTaken() {
   127  				err = vcursor.ReleaseLock(ctx)
   128  				if err != nil {
   129  					return nil, err
   130  				}
   131  			}
   132  		}
   133  	}
   134  	return &sqltypes.Result{
   135  		Fields: fields,
   136  		Rows:   []sqltypes.Row{rrow},
   137  	}, nil
   138  }
   139  
   140  func (lf *LockFunc) execLock(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, rs *srvtopo.ResolvedShard) (*sqltypes.Result, error) {
   141  	boundQuery := &querypb.BoundQuery{
   142  		Sql:           fmt.Sprintf("select %s from dual", sqlparser.String(lf.Typ)),
   143  		BindVariables: bindVars,
   144  	}
   145  	qr, err := vcursor.ExecuteLock(ctx, rs, boundQuery, lf.Typ.Type)
   146  	if err != nil {
   147  		return nil, err
   148  	}
   149  	if len(qr.Rows) != 1 && len(qr.Fields) != 1 {
   150  		return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "unexpected rows or fields returned for the lock function: %v", lf.Typ.Type)
   151  	}
   152  	return qr, nil
   153  }
   154  
   155  // TryStreamExecute is part of the Primitive interface
   156  func (l *Lock) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error {
   157  	qr, err := l.TryExecute(ctx, vcursor, bindVars, wantfields)
   158  	if err != nil {
   159  		return err
   160  	}
   161  	return callback(qr)
   162  }
   163  
   164  // GetFields is part of the Primitive interface
   165  func (l *Lock) GetFields(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) (*sqltypes.Result, error) {
   166  	rss, _, err := vcursor.ResolveDestinations(ctx, l.Keyspace.Name, nil, []key.Destination{l.TargetDestination})
   167  	if err != nil {
   168  		return nil, err
   169  	}
   170  	if len(rss) != 1 {
   171  		return nil, vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "lock query can be routed to single shard only: %v", rss)
   172  	}
   173  	boundQuery := []*querypb.BoundQuery{{
   174  		Sql:           l.FieldQuery,
   175  		BindVariables: bindVars,
   176  	}}
   177  	qr, errs := vcursor.ExecuteMultiShard(ctx, l, rss, boundQuery, false, true)
   178  	if len(errs) > 0 {
   179  		return nil, vterrors.Aggregate(errs)
   180  	}
   181  	return qr, nil
   182  }
   183  
   184  func (l *Lock) description() PrimitiveDescription {
   185  	other := map[string]any{
   186  		"FieldQuery": l.FieldQuery,
   187  	}
   188  	var lf []string
   189  	for _, f := range l.LockFunctions {
   190  		lf = append(lf, sqlparser.String(f.Typ))
   191  	}
   192  	other["lock_func"] = lf
   193  	return PrimitiveDescription{
   194  		OperatorType:      "Lock",
   195  		Keyspace:          l.Keyspace,
   196  		TargetDestination: l.TargetDestination,
   197  		Other:             other,
   198  	}
   199  }