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  }