Example #1
0
// 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
}
Example #2
0
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
}