// 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 }
func (c *cliState) doCheckStatement(startState, contState, execState cliStateEnum) cliStateEnum { // From here on, client-side syntax checking is enabled. var parser parser.Parser parsedStmts, err := parser.Parse(c.concatLines, c.syntax) if err != nil { _ = c.invalidSyntax(0, "statement ignored: %v", err) // Even on failure, add the last (erroneous) lines as-is to the // history, so that the user can recall them later to fix them. for i := c.partialStmtsLen; i < len(c.partialLines); i++ { c.addHistory(c.partialLines[i]) } // Stop here if exiterr is set. if c.errExit { return cliStop } // Otherwise, remove the erroneous lines from the buffered input, // then try again. c.partialLines = c.partialLines[:c.partialStmtsLen] if len(c.partialLines) == 0 { return startState } return contState } if !isInteractive { return execState } if c.normalizeHistory { // Add statements, not lines, to the history. for i := c.partialStmtsLen; i < len(parsedStmts); i++ { c.addHistory(parsedStmts[i].String() + ";") } } else { // Add the last lines received to the history. for i := c.partialStmtsLen; i < len(c.partialLines); i++ { c.addHistory(c.partialLines[i]) } } // Replace the last entered lines by the last entered statements. c.partialLines = c.partialLines[:c.partialStmtsLen] for i := c.partialStmtsLen; i < len(parsedStmts); i++ { c.partialLines = append(c.partialLines, parsedStmts[i].String()+";") } nextState := execState // In interactive mode, we make some additional effort to help the user: // if the entry so far is starting an incomplete transaction, push // the user to enter input over multiple lines. if endsWithIncompleteTxn(parsedStmts) && c.lastInputLine != "" { if c.partialStmtsLen == 0 { fmt.Fprintln(osStderr, "Now adding input for a multi-line SQL transaction client-side.\n"+ "Press Enter two times to send the SQL text collected so far to the server, or Ctrl+C to cancel.") } nextState = contState } c.partialStmtsLen = len(parsedStmts) return nextState }