vitess.io/vitess@v0.16.2/go/vt/vtgate/engine/vindex_lookup.go (about)

     1  /*
     2  Copyright 2022 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  	vtgatepb "vitess.io/vitess/go/vt/proto/vtgate"
    23  
    24  	"vitess.io/vitess/go/vt/key"
    25  
    26  	"vitess.io/vitess/go/sqltypes"
    27  	querypb "vitess.io/vitess/go/vt/proto/query"
    28  	vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc"
    29  	"vitess.io/vitess/go/vt/vterrors"
    30  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    31  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    32  )
    33  
    34  var _ Primitive = (*VindexLookup)(nil)
    35  
    36  type VindexLookup struct {
    37  	Opcode Opcode
    38  
    39  	// The vindex to use to do the Map
    40  	Vindex vindexes.LookupPlanable
    41  
    42  	// Keyspace specifies the keyspace to send the query to.
    43  	Keyspace *vindexes.Keyspace
    44  
    45  	Arguments []string
    46  
    47  	// Values specifies the vindex values to use for routing.
    48  	Values []evalengine.Expr
    49  
    50  	// We fetch data in order to do the map from this primitive
    51  	Lookup Primitive
    52  
    53  	// This is the side that needs to be routed
    54  	SendTo *Route
    55  }
    56  
    57  // RouteType implements the Primitive interface
    58  func (vr *VindexLookup) RouteType() string {
    59  	return "VindexLookup"
    60  }
    61  
    62  // GetKeyspaceName implements the Primitive interface
    63  func (vr *VindexLookup) GetKeyspaceName() string {
    64  	return vr.SendTo.GetKeyspaceName()
    65  }
    66  
    67  // GetTableName implements the Primitive interface
    68  func (vr *VindexLookup) GetTableName() string {
    69  	return vr.SendTo.GetTableName()
    70  }
    71  
    72  // GetFields implements the Primitive interface
    73  func (vr *VindexLookup) GetFields(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) (*sqltypes.Result, error) {
    74  	return vr.SendTo.GetFields(ctx, vcursor, bindVars)
    75  }
    76  
    77  // NeedsTransaction implements the Primitive interface
    78  func (vr *VindexLookup) NeedsTransaction() bool {
    79  	return vr.SendTo.NeedsTransaction()
    80  }
    81  
    82  // TryExecute implements the Primitive interface
    83  func (vr *VindexLookup) TryExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool) (*sqltypes.Result, error) {
    84  	ids, err := vr.generateIds(vcursor, bindVars)
    85  	if err != nil {
    86  		return nil, err
    87  	}
    88  	results, err := vr.lookup(ctx, vcursor, ids)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  
    93  	dest, err := vr.mapVindexToDestination(ids, results, bindVars)
    94  	if err != nil {
    95  		return nil, err
    96  	}
    97  
    98  	return vr.SendTo.executeAfterLookup(ctx, vcursor, bindVars, wantfields, ids, dest)
    99  }
   100  
   101  func (vr *VindexLookup) mapVindexToDestination(ids []sqltypes.Value, results []*sqltypes.Result, bindVars map[string]*querypb.BindVariable) ([]key.Destination, error) {
   102  	dest, err := vr.Vindex.MapResult(ids, results)
   103  	if err != nil {
   104  		return nil, err
   105  	}
   106  
   107  	if vr.Opcode == IN {
   108  		valsBV := &querypb.BindVariable{Type: querypb.Type_TUPLE}
   109  		valsBV.Values = make([]*querypb.Value, 0, len(ids))
   110  		for _, value := range ids {
   111  			valsBV.Values = append(valsBV.Values, sqltypes.ValueToProto(value))
   112  		}
   113  		bindVars[ListVarName] = valsBV
   114  	}
   115  	return dest, nil
   116  }
   117  
   118  // TryStreamExecute implements the Primitive interface
   119  func (vr *VindexLookup) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error {
   120  	ids, err := vr.generateIds(vcursor, bindVars)
   121  	if err != nil {
   122  		return err
   123  	}
   124  
   125  	results, err := vr.lookup(ctx, vcursor, ids)
   126  	if err != nil {
   127  		return err
   128  	}
   129  
   130  	dest, err := vr.mapVindexToDestination(ids, results, bindVars)
   131  	if err != nil {
   132  		return err
   133  	}
   134  
   135  	return vr.SendTo.streamExecuteAfterLookup(ctx, vcursor, bindVars, wantfields, callback, ids, dest)
   136  }
   137  
   138  // Inputs implements the Primitive interface
   139  func (vr *VindexLookup) Inputs() []Primitive {
   140  	if vr.Lookup != nil {
   141  		return []Primitive{vr.Lookup, vr.SendTo}
   142  	}
   143  
   144  	return []Primitive{vr.SendTo}
   145  }
   146  
   147  // description implements the Primitive interface
   148  func (vr *VindexLookup) description() PrimitiveDescription {
   149  	other := map[string]any{}
   150  	if vr.Values != nil {
   151  		formattedValues := make([]string, 0, len(vr.Values))
   152  		for _, value := range vr.Values {
   153  			formattedValues = append(formattedValues, evalengine.FormatExpr(value))
   154  		}
   155  		other["Values"] = formattedValues
   156  	}
   157  	other["Vindex"] = vr.Vindex.String()
   158  
   159  	return PrimitiveDescription{
   160  		OperatorType: "VindexLookup",
   161  		Variant:      vr.Opcode.String(),
   162  		Keyspace:     vr.Keyspace,
   163  		Other:        other,
   164  	}
   165  }
   166  
   167  func (vr *VindexLookup) lookup(ctx context.Context, vcursor VCursor, ids []sqltypes.Value) ([]*sqltypes.Result, error) {
   168  	co := vr.Vindex.GetCommitOrder()
   169  	if co != vtgatepb.CommitOrder_NORMAL {
   170  		vcursor.Session().SetCommitOrder(co)
   171  		defer vcursor.Session().SetCommitOrder(vtgatepb.CommitOrder_NORMAL)
   172  	}
   173  	if ids[0].IsIntegral() || vr.Vindex.AllowBatch() {
   174  		return vr.executeBatch(ctx, vcursor, ids)
   175  	}
   176  	return vr.executeNonBatch(ctx, vcursor, ids)
   177  }
   178  
   179  func (vr *VindexLookup) executeNonBatch(ctx context.Context, vcursor VCursor, ids []sqltypes.Value) ([]*sqltypes.Result, error) {
   180  	results := make([]*sqltypes.Result, 0, len(ids))
   181  	// for non integral and binary type, fallback to send query per id
   182  	for _, id := range ids {
   183  		vars, err := sqltypes.BuildBindVariable([]any{id})
   184  		if err != nil {
   185  			return nil, err
   186  		}
   187  		bindVars := map[string]*querypb.BindVariable{
   188  			vr.Arguments[0]: vars,
   189  		}
   190  
   191  		var result *sqltypes.Result
   192  		if vr.Vindex.AutoCommitEnabled() {
   193  			result, err = vcursor.ExecutePrimitiveStandalone(ctx, vr.Lookup, bindVars, false)
   194  		} else {
   195  			result, err = vcursor.ExecutePrimitive(ctx, vr.Lookup, bindVars, false)
   196  		}
   197  		if err != nil {
   198  			return nil, err
   199  		}
   200  
   201  		rows := make([][]sqltypes.Value, 0, len(result.Rows))
   202  		for _, row := range result.Rows {
   203  			rows = append(rows, []sqltypes.Value{row[1]})
   204  		}
   205  		results = append(results, &sqltypes.Result{
   206  			Rows: rows,
   207  		})
   208  	}
   209  	return results, nil
   210  }
   211  
   212  func (vr *VindexLookup) executeBatch(ctx context.Context, vcursor VCursor, ids []sqltypes.Value) ([]*sqltypes.Result, error) {
   213  	results := make([]*sqltypes.Result, 0, len(ids))
   214  	// for integral types, batch query all ids and then map them back to the input order
   215  	vars, err := sqltypes.BuildBindVariable(ids)
   216  	if err != nil {
   217  		return nil, err
   218  	}
   219  	bindVars := map[string]*querypb.BindVariable{
   220  		vr.Arguments[0]: vars,
   221  	}
   222  
   223  	var result *sqltypes.Result
   224  	if vr.Vindex.AutoCommitEnabled() {
   225  		result, err = vcursor.ExecutePrimitiveStandalone(ctx, vr.Lookup, bindVars, false)
   226  	} else {
   227  		result, err = vcursor.ExecutePrimitive(ctx, vr.Lookup, bindVars, false)
   228  	}
   229  	if err != nil {
   230  		return nil, err
   231  	}
   232  
   233  	if err != nil {
   234  		return nil, vterrors.Wrapf(err, "failed while running the lookup query")
   235  	}
   236  	resultMap := make(map[string][][]sqltypes.Value)
   237  	for _, row := range result.Rows {
   238  		resultMap[row[0].ToString()] = append(resultMap[row[0].ToString()], []sqltypes.Value{row[1]})
   239  	}
   240  
   241  	for _, id := range ids {
   242  		results = append(results, &sqltypes.Result{
   243  			Rows: resultMap[id.ToString()],
   244  		})
   245  	}
   246  	return results, nil
   247  }
   248  
   249  func (vr *VindexLookup) generateIds(vcursor VCursor, bindVars map[string]*querypb.BindVariable) ([]sqltypes.Value, error) {
   250  	env := evalengine.EnvWithBindVars(bindVars, vcursor.ConnCollation())
   251  	value, err := env.Evaluate(vr.Values[0])
   252  	if err != nil {
   253  		return nil, err
   254  	}
   255  	switch vr.Opcode {
   256  	case Equal, EqualUnique:
   257  		return []sqltypes.Value{value.Value()}, nil
   258  	case IN:
   259  		return value.TupleValues(), nil
   260  	}
   261  	return nil, vterrors.Errorf(vtrpcpb.Code_INTERNAL, "opcode %s not supported for VindexLookup", vr.Opcode.String())
   262  }