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 }