vitess.io/vitess@v0.16.2/go/vt/vtgate/plan_execute.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 vtgate 18 19 import ( 20 "context" 21 "time" 22 23 "vitess.io/vitess/go/vt/vtgate/logstats" 24 25 "vitess.io/vitess/go/sqltypes" 26 querypb "vitess.io/vitess/go/vt/proto/query" 27 vtrpcpb "vitess.io/vitess/go/vt/proto/vtrpc" 28 "vitess.io/vitess/go/vt/sqlparser" 29 "vitess.io/vitess/go/vt/vterrors" 30 "vitess.io/vitess/go/vt/vtgate/engine" 31 ) 32 33 type planExec func(ctx context.Context, plan *engine.Plan, vc *vcursorImpl, bindVars map[string]*querypb.BindVariable, startTime time.Time) error 34 type txResult func(sqlparser.StatementType, *sqltypes.Result) error 35 36 func (e *Executor) newExecute( 37 ctx context.Context, 38 safeSession *SafeSession, 39 sql string, 40 bindVars map[string]*querypb.BindVariable, 41 logStats *logstats.LogStats, 42 execPlan planExec, // used when there is a plan to execute 43 recResult txResult, // used when it's something simple like begin/commit/rollback/savepoint 44 ) error { 45 // 1: Prepare before planning and execution 46 47 // Start an implicit transaction if necessary. 48 err := e.startTxIfNecessary(ctx, safeSession) 49 if err != nil { 50 return err 51 } 52 53 if bindVars == nil { 54 bindVars = make(map[string]*querypb.BindVariable) 55 } 56 57 query, comments := sqlparser.SplitMarginComments(sql) 58 vcursor, err := newVCursorImpl(safeSession, comments, e, logStats, e.vm, e.VSchema(), e.resolver.resolver, e.serv, e.warnShardedOnly, e.pv) 59 if err != nil { 60 return err 61 } 62 63 // 2: Create a plan for the query 64 plan, stmt, err := e.getPlan(ctx, vcursor, query, comments, bindVars, safeSession, logStats) 65 execStart := e.logPlanningFinished(logStats, plan) 66 67 if err != nil { 68 safeSession.ClearWarnings() 69 return err 70 } 71 72 if plan.Type != sqlparser.StmtShow { 73 safeSession.ClearWarnings() 74 } 75 76 // add any warnings that the planner wants to add 77 for _, warning := range plan.Warnings { 78 safeSession.RecordWarning(warning) 79 } 80 81 result, err := e.handleTransactions(ctx, safeSession, plan, logStats, vcursor, stmt) 82 if err != nil { 83 return err 84 } 85 if result != nil { 86 return recResult(plan.Type, result) 87 } 88 89 // 3: Prepare for execution 90 err = e.addNeededBindVars(plan.BindVarNeeds, bindVars, safeSession) 91 if err != nil { 92 logStats.Error = err 93 return err 94 } 95 96 if plan.Instructions.NeedsTransaction() { 97 return e.insideTransaction(ctx, safeSession, logStats, 98 func() error { 99 return execPlan(ctx, plan, vcursor, bindVars, execStart) 100 }) 101 } 102 103 return execPlan(ctx, plan, vcursor, bindVars, execStart) 104 } 105 106 // handleTransactions deals with transactional queries: begin, commit, rollback and savepoint management 107 func (e *Executor) handleTransactions( 108 ctx context.Context, 109 safeSession *SafeSession, 110 plan *engine.Plan, 111 logStats *logstats.LogStats, 112 vcursor *vcursorImpl, 113 stmt sqlparser.Statement, 114 ) (*sqltypes.Result, error) { 115 // We need to explicitly handle errors, and begin/commit/rollback, since these control transactions. Everything else 116 // will fall through and be handled through planning 117 switch plan.Type { 118 case sqlparser.StmtBegin: 119 qr, err := e.handleBegin(ctx, safeSession, logStats, stmt) 120 return qr, err 121 case sqlparser.StmtCommit: 122 qr, err := e.handleCommit(ctx, safeSession, logStats) 123 return qr, err 124 case sqlparser.StmtRollback: 125 qr, err := e.handleRollback(ctx, safeSession, logStats) 126 return qr, err 127 case sqlparser.StmtSavepoint: 128 qr, err := e.handleSavepoint(ctx, safeSession, plan.Original, "Savepoint", logStats, func(_ string) (*sqltypes.Result, error) { 129 // Safely to ignore as there is no transaction. 130 return &sqltypes.Result{}, nil 131 }, vcursor.ignoreMaxMemoryRows) 132 return qr, err 133 case sqlparser.StmtSRollback: 134 qr, err := e.handleSavepoint(ctx, safeSession, plan.Original, "Rollback Savepoint", logStats, func(query string) (*sqltypes.Result, error) { 135 // Error as there is no transaction, so there is no savepoint that exists. 136 return nil, vterrors.NewErrorf(vtrpcpb.Code_NOT_FOUND, vterrors.SPDoesNotExist, "SAVEPOINT does not exist: %s", query) 137 }, vcursor.ignoreMaxMemoryRows) 138 return qr, err 139 case sqlparser.StmtRelease: 140 qr, err := e.handleSavepoint(ctx, safeSession, plan.Original, "Release Savepoint", logStats, func(query string) (*sqltypes.Result, error) { 141 // Error as there is no transaction, so there is no savepoint that exists. 142 return nil, vterrors.NewErrorf(vtrpcpb.Code_NOT_FOUND, vterrors.SPDoesNotExist, "SAVEPOINT does not exist: %s", query) 143 }, vcursor.ignoreMaxMemoryRows) 144 return qr, err 145 } 146 return nil, nil 147 } 148 149 func (e *Executor) startTxIfNecessary(ctx context.Context, safeSession *SafeSession) error { 150 if !safeSession.Autocommit && !safeSession.InTransaction() { 151 if err := e.txConn.Begin(ctx, safeSession, nil); err != nil { 152 return err 153 } 154 } 155 return nil 156 } 157 158 func (e *Executor) insideTransaction(ctx context.Context, safeSession *SafeSession, logStats *logstats.LogStats, execPlan func() error) error { 159 mustCommit := false 160 if safeSession.Autocommit && !safeSession.InTransaction() { 161 mustCommit = true 162 if err := e.txConn.Begin(ctx, safeSession, nil); err != nil { 163 return err 164 } 165 // The defer acts as a failsafe. If commit was successful, 166 // the rollback will be a no-op. 167 defer e.txConn.Rollback(ctx, safeSession) // nolint:errcheck 168 } 169 170 // The SetAutocommitable flag should be same as mustCommit. 171 // If we started a transaction because of autocommit, then mustCommit 172 // will be true, which means that we can autocommit. If we were already 173 // in a transaction, it means that the app started it, or we are being 174 // called recursively. If so, we cannot autocommit because whatever we 175 // do is likely not final. 176 // The control flow is such that autocommitable can only be turned on 177 // at the beginning, but never after. 178 safeSession.SetAutocommittable(mustCommit) 179 180 // If we want to instantly commit the query, then there is no need to add savepoints. 181 // Any partial failure of the query will be taken care by rollback. 182 safeSession.SetSavepointState(!mustCommit) 183 184 // Execute! 185 err := execPlan() 186 if err != nil { 187 return err 188 } 189 190 if mustCommit { 191 commitStart := time.Now() 192 if err := e.txConn.Commit(ctx, safeSession); err != nil { 193 return err 194 } 195 logStats.CommitTime = time.Since(commitStart) 196 } 197 return nil 198 } 199 200 func (e *Executor) executePlan( 201 ctx context.Context, 202 safeSession *SafeSession, 203 plan *engine.Plan, 204 vcursor *vcursorImpl, 205 bindVars map[string]*querypb.BindVariable, 206 logStats *logstats.LogStats, 207 execStart time.Time, 208 ) (*sqltypes.Result, error) { 209 210 // 4: Execute! 211 qr, err := vcursor.ExecutePrimitive(ctx, plan.Instructions, bindVars, true) 212 213 // 5: Log and add statistics 214 e.setLogStats(logStats, plan, vcursor, execStart, err, qr) 215 216 // Check if there was partial DML execution. If so, rollback the effect of the partially executed query. 217 if err != nil { 218 return nil, e.rollbackExecIfNeeded(ctx, safeSession, bindVars, logStats, err) 219 } 220 return qr, nil 221 } 222 223 // rollbackExecIfNeeded rollbacks the partial execution if earlier it was detected that it needs partial query execution to be rolled back. 224 func (e *Executor) rollbackExecIfNeeded(ctx context.Context, safeSession *SafeSession, bindVars map[string]*querypb.BindVariable, logStats *logstats.LogStats, err error) error { 225 if safeSession.InTransaction() && safeSession.IsRollbackSet() { 226 rErr := e.rollbackPartialExec(ctx, safeSession, bindVars, logStats) 227 return vterrors.Wrap(err, rErr.Error()) 228 } 229 return err 230 } 231 232 // rollbackPartialExec rollbacks to the savepoint or rollbacks transaction based on the value set on SafeSession.rollbackOnPartialExec. 233 // Once, it is used the variable is reset. 234 // If it fails to rollback to the previous savepoint then, the transaction is forced to be rolled back. 235 func (e *Executor) rollbackPartialExec(ctx context.Context, safeSession *SafeSession, bindVars map[string]*querypb.BindVariable, logStats *logstats.LogStats) error { 236 var err error 237 238 // needs to rollback only once. 239 rQuery := safeSession.rollbackOnPartialExec 240 if rQuery != txRollback { 241 safeSession.SavepointRollback() 242 _, _, err := e.execute(ctx, safeSession, rQuery, bindVars, logStats) 243 if err == nil { 244 return vterrors.New(vtrpcpb.Code_ABORTED, "reverted partial DML execution failure") 245 } 246 // not able to rollback changes of the failed query, so have to abort the complete transaction. 247 } 248 249 // abort the transaction. 250 _ = e.txConn.Rollback(ctx, safeSession) 251 var errMsg = "transaction rolled back to reverse changes of partial DML execution" 252 if err != nil { 253 return vterrors.Wrap(err, errMsg) 254 } 255 return vterrors.New(vtrpcpb.Code_ABORTED, errMsg) 256 } 257 258 func (e *Executor) setLogStats(logStats *logstats.LogStats, plan *engine.Plan, vcursor *vcursorImpl, execStart time.Time, err error, qr *sqltypes.Result) { 259 logStats.StmtType = plan.Type.String() 260 logStats.ActiveKeyspace = vcursor.keyspace 261 logStats.TablesUsed = plan.TablesUsed 262 logStats.TabletType = vcursor.TabletType().String() 263 errCount := e.logExecutionEnd(logStats, execStart, plan, err, qr) 264 plan.AddStats(1, time.Since(logStats.StartTime), logStats.ShardQueries, logStats.RowsAffected, logStats.RowsReturned, errCount) 265 } 266 267 func (e *Executor) logExecutionEnd(logStats *logstats.LogStats, execStart time.Time, plan *engine.Plan, err error, qr *sqltypes.Result) uint64 { 268 logStats.ExecuteTime = time.Since(execStart) 269 270 e.updateQueryCounts(plan.Instructions.RouteType(), plan.Instructions.GetKeyspaceName(), plan.Instructions.GetTableName(), int64(logStats.ShardQueries)) 271 272 var errCount uint64 273 if err != nil { 274 logStats.Error = err 275 errCount = 1 276 } else { 277 logStats.RowsAffected = qr.RowsAffected 278 logStats.RowsReturned = uint64(len(qr.Rows)) 279 } 280 return errCount 281 } 282 283 func (e *Executor) logPlanningFinished(logStats *logstats.LogStats, plan *engine.Plan) time.Time { 284 execStart := time.Now() 285 if plan != nil { 286 logStats.StmtType = plan.Type.String() 287 } 288 logStats.PlanTime = execStart.Sub(logStats.StartTime) 289 return execStart 290 }