Skip to content

Commit

Permalink
cli: support -f to read SQL from a file
Browse files Browse the repository at this point in the history
Release note (cli change): `cockroach sql` and `cockroach demo` now
support the command-line parameter `--input-file` (shorthand `-f`) to
read commands from a named file. The behavior is the same as if the
file was redirected on the standard input; in particular, the
processing stops at the first error encountered (which is different
from interactive usage with a prompt).

Note that it is not (yet) possible to combine `-f` with `-e`.
  • Loading branch information
knz committed Sep 24, 2020
1 parent 7930434 commit cfda64a
Show file tree
Hide file tree
Showing 7 changed files with 104 additions and 8 deletions.
29 changes: 29 additions & 0 deletions pkg/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2060,3 +2060,32 @@ func Example_dump_no_visible_columns() {
// CREATE TABLE public.t (FAMILY "primary" (rowid)
// );
}

// Example_read_from_file tests the -f parameter.
// The input file contains a mix of client-side and
// server-side commands to ensure that both are supported with -f.
func Example_read_from_file() {
c := newCLITest(cliTestParams{})
defer c.cleanup()

c.RunWithArgs([]string{"sql", "-e", "select 1", "-f", "testdata/inputfile.sql"})
c.RunWithArgs([]string{"sql", "-f", "testdata/inputfile.sql"})

// Output:
// sql -e select 1 -f testdata/inputfile.sql
// ERROR: unsupported combination: --execute and --input-file
// sql -f testdata/inputfile.sql
// SET
// CREATE TABLE
// > INSERT INTO test(s) VALUES ('hello'), ('world');
// INSERT 2
// > SELECT * FROM test;
// s
// hello
// world
// > SELECT undefined;
// ERROR: column "undefined" does not exist
// SQLSTATE: 42703
// ERROR: column "undefined" does not exist
// SQLSTATE: 42703
}
15 changes: 14 additions & 1 deletion pkg/cli/cliflags/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -256,7 +256,20 @@ Execute the SQL statement(s) on the command line, then exit. This flag may be
specified multiple times and each value may contain multiple semicolon
separated statements. If an error occurs in any statement, the command exits
with a non-zero status code and further statements are not executed. The
results of each SQL statement are printed on the standard output.`,
results of each SQL statement are printed on the standard output.
This flag is incompatible with --input-file / -f.`,
}

InputFile = FlagInfo{
Name: "input-file",
Shorthand: "f",
Description: `
Read and execute the SQL statement(s) from the specified file.
The file is processed as if it has been redirected on the standard
input of the shell.
This flag is incompatible with --execute / -e.`,
}

