github.com/cockroachdb/cockroach@v20.2.0-alpha.1+incompatible/pkg/cli/sql.go (about)

     1  // Copyright 2015 The Cockroach Authors.
     2  //
     3  // Use of this software is governed by the Business Source License
     4  // included in the file licenses/BSL.txt.
     5  //
     6  // As of the Change Date specified in that file, in accordance with
     7  // the Business Source License, use of this software will be governed
     8  // by the Apache License, Version 2.0, included in the file
     9  // licenses/APL.txt.
    10  
    11  package cli
    12  
    13  import (
    14  	"bufio"
    15  	"bytes"
    16  	"context"
    17  	"fmt"
    18  	"io"
    19  	"net/url"
    20  	"os"
    21  	"os/exec"
    22  	"path/filepath"
    23  	"regexp"
    24  	"sort"
    25  	"strconv"
    26  	"strings"
    27  	"time"
    28  
    29  	"github.com/cockroachdb/cockroach/pkg/base"
    30  	"github.com/cockroachdb/cockroach/pkg/cli/cliflags"
    31  	"github.com/cockroachdb/cockroach/pkg/roachpb"
    32  	"github.com/cockroachdb/cockroach/pkg/sql"
    33  	"github.com/cockroachdb/cockroach/pkg/sql/lex"
    34  	"github.com/cockroachdb/cockroach/pkg/sql/parser"
    35  	"github.com/cockroachdb/cockroach/pkg/sql/pgwire/pgerror"
    36  	"github.com/cockroachdb/cockroach/pkg/sql/sem/builtins"
    37  	"github.com/cockroachdb/cockroach/pkg/util/envutil"
    38  	"github.com/cockroachdb/cockroach/pkg/util/log"
    39  	"github.com/cockroachdb/errors"
    40  	readline "github.com/knz/go-libedit"
    41  	isatty "github.com/mattn/go-isatty"
    42  	"github.com/spf13/cobra"
    43  )
    44  
    45  const (
    46  	welcomeMessage = `#
    47  # Welcome to the CockroachDB SQL shell.
    48  # All statements must be terminated by a semicolon.
    49  # To exit, type: \q.
    50  #
    51  `
    52  	helpMessageFmt = `You are using 'cockroach sql', CockroachDB's lightweight SQL client.
    53  Type:
    54    \? or "help"      print this help.
    55    \q, quit, exit    exit the shell (Ctrl+C/Ctrl+D also supported).
    56    \! CMD            run an external command and print its results on standard output.
    57    \| CMD            run an external command and run its output as SQL statements.
    58    \set [NAME]       set a client-side flag or (without argument) print the current settings.
    59    \unset NAME       unset a flag.
    60    \show             during a multi-line statement or transaction, show the SQL entered so far.
    61    \h [NAME]         help on syntax of SQL commands.
    62    \hf [NAME]        help on SQL built-in functions.
    63    \l                list all databases in the CockroachDB cluster.
    64    \dt               show the tables of the current schema in the current database.
    65    \du               list the users for all databases.
    66    \d [TABLE]        show details about columns in the specified table, or alias for '\dt' if no table is specified.
    67  %s
    68  More documentation about our SQL dialect and the CLI shell is available online:
    69  %s
    70  %s`
    71  
    72  	demoCommandsHelp = `
    73  Commands specific to the demo shell (EXPERIMENTAL):
    74    \demo ls                     list the demo nodes and their connection URLs.
    75    \demo shutdown <nodeid>      stop a demo node.
    76    \demo restart <nodeid>       restart a stopped demo node.
    77    \demo decommission <nodeid>  decommission a node.
    78    \demo recommission <nodeid>  recommission a node.
    79  `
    80  
    81  	defaultPromptPattern = "%n@%M/%/%x>"
    82  
    83  	// debugPromptPattern avoids substitution patterns that require a db roundtrip.
    84  	debugPromptPattern = "%n@%M>"
    85  )
    86  
    87  // sqlShellCmd opens a sql shell.
    88  var sqlShellCmd = &cobra.Command{
    89  	Use:   "sql [options]",
    90  	Short: "open a sql shell",
    91  	Long: `
    92  Open a sql shell running against a cockroach database.
    93  `,
    94  	Args: cobra.NoArgs,
    95  	RunE: MaybeDecorateGRPCError(runTerm),
    96  }
    97  
    98  // cliState defines the current state of the CLI during
    99  // command-line processing.
   100  type cliState struct {
   101  	conn *sqlConn
   102  	// ins is used to read lines if isInteractive is true.
   103  	ins readline.EditLine
   104  	// buf is used to read lines if isInteractive is false.
   105  	buf *bufio.Reader
   106  
   107  	// Options
   108  	//
   109  	// Determines whether to stop the client upon encountering an error.
   110  	errExit bool
   111  	// Determines whether to perform client-side syntax checking.
   112  	checkSyntax bool
   113  
   114  	// The prompt at the beginning of a multi-line entry.
   115  	fullPrompt string
   116  	// The prompt on a continuation line in a multi-line entry.
   117  	continuePrompt string
   118  	// Which prompt to use to populate currentPrompt.
   119  	useContinuePrompt bool
   120  	// The current prompt, either fullPrompt or continuePrompt.
   121  	currentPrompt string
   122  	// The string used to produce the value of fullPrompt.
   123  	customPromptPattern string
   124  
   125  	// State
   126  	//
   127  	// lastInputLine is the last valid line obtained from readline.
   128  	lastInputLine string
   129  	// atEOF indicates whether the last call to readline signaled EOF on input.
   130  	atEOF bool
   131  	// lastKnownTxnStatus reports the last known transaction
   132  	// status. Erased after every statement executed, until the next
   133  	// query to the server updates it.
   134  	lastKnownTxnStatus string
   135  
   136  	// forwardLines is the array of lookahead lines. This gets
   137  	// populated when there is more than one line of input
   138  	// in the data read by ReadLine(), which can happen
   139  	// when copy-pasting.
   140  	forwardLines []string
   141  
   142  	// partialLines is the array of lines accumulated so far in a
   143  	// multi-line entry.
   144  	partialLines []string
   145  
   146  	// partialStmtsLen represents the number of entries in partialLines
   147  	// parsed successfully so far. It grows larger than zero whenever 1)
   148  	// syntax checking is enabled and 2) multi-statement (as opposed to
   149  	// multi-line) entry starts, i.e. when the shell decides to continue
   150  	// inputting statements even after a full statement followed by a
   151  	// semicolon was read successfully. This is currently used for
   152  	// multi-line transaction editing.
   153  	partialStmtsLen int
   154  
   155  	// concatLines is the concatenation of partialLines, computed during
   156  	// doCheckStatement and then reused in doRunStatement().
   157  	concatLines string
   158  
   159  	// exitErr defines the error to report to the user upon termination.
   160  	// This can carry over from one line of input to another. For
   161  	// example in the interactive shell, a statement causing a SQL
   162  	// error, followed by lines of whitespace or SQL comments, followed
   163  	// by Ctrl+D, causes the shell to terminate with an error --
   164  	// reporting the status of the last valid SQL statement executed.
   165  	exitErr error
   166  
   167  	// autoTrace, when non-empty, encloses the executed statements
   168  	// by suitable SET TRACING and SHOW TRACE FOR SESSION statements.
   169  	autoTrace string
   170  }
   171  
   172  // cliStateEnum drives the CLI state machine in runInteractive().
   173  type cliStateEnum int
   174  
   175  const (
   176  	cliStart cliStateEnum = iota
   177  	cliStop
   178  
   179  	// Querying the server for the current transaction status
   180  	// and setting the prompt accordingly.
   181  	cliRefreshPrompt
   182  
   183  	// Just before reading the first line of a potentially multi-line
   184  	// statement.
   185  	cliStartLine
   186  
   187  	// Just before reading the 2nd line or after in a multi-line
   188  	// statement.
   189  	cliContinueLine
   190  
   191  	// Actually reading the input and checking for input errors.
   192  	cliReadLine
   193  
   194  	// Determine to do with the newly input line.
   195  	cliDecidePath
   196  
   197  	// Process the first line of a (possibly multi-line) entry.
   198  	cliProcessFirstLine
   199  
   200  	// Check and handle for client-side commands.
   201  	cliHandleCliCmd
   202  
   203  	// Concatenate the inputs so far, discard entry made only of whitespace
   204  	// and/or comments, and check for semicolons.
   205  	cliPrepareStatementLine
   206  
   207  	// Perform syntax validation if enabled with check_syntax,
   208  	// and possibly trigger entry for multi-line transactions.
   209  	cliCheckStatement
   210  
   211  	// Actually run the SQL buffered so far.
   212  	cliRunStatement
   213  )
   214  
   215  // printCliHelp prints a short inline help about the CLI.
   216  func (c *cliState) printCliHelp() {
   217  	demoHelpStr := ""
   218  	if demoCtx.transientCluster != nil {
   219  		demoHelpStr = demoCommandsHelp
   220  	}
   221  	fmt.Printf(helpMessageFmt,
   222  		demoHelpStr,
   223  		base.DocsURL("sql-statements.html"),
   224  		base.DocsURL("use-the-built-in-sql-client.html"),
   225  	)
   226  	fmt.Println()
   227  }
   228  
   229  const noLineEditor readline.EditLine = -1
   230  
   231  func (c *cliState) hasEditor() bool {
   232  	return c.ins != noLineEditor
   233  }
   234  
   235  // addHistory persists a line of input to the readline history file.
   236  func (c *cliState) addHistory(line string) {
   237  	if !c.hasEditor() || len(line) == 0 {
   238  		return
   239  	}
   240  
   241  	// ins.AddHistory will push command into memory and try to
   242  	// persist to disk (if ins's history file is set). err can
   243  	// be not nil only if it got a IO error while trying to persist.
   244  	if err := c.ins.AddHistory(line); err != nil {
   245  		log.Warningf(context.TODO(), "cannot save command-line history: %s", err)
   246  		log.Info(context.TODO(), "command-line history will not be saved in this session")
   247  		c.ins.SetAutoSaveHistory("", false)
   248  	}
   249  }
   250  
   251  func (c *cliState) invalidSyntax(
   252  	nextState cliStateEnum, format string, args ...interface{},
   253  ) cliStateEnum {
   254  	fmt.Fprint(stderr, "invalid syntax: ")
   255  	fmt.Fprintf(stderr, format, args...)
   256  	fmt.Fprintln(stderr)
   257  	c.exitErr = errInvalidSyntax
   258  	return nextState
   259  }
   260  
   261  func (c *cliState) invalidOptSet(nextState cliStateEnum, args []string) cliStateEnum {
   262  	return c.invalidSyntax(nextState, `\set %s. Try \? for help.`, strings.Join(args, " "))
   263  }
   264  
   265  func (c *cliState) invalidOptionChange(nextState cliStateEnum, opt string) cliStateEnum {
   266  	fmt.Fprintf(stderr, "cannot change option during multi-line editing: %s\n", opt)
   267  	return nextState
   268  }
   269  
   270  func (c *cliState) internalServerError(nextState cliStateEnum, err error) cliStateEnum {
   271  	fmt.Fprintf(stderr, "internal server error: %v\n", err)
   272  	c.exitErr = err
   273  	return nextState
   274  }
   275  
   276  var options = map[string]struct {
   277  	description               string
   278  	isBoolean                 bool
   279  	validDuringMultilineEntry bool
   280  	set                       func(c *cliState, val string) error
   281  	reset                     func(c *cliState) error
   282  	// display is used to retrieve the current value.
   283  	display    func(c *cliState) string
   284  	deprecated bool
   285  }{
   286  	`auto_trace`: {
   287  		description:               "automatically run statement tracing on each executed statement",
   288  		isBoolean:                 false,
   289  		validDuringMultilineEntry: false,
   290  		set: func(c *cliState, val string) error {
   291  			val = strings.ToLower(strings.TrimSpace(val))
   292  			switch val {
   293  			case "false", "0", "off":
   294  				c.autoTrace = ""
   295  			case "true", "1":
   296  				val = "on"
   297  				fallthrough
   298  			default:
   299  				c.autoTrace = "on, " + val
   300  			}
   301  			return nil
   302  		},
   303  		reset: func(c *cliState) error {
   304  			c.autoTrace = ""
   305  			return nil
   306  		},
   307  		display: func(c *cliState) string {
   308  			if c.autoTrace == "" {
   309  				return "off"
   310  			}
   311  			return c.autoTrace
   312  		},
   313  	},
   314  	`display_format`: {
   315  		description:               "the output format for tabular data (table, csv, tsv, html, sql, records, raw)",
   316  		isBoolean:                 false,
   317  		validDuringMultilineEntry: true,
   318  		set: func(_ *cliState, val string) error {
   319  			return cliCtx.tableDisplayFormat.Set(val)
   320  		},
   321  		reset: func(_ *cliState) error {
   322  			displayFormat := tableDisplayTSV
   323  			if cliCtx.terminalOutput {
   324  				displayFormat = tableDisplayTable
   325  			}
   326  			cliCtx.tableDisplayFormat = displayFormat
   327  			return nil
   328  		},
   329  		display: func(_ *cliState) string { return cliCtx.tableDisplayFormat.String() },
   330  	},
   331  	`echo`: {
   332  		description:               "show SQL queries before they are sent to the server",
   333  		isBoolean:                 true,
   334  		validDuringMultilineEntry: false,
   335  		set:                       func(_ *cliState, _ string) error { sqlCtx.echo = true; return nil },
   336  		reset:                     func(_ *cliState) error { sqlCtx.echo = false; return nil },
   337  		display:                   func(_ *cliState) string { return strconv.FormatBool(sqlCtx.echo) },
   338  	},
   339  	`errexit`: {
   340  		description:               "exit the shell upon a query error",
   341  		isBoolean:                 true,
   342  		validDuringMultilineEntry: true,
   343  		set:                       func(c *cliState, _ string) error { c.errExit = true; return nil },
   344  		reset:                     func(c *cliState) error { c.errExit = false; return nil },
   345  		display:                   func(c *cliState) string { return strconv.FormatBool(c.errExit) },
   346  	},
   347  	`check_syntax`: {
   348  		description:               "check the SQL syntax before running a query (needs SHOW SYNTAX support on the server)",
   349  		isBoolean:                 true,
   350  		validDuringMultilineEntry: false,
   351  		set:                       func(c *cliState, _ string) error { c.checkSyntax = true; return nil },
   352  		reset:                     func(c *cliState) error { c.checkSyntax = false; return nil },
   353  		display:                   func(c *cliState) string { return strconv.FormatBool(c.checkSyntax) },
   354  	},
   355  	`show_times`: {
   356  		description:               "display the execution time after each query",
   357  		isBoolean:                 true,
   358  		validDuringMultilineEntry: true,
   359  		set:                       func(_ *cliState, _ string) error { sqlCtx.showTimes = true; return nil },
   360  		reset:                     func(_ *cliState) error { sqlCtx.showTimes = false; return nil },
   361  		display:                   func(_ *cliState) string { return strconv.FormatBool(sqlCtx.showTimes) },
   362  	},
   363  	`smart_prompt`: {
   364  		description:               "deprecated",
   365  		isBoolean:                 true,
   366  		validDuringMultilineEntry: false,
   367  		set:                       func(c *cliState, _ string) error { return nil },
   368  		reset:                     func(c *cliState) error { return nil },
   369  		display:                   func(c *cliState) string { return "false" },
   370  		deprecated:                true,
   371  	},
   372  	`prompt1`: {
   373  		description:               "prompt string to use before each command (the following are expanded: %M full host, %m host, %> port number, %n user, %/ database, %x txn status)",
   374  		isBoolean:                 false,
   375  		validDuringMultilineEntry: true,
   376  		set: func(c *cliState, val string) error {
   377  			c.customPromptPattern = val
   378  			return nil
   379  		},
   380  		reset: func(c *cliState) error {
   381  			c.customPromptPattern = defaultPromptPattern
   382  			return nil
   383  		},
   384  		display: func(c *cliState) string { return c.customPromptPattern },
   385  	},
   386  }
   387  
   388  // optionNames retains the names of every option in the map above in sorted
   389  // order. We want them sorted to ensure the output of \? is deterministic.
   390  var optionNames = func() []string {
   391  	names := make([]string, 0, len(options))
   392  	for k := range options {
   393  		names = append(names, k)
   394  	}
   395  	sort.Strings(names)
   396  	return names
   397  }()
   398  
   399  // handleSet supports the \set client-side command.
   400  func (c *cliState) handleSet(args []string, nextState, errState cliStateEnum) cliStateEnum {
   401  	if len(args) == 0 {
   402  		optData := make([][]string, 0, len(options))
   403  		for _, n := range optionNames {
   404  			if options[n].deprecated {
   405  				continue
   406  			}
   407  			optData = append(optData, []string{n, options[n].display(c), options[n].description})
   408  		}
   409  		err := printQueryOutput(os.Stdout,
   410  			[]string{"Option", "Value", "Description"},
   411  			newRowSliceIter(optData, "lll" /*align*/))
   412  		if err != nil {
   413  			panic(err)
   414  		}
   415  
   416  		return nextState
   417  	}
   418  
   419  	if len(args) == 1 {
   420  		// Try harder to find a value.
   421  		args = strings.SplitN(args[0], "=", 2)
   422  	}
   423  
   424  	opt, ok := options[args[0]]
   425  	if !ok {
   426  		return c.invalidOptSet(errState, args)
   427  	}
   428  	if len(c.partialLines) > 0 && !opt.validDuringMultilineEntry {
   429  		return c.invalidOptionChange(errState, args[0])
   430  	}
   431  
   432  	// Determine which value to use.
   433  	var val string
   434  	switch len(args) {
   435  	case 1:
   436  		val = "true"
   437  	case 2:
   438  		val = args[1]
   439  	default:
   440  		return c.invalidOptSet(errState, args)
   441  	}
   442  
   443  	// Run the command.
   444  	var err error
   445  	if !opt.isBoolean {
   446  		err = opt.set(c, val)
   447  	} else {
   448  		switch val {
   449  		case "true", "1", "on":
   450  			err = opt.set(c, "true")
   451  		case "false", "0", "off":
   452  			err = opt.reset(c)
   453  		default:
   454  			return c.invalidOptSet(errState, args)
   455  		}
   456  	}
   457  
   458  	if err != nil {
   459  		fmt.Fprintf(stderr, "\\set %s: %v\n", strings.Join(args, " "), err)
   460  		return errState
   461  	}
   462  
   463  	return nextState
   464  }
   465  
   466  // handleUnset supports the \unset client-side command.
   467  func (c *cliState) handleUnset(args []string, nextState, errState cliStateEnum) cliStateEnum {
   468  	if len(args) != 1 {
   469  		return c.invalidSyntax(errState, `\unset %s. Try \? for help.`, strings.Join(args, " "))
   470  	}
   471  	opt, ok := options[args[0]]
   472  	if !ok {
   473  		return c.invalidSyntax(errState, `\unset %s. Try \? for help.`, strings.Join(args, " "))
   474  	}
   475  	if len(c.partialLines) > 0 && !opt.validDuringMultilineEntry {
   476  		return c.invalidOptionChange(errState, args[0])
   477  	}
   478  	if err := opt.reset(c); err != nil {
   479  		fmt.Fprintf(stderr, "\\unset %s: %v\n", args[0], err)
   480  		return errState
   481  	}
   482  	return nextState
   483  }
   484  
   485  func isEndOfStatement(lastTok int) bool {
   486  	return lastTok == ';' || lastTok == parser.HELPTOKEN
   487  }
   488  
   489  // handleDemo handles operations on \demo.
   490  // This can only be done from `cockroach demo`.
   491  func (c *cliState) handleDemo(cmd []string, nextState, errState cliStateEnum) cliStateEnum {
   492  	// A transient cluster signifies the presence of `cockroach demo`.
   493  	if demoCtx.transientCluster == nil {
   494  		return c.invalidSyntax(errState, `\demo can only be run with cockroach demo`)
   495  	}
   496  
   497  	if len(cmd) == 1 && cmd[0] == "ls" {
   498  		demoCtx.transientCluster.listDemoNodes(os.Stdout, false /* justOne */)
   499  		return nextState
   500  	}
   501  
   502  	if len(cmd) != 2 {
   503  		return c.invalidSyntax(errState, `\demo expects 2 parameters`)
   504  	}
   505  
   506  	nodeID, err := strconv.ParseInt(cmd[1], 10, 32)
   507  	if err != nil {
   508  		return c.invalidSyntax(
   509  			errState,
   510  			"%s",
   511  			errors.Wrapf(err, "cannot convert %s to string", cmd[2]).Error(),
   512  		)
   513  	}
   514  
   515  	switch cmd[0] {
   516  	case "shutdown":
   517  		if err := demoCtx.transientCluster.DrainAndShutdown(roachpb.NodeID(nodeID)); err != nil {
   518  			return c.internalServerError(errState, err)
   519  		}
   520  		fmt.Printf("node %d has been shutdown\n", nodeID)
   521  		return nextState
   522  	case "restart":
   523  		if err := demoCtx.transientCluster.RestartNode(roachpb.NodeID(nodeID)); err != nil {
   524  			return c.internalServerError(errState, err)
   525  		}
   526  		fmt.Printf("node %d has been restarted\n", nodeID)
   527  		return nextState
   528  	case "recommission":
   529  		if err := demoCtx.transientCluster.CallDecommission(roachpb.NodeID(nodeID), false /* decommissioning */); err != nil {
   530  			return c.internalServerError(errState, err)
   531  		}
   532  		fmt.Printf("node %d has been recommissioned\n", nodeID)
   533  		return nextState
   534  	case "decommission":
   535  		if err := demoCtx.transientCluster.CallDecommission(roachpb.NodeID(nodeID), true /* decommissioning */); err != nil {
   536  			return c.internalServerError(errState, err)
   537  		}
   538  		fmt.Printf("node %d has been decommissioned\n", nodeID)
   539  		return nextState
   540  	}
   541  	return c.invalidSyntax(errState, `command not recognized: %s`, cmd[0])
   542  }
   543  
   544  // handleHelp prints SQL help.
   545  func (c *cliState) handleHelp(cmd []string, nextState, errState cliStateEnum) cliStateEnum {
   546  	cmdrest := strings.TrimSpace(strings.Join(cmd, " "))
   547  	command := strings.ToUpper(cmdrest)
   548  	if command == "" {
   549  		fmt.Print(parser.AllHelp)
   550  	} else {
   551  		if h, ok := parser.HelpMessages[command]; ok {
   552  			msg := parser.HelpMessage{Command: command, HelpMessageBody: h}
   553  			msg.Format(os.Stdout)
   554  			fmt.Println()
   555  		} else {
   556  			fmt.Fprintf(stderr,
   557  				"no help available for %q.\nTry \\h with no argument to see available help.\n", cmdrest)
   558  			return errState
   559  		}
   560  	}
   561  	return nextState
   562  }
   563  
   564  // handleFunctionHelp prints help about built-in functions.
   565  func (c *cliState) handleFunctionHelp(cmd []string, nextState, errState cliStateEnum) cliStateEnum {
   566  	funcName := strings.TrimSpace(strings.Join(cmd, " "))
   567  	if funcName == "" {
   568  		for _, f := range builtins.AllBuiltinNames {
   569  			fmt.Println(f)
   570  		}
   571  		fmt.Println()
   572  	} else {
   573  		helpText, _ := c.serverSideParse(fmt.Sprintf("select %s(??", funcName))
   574  		if helpText != "" {
   575  			fmt.Println(helpText)
   576  		} else {
   577  			fmt.Fprintf(stderr,
   578  				"no help available for %q.\nTry \\hf with no argument to see available help.\n", funcName)
   579  			return errState
   580  		}
   581  	}
   582  	return nextState
   583  }
   584  
   585  // execSyscmd executes system commands.
   586  func execSyscmd(command string) (string, error) {
   587  	var cmd *exec.Cmd
   588  
   589  	shell := envutil.GetShellCommand(command)
   590  	cmd = exec.Command(shell[0], shell[1:]...)
   591  
   592  	var out bytes.Buffer
   593  	cmd.Stdout = &out
   594  	cmd.Stderr = stderr
   595  
   596  	if err := cmd.Run(); err != nil {
   597  		return "", fmt.Errorf("error in external command: %s", err)
   598  	}
   599  
   600  	return out.String(), nil
   601  }
   602  
   603  var errInvalidSyntax = errors.New("invalid syntax")
   604  
   605  // runSyscmd runs system commands on the interactive CLI.
   606  func (c *cliState) runSyscmd(line string, nextState, errState cliStateEnum) cliStateEnum {
   607  	command := strings.Trim(line[2:], " \r\n\t\f")
   608  	if command == "" {
   609  		fmt.Fprintf(stderr, "Usage:\n  \\! [command]\n")
   610  		c.exitErr = errInvalidSyntax
   611  		return errState
   612  	}
   613  
   614  	cmdOut, err := execSyscmd(command)
   615  	if err != nil {
   616  		fmt.Fprintf(stderr, "command failed: %s\n", err)
   617  		c.exitErr = err
   618  		return errState
   619  	}
   620  
   621  	fmt.Print(cmdOut)
   622  	return nextState
   623  }
   624  
   625  // pipeSyscmd executes system commands and pipe the output into the current SQL.
   626  func (c *cliState) pipeSyscmd(line string, nextState, errState cliStateEnum) cliStateEnum {
   627  	command := strings.Trim(line[2:], " \n\r\t\f")
   628  	if command == "" {
   629  		fmt.Fprintf(stderr, "Usage:\n  \\| [command]\n")
   630  		c.exitErr = errInvalidSyntax
   631  		return errState
   632  	}
   633  
   634  	cmdOut, err := execSyscmd(command)
   635  	if err != nil {
   636  		fmt.Fprintf(stderr, "command failed: %s\n", err)
   637  		c.exitErr = err
   638  		return errState
   639  	}
   640  
   641  	c.lastInputLine = cmdOut
   642  	return nextState
   643  }
   644  
   645  // rePromptFmt recognizes every substitution pattern in the prompt format string.
   646  var rePromptFmt = regexp.MustCompile("(%.)")
   647  
   648  // rePromptDbState recognizes every substitution pattern that requires
   649  // access to the current database state.
   650  // Currently:
   651  // %/ database name
   652  // %x txn status
   653  var rePromptDbState = regexp.MustCompile("(?:^|[^%])%[/x]")
   654  
   655  // unknownDbName is the string to use in the prompt when
   656  // the database cannot be determined.
   657  const unknownDbName = "?"
   658  
   659  // unknownTxnStatus is the string to use in the prompt when the txn status cannot be determined.
   660  const unknownTxnStatus = " ?"
   661  
   662  // doRefreshPrompts refreshes the prompts of the client depending on the
   663  // status of the current transaction.
   664  func (c *cliState) doRefreshPrompts(nextState cliStateEnum) cliStateEnum {
   665  	if !c.hasEditor() {
   666  		return nextState
   667  	}
   668  
   669  	parsedURL, err := url.Parse(c.conn.url)
   670  	if err != nil {
   671  		// If parsing fails, we'll keep the entire URL. The Open call succeeded, and that
   672  		// is the important part.
   673  		c.fullPrompt = c.conn.url + "> "
   674  		c.continuePrompt = strings.Repeat(" ", len(c.fullPrompt)-3) + "-> "
   675  		return nextState
   676  	}
   677  
   678  	userName := ""
   679  	if parsedURL.User != nil {
   680  		userName = parsedURL.User.Username()
   681  	}
   682  
   683  	dbName := unknownDbName
   684  	c.lastKnownTxnStatus = unknownTxnStatus
   685  
   686  	wantDbStateInPrompt := rePromptDbState.MatchString(c.customPromptPattern)
   687  	if wantDbStateInPrompt {
   688  		c.refreshTransactionStatus()
   689  		// refreshDatabaseName() must be called *after* refreshTransactionStatus(),
   690  		// even when %/ appears before %x in the prompt format.
   691  		// This is because the database name should not be queried during
   692  		// some transaction phases.
   693  		dbName = c.refreshDatabaseName()
   694  	}
   695  
   696  	c.fullPrompt = rePromptFmt.ReplaceAllStringFunc(c.customPromptPattern, func(m string) string {
   697  		switch m {
   698  		case "%M":
   699  			return parsedURL.Host // full host name.
   700  		case "%m":
   701  			return parsedURL.Hostname() // host name.
   702  		case "%>":
   703  			return parsedURL.Port() // port.
   704  		case "%n": // user name.
   705  			return userName
   706  		case "%/": // database name.
   707  			return dbName
   708  		case "%x": // txn status.
   709  			return c.lastKnownTxnStatus
   710  		case "%%":
   711  			return "%"
   712  		default:
   713  			err = fmt.Errorf("unrecognized format code in prompt: %q", m)
   714  			return ""
   715  		}
   716  
   717  	})
   718  	if err != nil {
   719  		c.fullPrompt = err.Error()
   720  	}
   721  
   722  	c.fullPrompt += " "
   723  
   724  	if len(c.fullPrompt) < 3 {
   725  		c.continuePrompt = "> "
   726  	} else {
   727  		// continued statement prompt is: "        -> ".
   728  		c.continuePrompt = strings.Repeat(" ", len(c.fullPrompt)-3) + "-> "
   729  	}
   730  
   731  	switch c.useContinuePrompt {
   732  	case true:
   733  		c.currentPrompt = c.continuePrompt
   734  	case false:
   735  		c.currentPrompt = c.fullPrompt
   736  	}
   737  
   738  	// Configure the editor to use the new prompt.
   739  	c.ins.SetLeftPrompt(c.currentPrompt)
   740  
   741  	return nextState
   742  }
   743  
   744  // refreshTransactionStatus retrieves and sets the current transaction status.
   745  func (c *cliState) refreshTransactionStatus() {
   746  	c.lastKnownTxnStatus = unknownTxnStatus
   747  
   748  	dbVal, hasVal := c.conn.getServerValue("transaction status", `SHOW TRANSACTION STATUS`)
   749  	if !hasVal {
   750  		return
   751  	}
   752  
   753  	txnString := formatVal(dbVal,
   754  		false /* showPrintableUnicode */, false /* shownewLinesAndTabs */)
   755  
   756  	// Change the prompt based on the response from the server.
   757  	switch txnString {
   758  	case sql.NoTxnStateStr:
   759  		c.lastKnownTxnStatus = ""
   760  	case sql.AbortedStateStr:
   761  		c.lastKnownTxnStatus = " ERROR"
   762  	case sql.CommitWaitStateStr:
   763  		c.lastKnownTxnStatus = "  DONE"
   764  	case sql.OpenStateStr:
   765  		// The state AutoRetry is reported by the server as Open, so no need to
   766  		// handle it here.
   767  		c.lastKnownTxnStatus = "  OPEN"
   768  	}
   769  }
   770  
   771  // refreshDatabaseName retrieves the current database name from the server.
   772  // The database name is only queried if there is no transaction ongoing,
   773  // or the transaction is fully open.
   774  func (c *cliState) refreshDatabaseName() string {
   775  	if !(c.lastKnownTxnStatus == "" /*NoTxn*/ ||
   776  		c.lastKnownTxnStatus == "  OPEN" ||
   777  		c.lastKnownTxnStatus == unknownTxnStatus) {
   778  		return unknownDbName
   779  	}
   780  
   781  	dbVal, hasVal := c.conn.getServerValue("database name", `SHOW DATABASE`)
   782  	if !hasVal {
   783  		return unknownDbName
   784  	}
   785  
   786  	if dbVal == "" {
   787  		// Attempt to be helpful to new users.
   788  		fmt.Fprintln(stderr, "warning: no current database set."+
   789  			" Use SET database = <dbname> to change, CREATE DATABASE to make a new database.")
   790  	}
   791  
   792  	dbName := formatVal(dbVal.(string),
   793  		false /* showPrintableUnicode */, false /* shownewLinesAndTabs */)
   794  
   795  	// Preserve the current database name in case of reconnects.
   796  	c.conn.dbName = dbName
   797  
   798  	return dbName
   799  }
   800  
   801  var cmdHistFile = envutil.EnvOrDefaultString("COCKROACH_SQL_CLI_HISTORY", ".cockroachsql_history")
   802  
   803  // GetCompletions implements the readline.CompletionGenerator interface.
   804  func (c *cliState) GetCompletions(_ string) []string {
   805  	sql, _ := c.ins.GetLineInfo()
   806  
   807  	if !strings.HasSuffix(sql, "??") {
   808  		fmt.Fprintf(c.ins.Stdout(),
   809  			"\ntab completion not supported; append '??' and press tab for contextual help\n\n")
   810  	} else {
   811  		helpText, err := c.serverSideParse(sql)
   812  		if helpText != "" {
   813  			// We have a completion suggestion. Use that.
   814  			fmt.Fprintf(c.ins.Stdout(), "\nSuggestion:\n%s\n", helpText)
   815  		} else if err != nil {
   816  			// Some other error. Display it.
   817  			fmt.Fprintln(c.ins.Stdout())
   818  			cliOutputError(c.ins.Stdout(), err, true /*showSeverity*/, false /*verbose*/)
   819  		}
   820  	}
   821  
   822  	// After the suggestion or error, re-display the prompt and current entry.
   823  	fmt.Fprint(c.ins.Stdout(), c.currentPrompt, sql)
   824  	return nil
   825  }
   826  
   827  func (c *cliState) doStart(nextState cliStateEnum) cliStateEnum {
   828  	// Common initialization.
   829  	c.partialLines = []string{}
   830  
   831  	if cliCtx.isInteractive {
   832  		fmt.Println("#\n# Enter \\? for a brief introduction.\n#")
   833  	}
   834  
   835  	return nextState
   836  }
   837  
   838  func (c *cliState) doStartLine(nextState cliStateEnum) cliStateEnum {
   839  	// Clear the input buffer.
   840  	c.atEOF = false
   841  	c.partialLines = c.partialLines[:0]
   842  	c.partialStmtsLen = 0
   843  
   844  	c.useContinuePrompt = false
   845  
   846  	return nextState
   847  }
   848  
   849  func (c *cliState) doContinueLine(nextState cliStateEnum) cliStateEnum {
   850  	c.atEOF = false
   851  
   852  	c.useContinuePrompt = true
   853  
   854  	return nextState
   855  }
   856  
   857  // doReadline reads a line of input and check the input status.  If
   858  // input was successful it populates c.lastInputLine.  Otherwise
   859  // c.exitErr is set in some cases and an error/retry state is returned.
   860  func (c *cliState) doReadLine(nextState cliStateEnum) cliStateEnum {
   861  	if len(c.forwardLines) > 0 {
   862  		// Are there some lines accumulated from a previous multi-line
   863  		// readline input? If so, consume one.
   864  		c.lastInputLine = c.forwardLines[0]
   865  		c.forwardLines = c.forwardLines[1:]
   866  		return nextState
   867  	}
   868  
   869  	var l string
   870  	var err error
   871  	if c.buf == nil {
   872  		l, err = c.ins.GetLine()
   873  		if len(l) > 0 && l[len(l)-1] == '\n' {
   874  			// Strip the final newline.
   875  			l = l[:len(l)-1]
   876  		} else {
   877  			// There was no newline at the end of the input
   878  			// (e.g. Ctrl+C was entered). Force one.
   879  			fmt.Fprintln(c.ins.Stdout())
   880  		}
   881  	} else {
   882  		l, err = c.buf.ReadString('\n')
   883  		// bufio.ReadString() differs from readline.Readline in the handling of
   884  		// EOF. Readline only returns EOF when there is nothing left to read and
   885  		// there is no partial line while bufio.ReadString() returns EOF when the
   886  		// end of input has been reached but will return the non-empty partial line
   887  		// as well. We workaround this by converting the bufio behavior to match
   888  		// the Readline behavior.
   889  		if err == io.EOF && len(l) != 0 {
   890  			err = nil
   891  		} else if err == nil {
   892  			// From the bufio.ReadString docs: ReadString returns err != nil if and
   893  			// only if the returned data does not end in delim. To match the behavior
   894  			// of readline.Readline, we strip off the trailing delimiter.
   895  			l = l[:len(l)-1]
   896  		}
   897  	}
   898  
   899  	switch {
   900  	case err == nil:
   901  		// Do we have multiple lines of input?
   902  		lines := strings.Split(l, "\n")
   903  		if len(lines) > 1 {
   904  			// Yes: only keep the first one for now, queue the remainder for
   905  			// next time the shell needs a line.
   906  			l = lines[0]
   907  			c.forwardLines = lines[1:]
   908  		}
   909  		// In any case, process one line.
   910  
   911  	case errors.Is(err, readline.ErrInterrupted):
   912  		if !cliCtx.isInteractive {
   913  			// Ctrl+C terminates non-interactive shells in all cases.
   914  			c.exitErr = err
   915  			return cliStop
   916  		}
   917  
   918  		if l != "" {
   919  			// Ctrl+C after the beginning of a line cancels the current
   920  			// line.
   921  			return cliReadLine
   922  		}
   923  
   924  		if len(c.partialLines) > 0 {
   925  			// Ctrl+C at the beginning of a line in a multi-line statement
   926  			// cancels the multi-line statement.
   927  			return cliStartLine
   928  		}
   929  
   930  		// Otherwise, also terminate with an interrupt error.
   931  		c.exitErr = err
   932  		return cliStop
   933  
   934  	case errors.Is(err, io.EOF):
   935  		c.atEOF = true
   936  
   937  		if cliCtx.isInteractive {
   938  			// In interactive mode, EOF terminates.
   939  			// exitErr is left to be whatever has set it previously.
   940  			return cliStop
   941  		}
   942  
   943  		// Non-interactive: if no partial statement, EOF terminates.
   944  		// exitErr is left to be whatever has set it previously.
   945  		if len(c.partialLines) == 0 {
   946  			return cliStop
   947  		}
   948  
   949  		// Otherwise, give the shell a chance to process the last input line.
   950  
   951  	default:
   952  		// Other errors terminate the shell.
   953  		fmt.Fprintf(stderr, "input error: %s\n", err)
   954  		c.exitErr = err
   955  		return cliStop
   956  	}
   957  
   958  	c.lastInputLine = l
   959  	return nextState
   960  }
   961  
   962  func (c *cliState) doProcessFirstLine(startState, nextState cliStateEnum) cliStateEnum {
   963  	// Special case: first line of multi-line statement.
   964  	// In this case ignore empty lines, and recognize "help" specially.
   965  	switch c.lastInputLine {
   966  	case "":
   967  		// Ignore empty lines, just continue reading if it isn't interactive mode.
   968  		return startState
   969  
   970  	case "help":
   971  		c.printCliHelp()
   972  		return startState
   973  
   974  	case "exit", "quit":
   975  		return cliStop
   976  	}
   977  
   978  	return nextState
   979  }
   980  
   981  func (c *cliState) doHandleCliCmd(loopState, nextState cliStateEnum) cliStateEnum {
   982  	if len(c.lastInputLine) == 0 || c.lastInputLine[0] != '\\' {
   983  		return nextState
   984  	}
   985  
   986  	errState := loopState
   987  	if c.errExit {
   988  		// If exiterr is set, an error in a client-side command also
   989  		// terminates the shell.
   990  		errState = cliStop
   991  	}
   992  
   993  	// This is a client-side command. Whatever happens, we are not going
   994  	// to handle it as a statement, so save the history.
   995  	c.addHistory(c.lastInputLine)
   996  
   997  	// As a convenience to the user, we strip the final semicolon, if
   998  	// any, in all cases.
   999  	line := strings.TrimRight(c.lastInputLine, "; ")
  1000  
  1001  	cmd := strings.Fields(line)
  1002  	switch cmd[0] {
  1003  	case `\q`, `\quit`, `\exit`:
  1004  		return cliStop
  1005  
  1006  	case `\`, `\?`, `\help`:
  1007  		c.printCliHelp()
  1008  
  1009  	case `\set`:
  1010  		return c.handleSet(cmd[1:], loopState, errState)
  1011  
  1012  	case `\unset`:
  1013  		return c.handleUnset(cmd[1:], loopState, errState)
  1014  
  1015  	case `\!`:
  1016  		return c.runSyscmd(c.lastInputLine, loopState, errState)
  1017  
  1018  	case `\show`:
  1019  		if len(c.partialLines) == 0 {
  1020  			fmt.Fprintf(stderr, "No input so far. Did you mean SHOW?\n")
  1021  		} else {
  1022  			for _, s := range c.partialLines {
  1023  				fmt.Println(s)
  1024  			}
  1025  		}
  1026  
  1027  	case `\|`:
  1028  		return c.pipeSyscmd(c.lastInputLine, nextState, errState)
  1029  
  1030  	case `\h`:
  1031  		return c.handleHelp(cmd[1:], loopState, errState)
  1032  
  1033  	case `\hf`:
  1034  		return c.handleFunctionHelp(cmd[1:], loopState, errState)
  1035  
  1036  	case `\l`:
  1037  		c.concatLines = `SHOW DATABASES`
  1038  		return cliRunStatement
  1039  
  1040  	case `\dt`:
  1041  		c.concatLines = `SHOW TABLES`
  1042  		return cliRunStatement
  1043  
  1044  	case `\du`:
  1045  		c.concatLines = `SHOW USERS`
  1046  		return cliRunStatement
  1047  
  1048  	case `\d`:
  1049  		if len(cmd) == 1 {
  1050  			c.concatLines = `SHOW TABLES`
  1051  			return cliRunStatement
  1052  		} else if len(cmd) == 2 {
  1053  			c.concatLines = `SHOW COLUMNS FROM ` + cmd[1]
  1054  			return cliRunStatement
  1055  		}
  1056  		return c.invalidSyntax(errState, `%s. Try \? for help.`, c.lastInputLine)
  1057  
  1058  	case `\demo`:
  1059  		return c.handleDemo(cmd[1:], loopState, errState)
  1060  
  1061  	default:
  1062  		if strings.HasPrefix(cmd[0], `\d`) {
  1063  			// Unrecognized command for now, but we want to be helpful.
  1064  			fmt.Fprint(stderr, "Suggestion: use the SQL SHOW statement to inspect your schema.\n")
  1065  		}
  1066  		return c.invalidSyntax(errState, `%s. Try \? for help.`, c.lastInputLine)
  1067  	}
  1068  
  1069  	return loopState
  1070  }
  1071  
  1072  func (c *cliState) doPrepareStatementLine(
  1073  	startState, contState, checkState, execState cliStateEnum,
  1074  ) cliStateEnum {
  1075  	c.partialLines = append(c.partialLines, c.lastInputLine)
  1076  
  1077  	// We join the statements back together with newlines in case
  1078  	// there is a significant newline inside a string literal.
  1079  	c.concatLines = strings.Trim(strings.Join(c.partialLines, "\n"), " \r\n\t\f")
  1080  
  1081  	if c.concatLines == "" {
  1082  		// Only whitespace.
  1083  		return startState
  1084  	}
  1085  
  1086  	lastTok, ok := parser.LastLexicalToken(c.concatLines)
  1087  	endOfStmt := isEndOfStatement(lastTok)
  1088  	if c.partialStmtsLen == 0 && !ok {
  1089  		// More whitespace, or comments. Still nothing to do. However
  1090  		// if the syntax was non-trivial to arrive here,
  1091  		// keep it for history.
  1092  		if c.lastInputLine != "" {
  1093  			c.addHistory(c.lastInputLine)
  1094  		}
  1095  		return startState
  1096  	}
  1097  	if c.atEOF {
  1098  		// Definitely no more input expected.
  1099  		if !endOfStmt {
  1100  			fmt.Fprintf(stderr, "missing semicolon at end of statement: %s\n", c.concatLines)
  1101  			c.exitErr = fmt.Errorf("last statement was not executed: %s", c.concatLines)
  1102  			return cliStop
  1103  		}
  1104  	}
  1105  
  1106  	if !endOfStmt {
  1107  		if lastTok == '?' {
  1108  			stdout := os.Stdout
  1109  			if c.hasEditor() {
  1110  				stdout = c.ins.Stdout()
  1111  			}
  1112  			fmt.Fprintf(stdout,
  1113  				"Note: a single '?' is a JSON operator. If you want contextual help, use '??'.\n")
  1114  		}
  1115  		return contState
  1116  	}
  1117  
  1118  	// Complete input. Remember it in the history.
  1119  	c.addHistory(c.concatLines)
  1120  
  1121  	if !c.checkSyntax {
  1122  		return execState
  1123  	}
  1124  
  1125  	return checkState
  1126  }
  1127  
  1128  func (c *cliState) doCheckStatement(startState, contState, execState cliStateEnum) cliStateEnum {
  1129  	// From here on, client-side syntax checking is enabled.
  1130  	helpText, err := c.serverSideParse(c.concatLines)
  1131  	if err != nil {
  1132  		if helpText != "" {
  1133  			// There was a help text included. Use it.
  1134  			fmt.Println(helpText)
  1135  		}
  1136  
  1137  		_ = c.invalidSyntax(0, "statement ignored: %v",
  1138  			&formattedError{err: err, showSeverity: false, verbose: false})
  1139  
  1140  		// Stop here if exiterr is set.
  1141  		if c.errExit {
  1142  			return cliStop
  1143  		}
  1144  
  1145  		// Remove the erroneous lines from the buffered input,
  1146  		// then try again.
  1147  		c.partialLines = c.partialLines[:c.partialStmtsLen]
  1148  		if len(c.partialLines) == 0 {
  1149  			return startState
  1150  		}
  1151  		return contState
  1152  	}
  1153  
  1154  	if !cliCtx.isInteractive {
  1155  		return execState
  1156  	}
  1157  
  1158  	nextState := execState
  1159  
  1160  	c.partialStmtsLen = len(c.partialLines)
  1161  
  1162  	return nextState
  1163  }
  1164  
  1165  func (c *cliState) doRunStatement(nextState cliStateEnum) cliStateEnum {
  1166  	// Once we send something to the server, the txn status may change arbitrarily.
  1167  	// Clear the known state so that further entries do not assume anything.
  1168  	c.lastKnownTxnStatus = " ?"
  1169  
  1170  	// Are we tracing?
  1171  	if c.autoTrace != "" {
  1172  		// Clear the trace by disabling tracing, then restart tracing
  1173  		// with the specified options.
  1174  		c.exitErr = c.conn.Exec("SET tracing = off; SET tracing = "+c.autoTrace, nil)
  1175  		if c.exitErr != nil {
  1176  			cliOutputError(stderr, c.exitErr, true /*showSeverity*/, false /*verbose*/)
  1177  			if c.errExit {
  1178  				return cliStop
  1179  			}
  1180  			return nextState
  1181  		}
  1182  	}
  1183  
  1184  	// Now run the statement/query.
  1185  	c.exitErr = runQueryAndFormatResults(c.conn, os.Stdout, makeQuery(c.concatLines))
  1186  	if c.exitErr != nil {
  1187  		cliOutputError(stderr, c.exitErr, true /*showSeverity*/, false /*verbose*/)
  1188  	}
  1189  
  1190  	// If we are tracing, stop tracing and display the trace. We do
  1191  	// this even if there was an error: a trace on errors is useful.
  1192  	if c.autoTrace != "" {
  1193  		// First, disable tracing.
  1194  		if err := c.conn.Exec("SET tracing = off", nil); err != nil {
  1195  			// Print the error for the SET tracing statement. This will
  1196  			// appear below the error for the main query above, if any,
  1197  			cliOutputError(stderr, err, true /*showSeverity*/, false /*verbose*/)
  1198  			if c.exitErr == nil {
  1199  				// The query had encountered no error above, but now we are
  1200  				// encountering an error on SET tracing. Consider this to
  1201  				// become the query's error.
  1202  				c.exitErr = err
  1203  			}
  1204  			// If the query above had encountered an error already
  1205  			// (c.exitErr != nil), we keep that as the main error for the
  1206  			// shell.
  1207  		} else {
  1208  			traceType := ""
  1209  			if strings.Contains(c.autoTrace, "kv") {
  1210  				traceType = "kv"
  1211  			}
  1212  			if err := runQueryAndFormatResults(c.conn, os.Stdout,
  1213  				makeQuery(fmt.Sprintf("SHOW %s TRACE FOR SESSION", traceType))); err != nil {
  1214  				cliOutputError(stderr, err, true /*showSeverity*/, false /*verbose*/)
  1215  				if c.exitErr == nil {
  1216  					// Both the query and SET tracing had encountered no error
  1217  					// above, but now we are encountering an error on SHOW TRACE
  1218  					// Consider this to become the query's error.
  1219  					c.exitErr = err
  1220  				}
  1221  				// If the query above or SET tracing had encountered an error
  1222  				// already (c.exitErr != nil), we keep that as the main error
  1223  				// for the shell.
  1224  			}
  1225  		}
  1226  	}
  1227  
  1228  	if c.exitErr != nil && c.errExit {
  1229  		return cliStop
  1230  	}
  1231  
  1232  	return nextState
  1233  }
  1234  
  1235  func (c *cliState) doDecidePath() cliStateEnum {
  1236  	if len(c.partialLines) == 0 {
  1237  		return cliProcessFirstLine
  1238  	} else if cliCtx.isInteractive {
  1239  		// In interactive mode, we allow client-side commands to be
  1240  		// issued on intermediate lines.
  1241  		return cliHandleCliCmd
  1242  	}
  1243  	// Neither interactive nor at start, continue with processing.
  1244  	return cliPrepareStatementLine
  1245  }
  1246  
  1247  // runInteractive runs the SQL client interactively, presenting
  1248  // a prompt to the user for each statement.
  1249  func runInteractive(conn *sqlConn) (exitErr error) {
  1250  	c := cliState{conn: conn}
  1251  
  1252  	state := cliStart
  1253  	for {
  1254  		if state == cliStop {
  1255  			break
  1256  		}
  1257  		switch state {
  1258  		case cliStart:
  1259  			cleanupFn, err := c.configurePreShellDefaults()
  1260  			defer cleanupFn()
  1261  			if err != nil {
  1262  				return err
  1263  			}
  1264  			if len(sqlCtx.execStmts) > 0 {
  1265  				// Single-line sql; run as simple as possible, without noise on stdout.
  1266  				return c.runStatements(sqlCtx.execStmts)
  1267  			}
  1268  
  1269  			state = c.doStart(cliStartLine)
  1270  
  1271  		case cliRefreshPrompt:
  1272  			state = c.doRefreshPrompts(cliReadLine)
  1273  
  1274  		case cliStartLine:
  1275  			state = c.doStartLine(cliRefreshPrompt)
  1276  
  1277  		case cliContinueLine:
  1278  			state = c.doContinueLine(cliRefreshPrompt)
  1279  
  1280  		case cliReadLine:
  1281  			state = c.doReadLine(cliDecidePath)
  1282  
  1283  		case cliDecidePath:
  1284  			state = c.doDecidePath()
  1285  
  1286  		case cliProcessFirstLine:
  1287  			state = c.doProcessFirstLine(cliStartLine, cliHandleCliCmd)
  1288  
  1289  		case cliHandleCliCmd:
  1290  			state = c.doHandleCliCmd(cliRefreshPrompt, cliPrepareStatementLine)
  1291  
  1292  		case cliPrepareStatementLine:
  1293  			state = c.doPrepareStatementLine(
  1294  				cliStartLine, cliContinueLine, cliCheckStatement, cliRunStatement,
  1295  			)
  1296  
  1297  		case cliCheckStatement:
  1298  			state = c.doCheckStatement(cliStartLine, cliContinueLine, cliRunStatement)
  1299  
  1300  		case cliRunStatement:
  1301  			state = c.doRunStatement(cliStartLine)
  1302  
  1303  		default:
  1304  			panic(fmt.Sprintf("unknown state: %d", state))
  1305  		}
  1306  	}
  1307  
  1308  	return c.exitErr
  1309  }
  1310  
  1311  // configurePreShellDefaults should be called after command-line flags
  1312  // have been loaded into the cliCtx/sqlCtx and .isInteractive /
  1313  // .terminalOutput have been initialized, but before the SQL shell or
  1314  // execution starts.
  1315  //
  1316  // The returned cleanupFn must be called even when the err return is
  1317  // not nil.
  1318  func (c *cliState) configurePreShellDefaults() (cleanupFn func(), err error) {
  1319  	if cliCtx.terminalOutput {
  1320  		// If results are shown on a terminal also enable printing of
  1321  		// times by default.
  1322  		sqlCtx.showTimes = true
  1323  	}
  1324  
  1325  	if cliCtx.isInteractive {
  1326  		// If a human user is providing the input, we want to help them with
  1327  		// what they are entering:
  1328  		c.errExit = false // let the user retry failing commands
  1329  		if !sqlCtx.debugMode {
  1330  			// Also, try to enable syntax checking if supported by the server.
  1331  			// This is a form of client-side error checking to help with large txns.
  1332  			c.tryEnableCheckSyntax()
  1333  		}
  1334  	} else {
  1335  		// When running non-interactive, by default we want errors to stop
  1336  		// further processing and we can just let syntax checking to be
  1337  		// done server-side to avoid client-side churn.
  1338  		c.errExit = true
  1339  		c.checkSyntax = false
  1340  		// We also don't need (smart) prompts at all.
  1341  	}
  1342  
  1343  	// An interactive readline prompter is comparatively slow at
  1344  	// reading input, so we only use it in interactive mode and when
  1345  	// there is also a terminal on stdout.
  1346  	if cliCtx.isInteractive && cliCtx.terminalOutput {
  1347  		// The readline initialization is not placed in
  1348  		// the doStart() method because of the defer.
  1349  		c.ins, c.exitErr = readline.InitFiles("cockroach",
  1350  			true, /* wideChars */
  1351  			stdin, os.Stdout, stderr)
  1352  		if errors.Is(c.exitErr, readline.ErrWidecharNotSupported) {
  1353  			log.Warning(context.TODO(), "wide character support disabled")
  1354  			c.ins, c.exitErr = readline.InitFiles("cockroach",
  1355  				false, stdin, os.Stdout, stderr)
  1356  		}
  1357  		if c.exitErr != nil {
  1358  			return cleanupFn, c.exitErr
  1359  		}
  1360  		// If the user has used bind -v or bind -l in their ~/.editrc,
  1361  		// this will reset the standard bindings. However we really
  1362  		// want in this shell that Ctrl+C, tab, Ctrl+Z and Ctrl+R
  1363  		// always have the same meaning.  So reload these bindings
  1364  		// explicitly no matter what ~/.editrc may have changed.
  1365  		c.ins.RebindControlKeys()
  1366  		cleanupFn = func() { c.ins.Close() }
  1367  	} else {
  1368  		c.ins = noLineEditor
  1369  		c.buf = bufio.NewReader(stdin)
  1370  		cleanupFn = func() {}
  1371  	}
  1372  
  1373  	if c.hasEditor() {
  1374  		// We only enable prompt and history management when the
  1375  		// interactive input prompter is enabled. This saves on churn and
  1376  		// memory when e.g. piping a large SQL script through the
  1377  		// command-line client.
  1378  
  1379  		// Default prompt is part of the connection URL. eg: "marc@localhost:26257>".
  1380  		c.customPromptPattern = defaultPromptPattern
  1381  		if sqlCtx.debugMode {
  1382  			c.customPromptPattern = debugPromptPattern
  1383  		}
  1384  
  1385  		c.ins.SetCompleter(c)
  1386  		if err := c.ins.UseHistory(-1 /*maxEntries*/, true /*dedup*/); err != nil {
  1387  			log.Warningf(context.TODO(), "cannot enable history: %v", err)
  1388  		} else {
  1389  			homeDir, err := envutil.HomeDir()
  1390  			if err != nil {
  1391  				log.Warningf(context.TODO(), "cannot retrieve user information: %v", err)
  1392  				log.Warning(context.TODO(), "history will not be saved")
  1393  			} else {
  1394  				histFile := filepath.Join(homeDir, cmdHistFile)
  1395  				err = c.ins.LoadHistory(histFile)
  1396  				if err != nil {
  1397  					log.Warningf(context.TODO(), "cannot load the command-line history (file corrupted?): %v", err)
  1398  					log.Warning(context.TODO(), "the history file will be cleared upon first entry")
  1399  				}
  1400  				c.ins.SetAutoSaveHistory(histFile, true)
  1401  			}
  1402  		}
  1403  	}
  1404  
  1405  	// After all the local options have been computed/initialized above,
  1406  	// process any overrides from the command line.
  1407  	for i := range sqlCtx.setStmts {
  1408  		if c.handleSet(sqlCtx.setStmts[i:i+1], cliStart, cliStop) == cliStop {
  1409  			return cleanupFn, c.exitErr
  1410  		}
  1411  	}
  1412  	return cleanupFn, nil
  1413  }
  1414  
  1415  // runOneStatement executes one statement and terminates
  1416  // on error.
  1417  func (c *cliState) runStatements(stmts []string) error {
  1418  	for {
  1419  		for i, stmt := range stmts {
  1420  			// We do not use the logic from doRunStatement here
  1421  			// because we need a different error handling mechanism:
  1422  			// the error, if any, must not be printed to stderr if
  1423  			// we are returning directly.
  1424  			c.exitErr = runQueryAndFormatResults(c.conn, os.Stdout, makeQuery(stmt))
  1425  			if c.exitErr != nil {
  1426  				if !c.errExit && i < len(stmts)-1 {
  1427  					// Print the error now because we don't get a chance later.
  1428  					cliOutputError(stderr, c.exitErr, true /*showSeverity*/, false /*verbose*/)
  1429  				}
  1430  				if c.errExit {
  1431  					break
  1432  				}
  1433  			}
  1434  		}
  1435  		// If --watch was specified and no error was encountered,
  1436  		// repeat.
  1437  		if sqlCtx.repeatDelay > 0 && c.exitErr == nil {
  1438  			time.Sleep(sqlCtx.repeatDelay)
  1439  			continue
  1440  		}
  1441  		break
  1442  	}
  1443  
  1444  	if c.exitErr != nil {
  1445  		// Don't write the error to stderr ourselves. Cobra will do this for
  1446  		// us on the exit path. We do want the details though, so add them.
  1447  		c.exitErr = &formattedError{err: c.exitErr, showSeverity: true, verbose: false}
  1448  	}
  1449  	return c.exitErr
  1450  }
  1451  
  1452  // checkInteractive sets the isInteractive parameter depending on the
  1453  // execution environment and the presence of -e flags.
  1454  func checkInteractive() {
  1455  	// We don't consider sessions interactives unless we have a
  1456  	// serious hunch they are. For now, only `cockroach sql` *without*
  1457  	// `-e` has the ability to input from a (presumably) human user,
  1458  	// and we'll also assume that there is no human if the standard
  1459  	// input is not terminal-like -- likely redirected from a file,
  1460  	// etc.
  1461  	cliCtx.isInteractive = len(sqlCtx.execStmts) == 0 && isatty.IsTerminal(os.Stdin.Fd())
  1462  }
  1463  
  1464  func runTerm(cmd *cobra.Command, args []string) error {
  1465  	checkInteractive()
  1466  
  1467  	if cliCtx.isInteractive {
  1468  		// The user only gets to see the welcome message on interactive sessions.
  1469  		fmt.Print(welcomeMessage)
  1470  	}
  1471  
  1472  	conn, err := makeSQLClient("cockroach sql", useDefaultDb)
  1473  	if err != nil {
  1474  		return err
  1475  	}
  1476  	defer conn.Close()
  1477  
  1478  	return runClient(cmd, conn)
  1479  }
  1480  
  1481  func runClient(cmd *cobra.Command, conn *sqlConn) error {
  1482  	// Open the connection to make sure everything is OK before running any
  1483  	// statements. Performs authentication.
  1484  	if err := conn.ensureConn(); err != nil {
  1485  		return err
  1486  	}
  1487  
  1488  	// Enable safe updates, unless disabled.
  1489  	setupSafeUpdates(cmd, conn)
  1490  
  1491  	return runInteractive(conn)
  1492  }
  1493  
  1494  // setupSafeUpdates attempts to enable "safe mode" if the session is
  1495  // interactive and the user is not disabling this behavior with
  1496  // --safe-updates=false.
  1497  func setupSafeUpdates(cmd *cobra.Command, conn *sqlConn) {
  1498  	pf := cmd.Flags()
  1499  	vf := pf.Lookup(cliflags.SafeUpdates.Name)
  1500  
  1501  	if !vf.Changed {
  1502  		// If `--safe-updates` was not specified, we need to set the default
  1503  		// based on whether the session is interactive. We cannot do this
  1504  		// earlier, because "session is interactive" depends on knowing
  1505  		// whether `-e` is also provided.
  1506  		sqlCtx.safeUpdates = cliCtx.isInteractive
  1507  	}
  1508  
  1509  	if !sqlCtx.safeUpdates {
  1510  		// nothing to do.
  1511  		return
  1512  	}
  1513  
  1514  	if err := conn.Exec("SET sql_safe_updates = TRUE", nil); err != nil {
  1515  		// We only enable the setting in interactive sessions. Ignoring
  1516  		// the error with a warning is acceptable, because the user is
  1517  		// there to decide what they want to do if it doesn't work.
  1518  		fmt.Fprintf(stderr, "warning: cannot enable safe updates: %v\n", err)
  1519  	}
  1520  }
  1521  
  1522  // tryEnableCheckSyntax attempts to enable check_syntax.
  1523  // The option is enabled if the SHOW SYNTAX statement is recognized
  1524  // by the server.
  1525  func (c *cliState) tryEnableCheckSyntax() {
  1526  	if err := c.conn.Exec("SHOW SYNTAX 'SHOW SYNTAX ''1'';'", nil); err != nil {
  1527  		fmt.Fprintf(stderr, "warning: cannot enable check_syntax: %v\n", err)
  1528  	} else {
  1529  		c.checkSyntax = true
  1530  	}
  1531  }
  1532  
  1533  // serverSideParse uses the SHOW SYNTAX statement to analyze the given string.
  1534  // If the syntax is correct, the function returns the statement
  1535  // decomposition in the first return value. If it is not, the function
  1536  // extracts a help string if available.
  1537  func (c *cliState) serverSideParse(sql string) (helpText string, err error) {
  1538  	cols, rows, err := runQuery(c.conn, makeQuery("SHOW SYNTAX "+lex.EscapeSQLString(sql)), true)
  1539  	if err != nil {
  1540  		// The query failed with some error. This is not a syntax error
  1541  		// detected by SHOW SYNTAX (those show up as valid rows) but
  1542  		// instead something else.
  1543  		return "", errors.Wrap(err, "unexpected error")
  1544  	}
  1545  
  1546  	if len(cols) < 2 {
  1547  		return "", errors.Newf(
  1548  			"invalid results for SHOW SYNTAX: %q %q", cols, rows)
  1549  	}
  1550  
  1551  	// If SHOW SYNTAX reports an error, then it does so on the first row.
  1552  	if len(rows) >= 1 && rows[0][0] == "error" {
  1553  		var message, code, detail, hint string
  1554  		for _, row := range rows {
  1555  			switch row[0] {
  1556  			case "error":
  1557  				message = row[1]
  1558  			case "detail":
  1559  				detail = row[1]
  1560  			case "hint":
  1561  				hint = row[1]
  1562  			case "code":
  1563  				code = row[1]
  1564  			}
  1565  		}
  1566  		// Is it a help text?
  1567  		if strings.HasPrefix(message, "help token in input") && strings.HasPrefix(hint, "help:") {
  1568  			// Yes: return it.
  1569  			helpText = hint[6:]
  1570  			hint = ""
  1571  		}
  1572  		// In any case report that there was an error while parsing.
  1573  		err := errors.Newf("%s", message)
  1574  		err = pgerror.WithCandidateCode(err, code)
  1575  		if hint != "" {
  1576  			err = errors.WithHint(err, hint)
  1577  		}
  1578  		if detail != "" {
  1579  			err = errors.WithDetail(err, detail)
  1580  		}
  1581  		return helpText, err
  1582  	}
  1583  	return "", nil
  1584  }