vitess.io/vitess@v0.16.2/go/vt/vtgate/engine/send.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  
    22  	"vitess.io/vitess/go/sqltypes"
    23  	"vitess.io/vitess/go/vt/key"
    24  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    25  	"vitess.io/vitess/go/vt/srvtopo"
    26  	"vitess.io/vitess/go/vt/vterrors"
    27  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    28  
    29  	querypb "vitess.io/vitess/go/vt/proto/query"
    30  )
    31  
    32  var _ Primitive = (*Send)(nil)
    33  
    34  // Send is an operator to send query to the specific keyspace, tabletType and destination
    35  type Send struct {
    36  	// Keyspace specifies the keyspace to send the query to.
    37  	Keyspace *vindexes.Keyspace
    38  
    39  	// TargetDestination specifies an explicit target destination to send the query to.
    40  	TargetDestination key.Destination
    41  
    42  	// Query specifies the query to be executed.
    43  	Query string
    44  
    45  	// IsDML specifies how to deal with autocommit behaviour
    46  	IsDML bool
    47  
    48  	// SingleShardOnly specifies that the query must be send to only single shard
    49  	SingleShardOnly bool
    50  
    51  	// ShardNameNeeded specified that the shard name is added to the bind variables
    52  	ShardNameNeeded bool
    53  
    54  	// MultishardAutocommit specifies that a multishard transaction query can autocommit
    55  	MultishardAutocommit bool
    56  
    57  	noInputs
    58  }
    59  
    60  // ShardName as key for setting shard name in bind variables map
    61  const ShardName = "__vt_shard"
    62  
    63  // NeedsTransaction implements the Primitive interface
    64  func (s *Send) NeedsTransaction() bool {
    65  	return s.IsDML
    66  }
    67  
    68  // RouteType implements Primitive interface
    69  func (s *Send) RouteType() string {
    70  	if s.IsDML {
    71  		return "SendDML"
    72  	}
    73  
    74  	return "Send"
    75  }
    76  
    77  // GetKeyspaceName implements Primitive interface
    78  func (s *Send) GetKeyspaceName() string {
    79  	return s.Keyspace.Name
    80  }
    81  
    82  // GetTableName implements Primitive interface
    83  func (s *Send) GetTableName() string {
    84  	return ""
    85  }
    86  
    87  // TryExecute implements Primitive interface
    88  func (s *Send) TryExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool) (*sqltypes.Result, error) {
    89  	ctx, cancelFunc := addQueryTimeout(ctx, vcursor, 0)
    90  	defer cancelFunc()
    91  	rss, _, err := vcursor.ResolveDestinations(ctx, s.Keyspace.Name, nil, []key.Destination{s.TargetDestination})
    92  	if err != nil {
    93  		return nil, err
    94  	}
    95  
    96  	if !s.Keyspace.Sharded && len(rss) != 1 {
    97  		return nil, vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "Keyspace does not have exactly one shard: %v", rss)
    98  	}
    99  
   100  	if s.SingleShardOnly && len(rss) != 1 {
   101  		return nil, vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "Unexpected error, DestinationKeyspaceID mapping to multiple shards: %s, got: %v", s.Query, s.TargetDestination)
   102  	}
   103  
   104  	queries := make([]*querypb.BoundQuery, len(rss))
   105  	for i, rs := range rss {
   106  		bv := bindVars
   107  		if s.ShardNameNeeded {
   108  			bv = copyBindVars(bindVars)
   109  			bv[ShardName] = sqltypes.StringBindVariable(rs.Target.Shard)
   110  		}
   111  		queries[i] = &querypb.BoundQuery{
   112  			Sql:           s.Query,
   113  			BindVariables: bv,
   114  		}
   115  	}
   116  
   117  	rollbackOnError := s.IsDML // for non-dml queries, there's no need to do a rollback
   118  	result, errs := vcursor.ExecuteMultiShard(ctx, s, rss, queries, rollbackOnError, s.canAutoCommit(vcursor, rss))
   119  	err = vterrors.Aggregate(errs)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	return result, nil
   124  }
   125  
   126  func (s *Send) canAutoCommit(vcursor VCursor, rss []*srvtopo.ResolvedShard) bool {
   127  	if s.IsDML {
   128  		return (len(rss) == 1 || s.MultishardAutocommit) && vcursor.AutocommitApproval()
   129  	}
   130  	return false
   131  }
   132  
   133  func copyBindVars(in map[string]*querypb.BindVariable) map[string]*querypb.BindVariable {
   134  	out := make(map[string]*querypb.BindVariable, len(in)+1)
   135  	for k, v := range in {
   136  		out[k] = v
   137  	}
   138  	return out
   139  }
   140  
   141  // TryStreamExecute implements Primitive interface
   142  func (s *Send) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error {
   143  	rss, _, err := vcursor.ResolveDestinations(ctx, s.Keyspace.Name, nil, []key.Destination{s.TargetDestination})
   144  	if err != nil {
   145  		return err
   146  	}
   147  
   148  	if !s.Keyspace.Sharded && len(rss) != 1 {
   149  		return vterrors.Errorf(vtrpcpb.Code_FAILED_PRECONDITION, "Keyspace does not have exactly one shard: %v", rss)
   150  	}
   151  
   152  	if s.SingleShardOnly && len(rss) != 1 {
   153  		return vterrors.Errorf(vtrpcpb.Code_INVALID_ARGUMENT, "Unexpected error, DestinationKeyspaceID mapping to multiple shards: %s, got: %v", s.Query, s.TargetDestination)
   154  	}
   155  
   156  	multiBindVars := make([]map[string]*querypb.BindVariable, len(rss))
   157  	for i, rs := range rss {
   158  		bv := bindVars
   159  		if s.ShardNameNeeded {
   160  			bv = copyBindVars(bindVars)
   161  			bv[ShardName] = sqltypes.StringBindVariable(rs.Target.Shard)
   162  		}
   163  		multiBindVars[i] = bv
   164  	}
   165  	errors := vcursor.StreamExecuteMulti(ctx, s, s.Query, rss, multiBindVars, s.IsDML, s.canAutoCommit(vcursor, rss), callback)
   166  	return vterrors.Aggregate(errors)
   167  }
   168  
   169  // GetFields implements Primitive interface
   170  func (s *Send) GetFields(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) (*sqltypes.Result, error) {
   171  	qr, err := vcursor.ExecutePrimitive(ctx, s, bindVars, false)
   172  	if err != nil {
   173  		return nil, err
   174  	}
   175  	qr.Rows = nil
   176  	return qr, nil
   177  }
   178  
   179  func (s *Send) description() PrimitiveDescription {
   180  	other := map[string]any{
   181  		"Query": s.Query,
   182  		"Table": s.GetTableName(),
   183  	}
   184  	if s.IsDML {
   185  		other["IsDML"] = true
   186  	}
   187  	if s.SingleShardOnly {
   188  		other["SingleShardOnly"] = true
   189  	}
   190  	if s.ShardNameNeeded {
   191  		other["ShardNameNeeded"] = true
   192  	}
   193  	if s.MultishardAutocommit {
   194  		other["MultishardAutocommit"] = true
   195  	}
   196  	return PrimitiveDescription{
   197  		OperatorType:      "Send",
   198  		Keyspace:          s.Keyspace,
   199  		TargetDestination: s.TargetDestination,
   200  		Other:             other,
   201  	}
   202  }