Watch = FlagInfo{
Expand Down
7 changes: 7 additions & 0 deletions pkg/cli/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -204,8 +204,14 @@ var sqlCtx = struct {
setStmts statementsValue

// execStmts is a list of statements to execute.
// Only valid if inputFile is empty.
execStmts statementsValue

// inputFile is the file to read from.
// If empty, os.Stdin is used.
// Only valid if execStmts is empty.
inputFile string

// repeatDelay indicates that the execStmts should be "watched"
// at the specified time interval. Zero disables
// the watch.
Expand Down Expand Up @@ -240,6 +246,7 @@ var sqlCtx = struct {
func setSQLContextDefaults() {
sqlCtx.setStmts = nil
sqlCtx.execStmts = nil
sqlCtx.inputFile = ""
sqlCtx.repeatDelay = 0
sqlCtx.safeUpdates = false
sqlCtx.showTimes = false
Expand Down
11 changes: 8 additions & 3 deletions pkg/cli/demo.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import (
"context"
gosql "database/sql"
"fmt"
"os"
"strings"
"time"

Expand Down Expand Up @@ -253,6 +252,12 @@ func checkDemoConfiguration(
}

func runDemo(cmd *cobra.Command, gen workload.Generator) (err error) {
cmdIn, closeFn, err := getInputFile()
if err != nil {
return err
}
defer closeFn()

if gen, err = checkDemoConfiguration(cmd, gen); err != nil {
return err
}
Expand Down Expand Up @@ -283,7 +288,7 @@ func runDemo(cmd *cobra.Command, gen workload.Generator) (err error) {
}
demoCtx.transientCluster = &c

checkInteractive(os.Stdin)
checkInteractive(cmdIn)

if cliCtx.isInteractive {
fmt.Printf(`#
Expand Down Expand Up @@ -359,7 +364,7 @@ func runDemo(cmd *cobra.Command, gen workload.Generator) (err error) {
conn := makeSQLConn(c.connURL)
defer conn.Close()

return runClient(cmd, conn)
return runClient(cmd, conn, cmdIn)
}

func waitForLicense(licenseDone <-chan error) error {
Expand Down
1 change: 1 addition & 0 deletions pkg/cli/flags.go
Original file line number Diff line number Diff line change
Expand Up @@ -656,6 +656,7 @@ func init() {
f := cmd.Flags()
varFlag(f, &sqlCtx.setStmts, cliflags.Set)
varFlag(f, &sqlCtx.execStmts, cliflags.Execute)
stringFlag(f, &sqlCtx.inputFile, cliflags.InputFile)
durationFlag(f, &sqlCtx.repeatDelay, cliflags.Watch)
boolFlag(f, &sqlCtx.safeUpdates, cliflags.SafeUpdates)
boolFlag(f, &sqlCtx.debugMode, cliflags.CliDebugMode)
Expand Down
31 changes: 27 additions & 4 deletions pkg/cli/sql.go
Original file line number Diff line number Diff line change
Expand Up @@ -1486,8 +1486,31 @@ func checkInteractive(stdin *os.File) {
cliCtx.isInteractive = len(sqlCtx.execStmts) == 0 && isatty.IsTerminal(stdin.Fd())
}

// getInputFile establishes where we are reading from.
func getInputFile() (cmdIn *os.File, closeFn func(), err error) {
if sqlCtx.inputFile == "" {
return os.Stdin, func() {}, nil
}

if len(sqlCtx.execStmts) != 0 {
return nil, nil, errors.Newf("unsupported combination: --%s and --%s", cliflags.Execute.Name, cliflags.InputFile.Name)
}

f, err := os.Open(sqlCtx.inputFile)
if err != nil {
return nil, nil, err
}
return f, func() { _ = f.Close() }, nil
}

func runTerm(cmd *cobra.Command, args []string) error {
checkInteractive(os.Stdin)
cmdIn, closeFn, err := getInputFile()
if err != nil {
return err
}
defer closeFn()

checkInteractive(cmdIn)

if cliCtx.isInteractive {
// The user only gets to see the welcome message on interactive sessions.
Expand All @@ -1500,10 +1523,10 @@ func runTerm(cmd *cobra.Command, args []string) error {
}
defer conn.Close()

return runClient(cmd, conn)
return runClient(cmd, conn, cmdIn)
}

func runClient(cmd *cobra.Command, conn *sqlConn) error {
func runClient(cmd *cobra.Command, conn *sqlConn, cmdIn *os.File) error {
// Open the connection to make sure everything is OK before running any
// statements. Performs authentication.
if err := conn.ensureConn(); err != nil {
Expand All @@ -1513,7 +1536,7 @@ func runClient(cmd *cobra.Command, conn *sqlConn) error {
// Enable safe updates, unless disabled.
setupSafeUpdates(cmd, conn)

return runInteractive(conn, os.Stdin)
return runInteractive(conn, cmdIn)
}

// setupSafeUpdates attempts to enable "safe mode" if the session is
Expand Down
18 changes: 18 additions & 0 deletions pkg/cli/testdata/inputfile.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
--- input file for Example_read_from_file.

--- don't report timestamps: it makes the output non-deterministic.
\unset show_times

USE defaultdb;
CREATE TABLE test(s STRING);

-- make the reminder echo its SQL.
\set echo
INSERT INTO test(s) VALUES ('hello'), ('world');
SELECT * FROM test;

-- produce an error, to test that processing stops.
SELECT undefined;

-- this is not executed
SELECT 'unseen';

0 comments on commit cfda64a

Please sign in to comment.