diff --git a/pkg/cli/cli_test.go b/pkg/cli/cli_test.go index 9a159437d032..38649a2e918c 100644 --- a/pkg/cli/cli_test.go +++ b/pkg/cli/cli_test.go @@ -625,6 +625,24 @@ func Example_sql() { // invalid syntax } +func Example_sql_watch() { + c := newCLITest(cliTestParams{}) + defer c.cleanup() + + c.RunWithArgs([]string{`sql`, `-e`, `create table d(x int); insert into d values(3)`}) + c.RunWithArgs([]string{`sql`, `--watch`, `.1s`, `-e`, `update d set x=x-1 returning 1/x as dec`}) + + // Output: + // sql -e create table d(x int); insert into d values(3) + // INSERT 1 + // sql --watch .1s -e update d set x=x-1 returning 1/x as dec + // dec + // 0.5 + // dec + // 1 + // pq: division by zero +} + func Example_sql_format() { c := newCLITest(cliTestParams{}) defer c.cleanup() diff --git a/pkg/cli/cliflags/flags.go b/pkg/cli/cliflags/flags.go index 507a03783a9d..2d4d0572727e 100644 --- a/pkg/cli/cliflags/flags.go +++ b/pkg/cli/cliflags/flags.go @@ -238,6 +238,14 @@ with a non-zero status code and further statements are not executed. The results of each SQL statement are printed on the standard output.`, } + Watch = FlagInfo{ + Name: "watch", + Description: ` +Repeat the SQL statement(s) specified with --execute +with the specified period. The client will stop watching +if an execution of the SQL statement(s) fail.`, + } + EchoSQL = FlagInfo{ Name: "echo-sql", Description: ` diff --git a/pkg/cli/context.go b/pkg/cli/context.go index fbe8ce82b105..3df1e20503bb 100644 --- a/pkg/cli/context.go +++ b/pkg/cli/context.go @@ -83,6 +83,7 @@ func initCLIDefaults() { sqlCtx.setStmts = nil sqlCtx.execStmts = nil + sqlCtx.repeatDelay = 0 sqlCtx.safeUpdates = false sqlCtx.showTimes = false sqlCtx.debugMode = false @@ -217,6 +218,11 @@ var sqlCtx = struct { // execStmts is a list of statements to execute. execStmts statementsValue + // repeatDelay indicates that the execStmts should be "watched" + // at the specified time interval. Zero disables + // the watch. + repeatDelay time.Duration + // safeUpdates indicates whether to set sql_safe_updates in the CLI // shell. safeUpdates bool diff --git a/pkg/cli/flags.go b/pkg/cli/flags.go index 112e7ac26c76..0ec09192770d 100644 --- a/pkg/cli/flags.go +++ b/pkg/cli/flags.go @@ -527,6 +527,7 @@ func init() { f := cmd.Flags() VarFlag(f, &sqlCtx.setStmts, cliflags.Set) VarFlag(f, &sqlCtx.execStmts, cliflags.Execute) + DurationFlag(f, &sqlCtx.repeatDelay, cliflags.Watch, sqlCtx.repeatDelay) BoolFlag(f, &sqlCtx.safeUpdates, cliflags.SafeUpdates, sqlCtx.safeUpdates) BoolFlag(f, &sqlCtx.debugMode, cliflags.CliDebugMode, sqlCtx.debugMode) } diff --git a/pkg/cli/sql.go b/pkg/cli/sql.go index 00285b02d1fc..0c18d9d2d776 100644 --- a/pkg/cli/sql.go +++ b/pkg/cli/sql.go @@ -24,6 +24,7 @@ import ( "sort" "strconv" "strings" + "time" "github.com/cockroachdb/cockroach/pkg/base" "github.com/cockroachdb/cockroach/pkg/cli/cliflags" @@ -1389,22 +1390,31 @@ func runInteractive(conn *sqlConn) (exitErr error) { // runOneStatement executes one statement and terminates // on error. func (c *cliState) runStatements(stmts []string) error { - for i, stmt := range stmts { - // We do not use the logic from doRunStatement here - // because we need a different error handling mechanism: - // the error, if any, must not be printed to stderr if - // we are returning directly. - c.exitErr = runQueryAndFormatResults(c.conn, os.Stdout, makeQuery(stmt)) - if c.exitErr != nil { - if !c.errExit && i < len(stmts)-1 { - // Print the error now because we don't get a chance later. - fmt.Fprintln(stderr, c.exitErr) - maybeShowErrorDetails(stderr, c.exitErr, false) - } - if c.errExit { - break + for { + for i, stmt := range stmts { + // We do not use the logic from doRunStatement here + // because we need a different error handling mechanism: + // the error, if any, must not be printed to stderr if + // we are returning directly. + c.exitErr = runQueryAndFormatResults(c.conn, os.Stdout, makeQuery(stmt)) + if c.exitErr != nil { + if !c.errExit && i < len(stmts)-1 { + // Print the error now because we don't get a chance later. + fmt.Fprintln(stderr, c.exitErr) + maybeShowErrorDetails(stderr, c.exitErr, false) + } + if c.errExit { + break + } } } + // If --watch was specified and no error was encountered, + // repeat. + if sqlCtx.repeatDelay > 0 && c.exitErr == nil { + time.Sleep(sqlCtx.repeatDelay) + continue + } + break } if c.exitErr != nil {