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 }