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

     1  /*
     2  Copyright 2019 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  	"sort"
    23  
    24  	"vitess.io/vitess/go/vt/vtgate/evalengine"
    25  
    26  	topodatapb "vitess.io/vitess/go/vt/proto/topodata"
    27  
    28  	"vitess.io/vitess/go/sqltypes"
    29  	"vitess.io/vitess/go/vt/srvtopo"
    30  	"vitess.io/vitess/go/vt/vtgate/vindexes"
    31  
    32  	querypb "vitess.io/vitess/go/vt/proto/query"
    33  )
    34  
    35  var _ Primitive = (*Update)(nil)
    36  
    37  // VindexValues contains changed values for a vindex.
    38  type VindexValues struct {
    39  	PvMap  map[string]evalengine.Expr
    40  	Offset int // Offset from ownedVindexQuery to provide input decision for vindex update.
    41  }
    42  
    43  // Update represents the instructions to perform an update.
    44  type Update struct {
    45  	*DML
    46  
    47  	// ChangedVindexValues contains values for updated Vindexes during an update statement.
    48  	ChangedVindexValues map[string]*VindexValues
    49  
    50  	// Update does not take inputs
    51  	noInputs
    52  }
    53  
    54  // TryExecute performs a non-streaming exec.
    55  func (upd *Update) TryExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool) (*sqltypes.Result, error) {
    56  	ctx, cancelFunc := addQueryTimeout(ctx, vcursor, upd.QueryTimeout)
    57  	defer cancelFunc()
    58  
    59  	rss, _, err := upd.findRoute(ctx, vcursor, bindVars)
    60  	if err != nil {
    61  		return nil, err
    62  	}
    63  	err = allowOnlyPrimary(rss...)
    64  	if err != nil {
    65  		return nil, err
    66  	}
    67  
    68  	switch upd.Opcode {
    69  	case Unsharded:
    70  		return upd.execUnsharded(ctx, upd, vcursor, bindVars, rss)
    71  	case Equal, EqualUnique, IN, Scatter, ByDestination, SubShard, MultiEqual:
    72  		return upd.execMultiDestination(ctx, upd, vcursor, bindVars, rss, upd.updateVindexEntries)
    73  	default:
    74  		// Unreachable.
    75  		return nil, fmt.Errorf("unsupported opcode: %v", upd.Opcode)
    76  	}
    77  }
    78  
    79  // TryStreamExecute performs a streaming exec.
    80  func (upd *Update) TryStreamExecute(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, wantfields bool, callback func(*sqltypes.Result) error) error {
    81  	res, err := upd.TryExecute(ctx, vcursor, bindVars, wantfields)
    82  	if err != nil {
    83  		return err
    84  	}
    85  	return callback(res)
    86  
    87  }
    88  
    89  // GetFields fetches the field info.
    90  func (upd *Update) GetFields(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable) (*sqltypes.Result, error) {
    91  	return nil, fmt.Errorf("BUG: unreachable code for %q", upd.Query)
    92  }
    93  
    94  // updateVindexEntries performs an update when a vindex is being modified
    95  // by the statement.
    96  // Note: the commit order may be different from the DML order because it's possible
    97  // for DMLs to reuse existing transactions.
    98  // Note 2: While changes are being committed, the changing row could be
    99  // unreachable by either the new or old column values.
   100  func (upd *Update) updateVindexEntries(ctx context.Context, vcursor VCursor, bindVars map[string]*querypb.BindVariable, rss []*srvtopo.ResolvedShard) error {
   101  	if len(upd.ChangedVindexValues) == 0 {
   102  		return nil
   103  	}
   104  	queries := make([]*querypb.BoundQuery, len(rss))
   105  	for i := range rss {
   106  		queries[i] = &querypb.BoundQuery{Sql: upd.OwnedVindexQuery, BindVariables: bindVars}
   107  	}
   108  	subQueryResult, errors := vcursor.ExecuteMultiShard(ctx, upd, rss, queries, false /* rollbackOnError */, false /* canAutocommit */)
   109  	for _, err := range errors {
   110  		if err != nil {
   111  			return err
   112  		}
   113  	}
   114  
   115  	if len(subQueryResult.Rows) == 0 {
   116  		return nil
   117  	}
   118  
   119  	fieldColNumMap := make(map[string]int)
   120  	for colNum, field := range subQueryResult.Fields {
   121  		fieldColNumMap[field.Name] = colNum
   122  	}
   123  	env := evalengine.EnvWithBindVars(bindVars, vcursor.ConnCollation())
   124  
   125  	for _, row := range subQueryResult.Rows {
   126  		ksid, err := resolveKeyspaceID(ctx, vcursor, upd.KsidVindex, row[0:upd.KsidLength])
   127  		if err != nil {
   128  			return err
   129  		}
   130  
   131  		vindexTable, err := upd.GetSingleTable()
   132  		if err != nil {
   133  			return err
   134  		}
   135  		for _, colVindex := range vindexTable.ColumnVindexes {
   136  			// Skip this vindex if no rows are being changed
   137  			updColValues, ok := upd.ChangedVindexValues[colVindex.Name]
   138  			if !ok {
   139  				continue
   140  			}
   141  
   142  			offset := updColValues.Offset
   143  			if !row[offset].IsNull() {
   144  				val, err := evalengine.ToInt64(row[offset])
   145  				if err != nil {
   146  					return err
   147  				}
   148  				if val == int64(1) { // 1 means that the old and new value are same and vindex update is not required.
   149  					continue
   150  				}
   151  			}
   152  
   153  			fromIds := make([]sqltypes.Value, 0, len(colVindex.Columns))
   154  			var vindexColumnKeys []sqltypes.Value
   155  			for _, vCol := range colVindex.Columns {
   156  				// Fetch the column values.
   157  				origColValue := row[fieldColNumMap[vCol.String()]]
   158  				fromIds = append(fromIds, origColValue)
   159  				if colValue, exists := updColValues.PvMap[vCol.String()]; exists {
   160  					resolvedVal, err := env.Evaluate(colValue)
   161  					if err != nil {
   162  						return err
   163  					}
   164  					vindexColumnKeys = append(vindexColumnKeys, resolvedVal.Value())
   165  				} else {
   166  					// Set the column value to original as this column in vindex is not updated.
   167  					vindexColumnKeys = append(vindexColumnKeys, origColValue)
   168  				}
   169  			}
   170  
   171  			if colVindex.Owned {
   172  				if err := colVindex.Vindex.(vindexes.Lookup).Update(ctx, vcursor, fromIds, ksid, vindexColumnKeys); err != nil {
   173  					return err
   174  				}
   175  			} else {
   176  				allNulls := true
   177  				for _, key := range vindexColumnKeys {
   178  					allNulls = key.IsNull()
   179  					if !allNulls {
   180  						break
   181  					}
   182  				}
   183  
   184  				// All columns for this Vindex are set to null, so we can skip verification
   185  				if allNulls {
   186  					continue
   187  				}
   188  
   189  				// If values were supplied, we validate against keyspace id.
   190  				verified, err := vindexes.Verify(ctx, colVindex.Vindex, vcursor, [][]sqltypes.Value{vindexColumnKeys}, [][]byte{ksid})
   191  				if err != nil {
   192  					return err
   193  				}
   194  
   195  				if !verified[0] {
   196  					return fmt.Errorf("values %v for column %v does not map to keyspace ids", vindexColumnKeys, colVindex.Columns)
   197  				}
   198  			}
   199  		}
   200  	}
   201  	return nil
   202  }
   203  
   204  func (upd *Update) description() PrimitiveDescription {
   205  	other := map[string]any{
   206  		"Query":                upd.Query,
   207  		"Table":                upd.GetTableName(),
   208  		"OwnedVindexQuery":     upd.OwnedVindexQuery,
   209  		"MultiShardAutocommit": upd.MultiShardAutocommit,
   210  		"QueryTimeout":         upd.QueryTimeout,
   211  	}
   212  
   213  	addFieldsIfNotEmpty(upd.DML, other)
   214  
   215  	var changedVindexes []string
   216  	for k, v := range upd.ChangedVindexValues {
   217  		changedVindexes = append(changedVindexes, fmt.Sprintf("%s:%d", k, v.Offset))
   218  	}
   219  	sort.Strings(changedVindexes) // We sort these so random changes in the map order does not affect output
   220  	if len(changedVindexes) > 0 {
   221  		other["ChangedVindexValues"] = changedVindexes
   222  	}
   223  
   224  	return PrimitiveDescription{
   225  		OperatorType:     "Update",
   226  		Keyspace:         upd.Keyspace,
   227  		Variant:          upd.Opcode.String(),
   228  		TargetTabletType: topodatapb.TabletType_PRIMARY,
   229  		Other:            other,
   230  	}
   231  }