Skip to content

Commit

Permalink
cli/sql: add a new table display format "raw"
Browse files Browse the repository at this point in the history
**tl;dr** use `\set display_format raw` before a query to get the strings unmodified

Background
----------

One of the inconveniences of SHOW CREATE TABLE is that its output, by
default, encloses the generated CREATE TABLE statement in an ASCII art
table, like this:

````
root@:26257/> SHOW CREATE TABLE t.kv;
+-------+------------------------------------------------+
| Table |                  CreateTable                   |
+-------+------------------------------------------------+
| t.kv  | CREATE TABLE kv (␤                             |
|       |     k INT NOT NULL,␤                           |
|       |     v INT NULL,␤                               |
|       |     CONSTRAINT "primary" PRIMARY KEY (k ASC),␤ |
|       |     FAMILY "primary" (k, v)␤                   |
|       | )                                              |
+-------+------------------------------------------------+
(1 row)
```

This prevents copy-pasting the output directly onto some other
shell or text file.

Meanwhile, our SQL CLI shell *does* provide alternative ways to print
out the results, that can be selected with `\set display_format ...`;
however, none of the formats defined so far are able to print out the
above CREATE TABLE statement **in a way that can be copy-pasted
elsewhere**; for example:

```
root@:26257/> \set display_format html
root@:26257/> SHOW CREATE TABLE t.kv;
<table>
<thead><tr><th>Table</th><th>CreateTable</th></tr></head>
<tbody>
<tr><td>t.kv</td><td>CREATE TABLE kv (<br/>     k INT NOT NULL,<br/>    v INT NULL,<br/>
    CONSTRAINT &#34;primary&#34; PRIMARY KEY (k ASC),<br/>  FAMILY &#34;primary&#34; (k, v)<br/>)</td></tr>
</tbody>
</table>
```

(This replaces all special characters)

```
root@:26257/> \set display_format csv
root@:26257/> SHOW CREATE TABLE t.kv;
1 row
Table,CreateTable
t.kv,"CREATE TABLE kv (
        k INT NOT NULL,
        v INT NULL,
        CONSTRAINT ""primary"" PRIMARY KEY (k ASC),
        FAMILY ""primary"" (k, v)
)"
```

(This escapes the double quotes by doubling them, which renders the SQL invalid)

And so forth.

Solution: new display format
----------------------------

The `cockroach sql` CLI shell (as well as any built-in command that
emits tables) can use a custom display format configurable either with
`--format` on the command-line, or `\set display_format` in the shell.

This patch adds a new format `raw` which causes the string
representation of every record to be printed as-is, starting on its
own line, so that it can be copy-pasted from a terminal.

This format makes an extra mile to ensure the data can be further
processed: each record is prefixed by an annotation of the form `# N`
followed by a newline character, where *N* is the number of characters
in the record, starting with the first character following the
newline. A subsequent automated processing tool can use this
information to determine record boundaries.

Application
-----------

```
root@:26257/> select createtable from [show create table t.kv];
...
CREATE TABLE kv (
        k INT NOT NULL,
        v INT NULL,
        CONSTRAINT "primary" PRIMARY KEY (k ASC),
        FAMILY "primary" (k, v)
)
...
```

Tada!
  • Loading branch information
knz committed May 16, 2017
1 parent 7ddde64 commit fc063d0
Show file tree
Hide file tree
Showing 4 changed files with 78 additions and 2 deletions.
51 changes: 51 additions & 0 deletions pkg/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -822,6 +822,7 @@ func Example_sql_table() {
c.RunWithArgs([]string{"sql", "--format=csv", "-e", "select * from t.t"})
c.RunWithArgs([]string{"sql", "--format=sql", "-e", "select * from t.t"})
c.RunWithArgs([]string{"sql", "--format=html", "-e", "select * from t.t"})
c.RunWithArgs([]string{"sql", "--format=raw", "-e", "select * from t.t"})
c.RunWithArgs([]string{"sql", "--format=records", "-e", "select * from t.t"})
c.RunWithArgs([]string{"sql", "--format=pretty", "-e", "select ' hai' as x"})
c.RunWithArgs([]string{"sql", "--format=pretty", "-e", "explain(indent) select s from t.t union all select s from t.t"})
Expand Down Expand Up @@ -941,6 +942,56 @@ func Example_sql_table() {
// <tr><td>a b c<br/>12 123123213 12313</td><td>tabs</td></tr>
// </tbody>
// </table>
// sql --format=raw -e select * from t.t
// # 2 columns
// # row 1
// ## 3
// foo
// ## 15
// printable ASCII
// # row 2
// ## 4
// "foo
// ## 27
// printable ASCII with quotes
// # row 3
// ## 4
// \foo
// ## 30
// printable ASCII with backslash
// # row 4
// ## 7
// foo
// bar
// ## 19
// non-printable ASCII
// # row 5
// ## 11
// κόσμε
// ## 14
// printable UTF8
// # row 6
// ## 2
// ñ
// ## 28
// printable UTF8 using escapes
// # row 7
// ## 6
// "\x01"
// ## 25
// non-printable UTF8 string
// # row 8
// ## 4
// ܈85
// ## 25
// UTF8 string with RTL char
// # row 9
// ## 24
// a b c
// 12 123123213 12313
// ## 4
// tabs
// # 9 rows
// sql --format=records -e select * from t.t
// -[ RECORD 1 ]
// s | foo
Expand Down
7 changes: 6 additions & 1 deletion pkg/cli/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ const (
tableDisplayRecords
tableDisplaySQL
tableDisplayHTML
tableDisplayRaw
)

// Type implements the pflag.Value interface.
Expand All @@ -82,6 +83,8 @@ func (f *tableDisplayFormat) String() string {
return "sql"
case tableDisplayHTML:
return "html"
case tableDisplayRaw:
return "raw"
}
return ""
}
Expand All @@ -101,9 +104,11 @@ func (f *tableDisplayFormat) Set(s string) error {
*f = tableDisplaySQL
case "html":
*f = tableDisplayHTML
case "raw":
*f = tableDisplayRaw
default:
return fmt.Errorf("invalid table display format: %s "+
"(possible values: tsv, csv, pretty, records, sql, html)", s)
"(possible values: tsv, csv, pretty, records, sql, html, raw)", s)
}
return nil
}
Expand Down
20 changes: 20 additions & 0 deletions pkg/cli/format_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,26 @@ func printQueryOutput(
_ = csvWriter.Write(cols)
_ = csvWriter.WriteAll(allRowsSlice)

case tableDisplayRaw:
fmt.Fprintf(w, "# %d column%s\n", len(cols),
util.Pluralize(int64(len(cols))))
nRows := 0
for {
row, err := allRows.Next()
if err == io.EOF {
break
}
if err != nil {
return err
}
nRows++
fmt.Fprintf(w, "# row %d\n", nRows)
for _, r := range row {
fmt.Fprintf(w, "## %d\n%s\n", len(r), r)
}
}
fmt.Fprintf(w, "# %d row%s\n", nRows, util.Pluralize(int64(nRows)))

case tableDisplayHTML:
fmt.Fprint(w, "<table>\n<thead><tr>")
for _, col := range cols {
Expand Down
2 changes: 1 addition & 1 deletion pkg/cli/interactive_tests/test_local_cmds.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ start_test "Check that \\set display_format properly errors out"
send "\\set display_format blabla\r"
eexpect "invalid table display format"
# check we don't see a stray "cannot change option during multi-line editing" tacked at the end
eexpect "html)\r\n"
eexpect "html, raw)\r\n"
eexpect root@
end_test

Expand Down

0 comments on commit fc063d0

Please sign in to comment.