// execRequest executes the request using the provided planner. // It parses the sql into statements, iterates through the statements, creates // KV transactions and automatically retries them when possible, and executes // the (synchronous attempt of) schema changes. // It will accumulate a result in Response for each statement. // It will resume a SQL transaction, if one was previously open for this client. // // execRequest handles the mismatch between the SQL interface that the Executor // provides, based on statements being streamed from the client in the context // of a session, and the KV client.Txn interface, based on (possibly-retriable) // callbacks passed to be executed in the context of a transaction. Actual // execution of statements in the context of a KV txn is delegated to // runTxnAttempt(). // // Args: // txnState: State about about ongoing transaction (if any). The state will be // updated. func (e *Executor) execRequest(session *Session, sql string, copymsg copyMsg) StatementResults { var res StatementResults txnState := &session.TxnState planMaker := &session.planner var stmts parser.StatementList var err error log.VEventf(session.Ctx(), 2, "execRequest: %s", sql) if session.planner.copyFrom != nil { stmts, err = session.planner.ProcessCopyData(sql, copymsg) } else if copymsg != copyMsgNone { err = fmt.Errorf("unexpected copy command") } else { stmts, err = planMaker.parser.Parse(sql, parser.Syntax(session.Syntax)) } if err != nil { // A parse error occurred: we can't determine if there were multiple // statements or only one, so just pretend there was one. if txnState.txn != nil { // Rollback the txn. txnState.updateStateAndCleanupOnErr(err, e) } res.ResultList = append(res.ResultList, Result{Err: err}) return res } if len(stmts) == 0 { res.Empty = true return res } // If the planMaker wants config updates to be blocked, then block them. defer planMaker.blockConfigUpdatesMaybe(e)() for len(stmts) > 0 { // Each iteration consumes a transaction's worth of statements. inTxn := txnState.State != NoTxn execOpt := client.TxnExecOptions{ Clock: e.cfg.Clock, } // Figure out the statements out of which we're going to try to consume // this iteration. If we need to create an implicit txn, only one statement // can be consumed. stmtsToExec := stmts // If protoTS is set, the transaction proto sets its Orig and Max timestamps // to it each retry. var protoTS *hlc.Timestamp // We can AutoRetry the next batch of statements if we're in a clean state // (i.e. the next statements we're going to see are the first statements in // a transaction). if !inTxn { // Detect implicit transactions. if _, isBegin := stmts[0].(*parser.BeginTransaction); !isBegin { execOpt.AutoCommit = true stmtsToExec = stmtsToExec[:1] // Check for AS OF SYSTEM TIME. If it is present but not detected here, // it will raise an error later on. protoTS, err = isAsOf(planMaker, stmtsToExec[0], e.cfg.Clock.Now()) if err != nil { res.ResultList = append(res.ResultList, Result{Err: err}) return res } if protoTS != nil { planMaker.avoidCachedDescriptors = true defer func() { planMaker.avoidCachedDescriptors = false }() } } txnState.resetForNewSQLTxn(e, session) txnState.autoRetry = true txnState.sqlTimestamp = e.cfg.Clock.PhysicalTime() if execOpt.AutoCommit { txnState.txn.SetDebugName(sqlImplicitTxnName, 0) } else { txnState.txn.SetDebugName(sqlTxnName, 0) } } else { txnState.autoRetry = false } execOpt.AutoRetry = txnState.autoRetry if txnState.State == NoTxn { panic("we failed to initialize a txn") } // Now actually run some statements. var remainingStmts parser.StatementList var results []Result origState := txnState.State txnClosure := func(txn *client.Txn, opt *client.TxnExecOptions) error { if txnState.State == Open && txnState.txn != txn { panic(fmt.Sprintf("closure wasn't called in the txn we set up for it."+ "\ntxnState.txn:%+v\ntxn:%+v\ntxnState:%+v", txnState.txn, txn, txnState)) } txnState.txn = txn if protoTS != nil { setTxnTimestamps(txnState.txn, *protoTS) } var err error if results != nil { // Some results were produced by a previous attempt. Discard them. ResultList(results).Close() } results, remainingStmts, err = runTxnAttempt(e, planMaker, origState, txnState, opt, stmtsToExec) // TODO(andrei): Until #7881 fixed. if err == nil && txnState.State == Aborted { log.Errorf(session.Ctx(), "7881: txnState is Aborted without an error propagating. stmtsToExec: %s, "+ "results: %+v, remainingStmts: %s, txnState: %+v", stmtsToExec, results, remainingStmts, txnState) } return err } // This is where the magic happens - we ask db to run a KV txn and possibly retry it. txn := txnState.txn // this might be nil if the txn was already aborted. err := txn.Exec(execOpt, txnClosure) // Update the Err field of the last result if the error was coming from // auto commit. The error was generated outside of the txn closure, so it was not // set in any result. if err != nil { lastResult := &results[len(results)-1] if aErr, ok := err.(*client.AutoCommitError); ok { // TODO(andrei): Until #7881 fixed. { log.Eventf(session.Ctx(), "executor got AutoCommitError: %s\n"+ "txn: %+v\nexecOpt.AutoRetry %t, execOpt.AutoCommit:%t, stmts %+v, remaining %+v", aErr, txnState.txn.Proto, execOpt.AutoRetry, execOpt.AutoCommit, stmts, remainingStmts) if txnState.txn == nil { log.Errorf(session.Ctx(), "7881: AutoCommitError on nil txn: %s, "+ "txnState %+v, execOpt %+v, stmts %+v, remaining %+v", aErr, txnState, execOpt, stmts, remainingStmts) txnState.sp.SetBaggageItem(keyFor7881Sample, "sample me please") } } lastResult.Err = aErr e.TxnAbortCount.Inc(1) txn.CleanupOnError(err) } if lastResult.Err == nil { log.Fatalf(session.Ctx(), "error (%s) was returned, but it was not set in the last result (%v)", err, lastResult) } } res.ResultList = append(res.ResultList, results...) // Now make sense of the state we got into and update txnState. if (txnState.State == RestartWait || txnState.State == Aborted) && txnState.commitSeen { // A COMMIT got an error (retryable or not). Too bad, this txn is toast. // After we return a result for COMMIT (with the COMMIT pgwire tag), the // user can't send any more commands. e.TxnAbortCount.Inc(1) txn.CleanupOnError(err) txnState.resetStateAndTxn(NoTxn) } if execOpt.AutoCommit { // If execOpt.AutoCommit was set, then the txn no longer exists at this point. txnState.resetStateAndTxn(NoTxn) } // If we're no longer in a transaction, finish the trace. if txnState.State == NoTxn { txnState.finishSQLTxn(session.context) } // If the txn is in any state but Open, exec the schema changes. They'll // short-circuit themselves if the mutation that queued them has been // rolled back from the table descriptor. stmtsExecuted := stmts[:len(stmtsToExec)-len(remainingStmts)] if txnState.State != Open { planMaker.checkTestingVerifyMetadataInitialOrDie(e, stmts) planMaker.checkTestingVerifyMetadataOrDie(e, stmtsExecuted) // Exec the schema changers (if the txn rolled back, the schema changers // will short-circuit because the corresponding descriptor mutation is not // found). planMaker.releaseLeases() txnState.schemaChangers.execSchemaChanges(e, planMaker, res.ResultList) } else { // We're still in a txn, so we only check that the verifyMetadata callback // fails the first time it's run. The gossip update that will make the // callback succeed only happens when the txn is done. planMaker.checkTestingVerifyMetadataInitialOrDie(e, stmtsExecuted) } // Figure out what statements to run on the next iteration. if err != nil { // Don't execute anything further. stmts = nil } else if execOpt.AutoCommit { stmts = stmts[1:] } else { stmts = remainingStmts } } return res }
"github.com/cockroachdb/cockroach/pkg/sql/parser" "github.com/cockroachdb/cockroach/pkg/sql/sqlbase" "github.com/cockroachdb/cockroach/pkg/util/encoding" "github.com/pkg/errors" ) const ( // PgServerVersion is the latest version of postgres that we claim to support. PgServerVersion = "9.5.0" ) var varGen = map[string]func(p *planner) string{ `DATABASE`: func(p *planner) string { return p.session.Database }, `DEFAULT_TRANSACTION_ISOLATION`: func(p *planner) string { return p.session.DefaultIsolationLevel.String() }, `SYNTAX`: func(p *planner) string { return parser.Syntax(p.session.Syntax).String() }, `TIME ZONE`: func(p *planner) string { return p.session.Location.String() }, `TRANSACTION ISOLATION LEVEL`: func(p *planner) string { return p.txn.Proto.Isolation.String() }, `TRANSACTION PRIORITY`: func(p *planner) string { return p.txn.UserPriority.String() }, `MAX_INDEX_KEYS`: func(_ *planner) string { return "32" }, `SEARCH_PATH`: func(p *planner) string { return strings.Join(p.session.SearchPath, ", ") }, `SERVER_VERSION`: func(_ *planner) string { return PgServerVersion }, } var varNames = func() []string { res := make([]string, 0, len(varGen)) for vName := range varGen { res = append(res, vName) } sort.Strings(res) return res }()
// Prepare returns the result types of the given statement. pinfo may // contain partial type information for placeholders. Prepare will // populate the missing types. The column result types are returned (or // nil if there are no results). func (e *Executor) Prepare( query string, session *Session, pinfo parser.PlaceholderTypes, ) (ResultColumns, error) { log.VEventf(session.Ctx(), 2, "preparing: %s", query) var p parser.Parser stmts, err := p.Parse(query, parser.Syntax(session.Syntax)) if err != nil { return nil, err } switch len(stmts) { case 0: return nil, nil case 1: // ignore default: return nil, errors.Errorf("expected 1 statement, but found %d", len(stmts)) } stmt := stmts[0] if err = pinfo.ProcessPlaceholderAnnotations(stmt); err != nil { return nil, err } protoTS, err := isAsOf(&session.planner, stmt, e.cfg.Clock.Now()) if err != nil { return nil, err } session.planner.resetForBatch(e) session.planner.semaCtx.Placeholders.SetTypes(pinfo) session.planner.evalCtx.PrepareOnly = true // Prepare needs a transaction because it needs to retrieve db/table // descriptors for type checking. // TODO(andrei): is this OK? If we're preparing as part of a SQL txn, how do // we check that they're reading descriptors consistent with the txn in which // they'll be used? txn := client.NewTxn(session.Ctx(), *e.cfg.DB) txn.Proto.Isolation = session.DefaultIsolationLevel session.planner.setTxn(txn) defer session.planner.setTxn(nil) if protoTS != nil { session.planner.avoidCachedDescriptors = true defer func() { session.planner.avoidCachedDescriptors = false }() setTxnTimestamps(txn, *protoTS) } plan, err := session.planner.prepare(stmt) if err != nil { return nil, err } if plan == nil { return nil, nil } defer plan.Close() cols := plan.Columns() for _, c := range cols { if err := checkResultType(c.Typ); err != nil { return nil, err } } return cols, nil }