Skip to content

Commit

Permalink
cli/sql: new output format json alongside ndjson
Browse files Browse the repository at this point in the history
Release note (cli change): The CLI commands that output SQL data
now support the output format JSON (`--format=json`), in addition
to newline-delimited JSON (ND-JSON, `--format=ndjson`) that had been
supported since v22.2.
  • Loading branch information
knz committed May 12, 2023
1 parent 2ad7ba6 commit 06201b9
Show file tree
Hide file tree
Showing 5 changed files with 83 additions and 21 deletions.
56 changes: 40 additions & 16 deletions pkg/cli/clisqlexec/format_json.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,43 +15,67 @@ import (
"io"
)

type ndjsonReporter struct {
cols []string
type jsonReporter struct {
start, end, middle []byte
currentPrefix []byte
cols []string
rowMap map[string]string
}

func (n *ndjsonReporter) describe(w io.Writer, cols []string) error {
func (n *jsonReporter) describe(w io.Writer, cols []string) error {
n.cols = cols
return nil
n.rowMap = make(map[string]string, len(cols))
for _, col := range cols {
n.rowMap[col] = ""
}
_, err := w.Write(n.start)
return err
}

func (n *ndjsonReporter) beforeFirstRow(w io.Writer, allRows RowStrIter) error {
func (n *jsonReporter) beforeFirstRow(w io.Writer, allRows RowStrIter) error {
return nil
}

func (n *ndjsonReporter) iter(w, ew io.Writer, rowIdx int, row []string) error {
retMap := make(map[string]string, len(row))
func (n *jsonReporter) iter(w, ew io.Writer, rowIdx int, row []string) error {
for i := range row {
retMap[n.cols[i]] = row[i]
n.rowMap[n.cols[i]] = row[i]
}
out, err := json.Marshal(retMap)
out, err := json.Marshal(n.rowMap)
if err != nil {
return err
}
if _, err := w.Write(out); err != nil {
if _, err := w.Write(n.currentPrefix); err != nil {
return err
}
if _, err := w.Write(newLineChar); err != nil {
n.currentPrefix = n.middle
if _, err := w.Write(out); err != nil {
return err
}
return nil
}

var newLineChar = []byte("\n")
func (n *jsonReporter) doneRows(w io.Writer, seenRows int) error {
_, err := w.Write(n.end)
return err
}

func (n *ndjsonReporter) doneRows(w io.Writer, seenRows int) error {
return nil
func (n *jsonReporter) doneNoRows(w io.Writer) error {
_, err := w.Write(n.end)
return err
}

func (n *ndjsonReporter) doneNoRows(w io.Writer) error {
return nil
func makeJSONReporter(format TableDisplayFormat) *jsonReporter {
r := &jsonReporter{
start: []byte("["),
end: []byte("\n]\n"),
middle: []byte(",\n "),
currentPrefix: []byte("\n "),
}
if format == TableDisplayNDJSON {
r.start = nil
r.currentPrefix = nil
r.end = []byte("\n")
r.middle = []byte("\n")
}
return r
}
4 changes: 3 additions & 1 deletion pkg/cli/clisqlexec/format_table.go
Original file line number Diff line number Diff line change
Expand Up @@ -254,7 +254,9 @@ func (sqlExecCtx *Context) makeReporter(w io.Writer) (rowReporter, func(), error
return reporter, cleanup, nil

case TableDisplayNDJSON:
return &ndjsonReporter{}, nil, nil
fallthrough
case TableDisplayJSON:
return makeJSONReporter(sqlExecCtx.TableDisplayFormat), nil, nil

case TableDisplayRaw:
return &rawReporter{}, nil, nil
Expand Down
30 changes: 30 additions & 0 deletions pkg/cli/clisqlexec/format_table_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,10 @@ thenshort`,
// ܈85 | 0
// sql --format=ndjson -e select * from t.u
// {"a|b":"0","f\"oo":"0","f'oo":"0","f\\oo":"0","short\nvery very long\nnot much":"0","very very long\nthenshort":"0","κόσμε":"0","܈85":"0"}
// sql --format=json -e select * from t.u
// [
// {"a|b":"0","f\"oo":"0","f'oo":"0","f\\oo":"0","short\nvery very long\nnot much":"0","very very long\nthenshort":"0","κόσμε":"0","܈85":"0"}
// ]
// sql --format=sql -e select * from t.u
// CREATE TABLE results (
// "f""oo" STRING,
Expand Down Expand Up @@ -220,6 +224,10 @@ func Example_sql_empty_table() {
// (0 rows)
// sql --format=records -e select * from t.norows
// sql --format=ndjson -e select * from t.norows
//
// sql --format=json -e select * from t.norows
// [
// ]
// sql --format=sql -e select * from t.norows
// CREATE TABLE results (
// x STRING
Expand Down Expand Up @@ -258,6 +266,12 @@ func Example_sql_empty_table() {
// {}
// {}
// {}
// sql --format=json -e select * from t.nocols
// [
// {},
// {},
// {}
// ]
// sql --format=sql -e select * from t.nocols
// CREATE TABLE results (
// );
Expand Down Expand Up @@ -300,6 +314,10 @@ func Example_sql_empty_table() {
// sql --format=records -e select * from t.nocolsnorows
// (0 rows)
// sql --format=ndjson -e select * from t.nocolsnorows
//
// sql --format=json -e select * from t.nocolsnorows
// [
// ]
// sql --format=sql -e select * from t.nocolsnorows
// CREATE TABLE results (
// );
Expand Down Expand Up @@ -626,6 +644,18 @@ func Example_sql_table() {
// {"d":"non-printable UTF8 string","s":"\\x01"}
// {"d":"UTF8 string with RTL char","s":"܈85"}
// {"d":"tabs","s":"a\tb\tc\n12\t123123213\t12313"}
// sql --format=json -e select * from t.t
// [
// {"d":"printable ASCII","s":"foo"},
// {"d":"printable ASCII with quotes","s":"\"foo"},
// {"d":"printable ASCII with backslash","s":"\\foo"},
// {"d":"non-printable ASCII","s":"foo\nbar"},
// {"d":"printable UTF8","s":"κόσμε"},
// {"d":"printable UTF8 using escapes","s":"ñ"},
// {"d":"non-printable UTF8 string","s":"\\x01"},
// {"d":"UTF8 string with RTL char","s":"܈85"},
// {"d":"tabs","s":"a\tb\tc\n12\t123123213\t12313"}
// ]
// sql --format=sql -e select * from t.t
// CREATE TABLE results (
// s STRING,
Expand Down
10 changes: 8 additions & 2 deletions pkg/cli/clisqlexec/table_display_format.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,9 +37,11 @@ const (
// TableDisplayRecords is a record-oriented format. It is somewhat
// compatible with 'psql' "expanded display" mode.
TableDisplayRecords
// TableDisplayNDJSON reports results in an nd-json format
// (https://github.com/ndjson/ndjson-spec).
// TableDisplayNDJSON reports results in an newlined-delimited JSON
// format (https://github.com/ndjson/ndjson-spec).
TableDisplayNDJSON
// TableDisplayJSON reports results using JSON.
TableDisplayJSON
// TableDisplaySQL reports results using SQL statements that mimic
// the creation of a SQL table containing the result values.
TableDisplaySQL
Expand Down Expand Up @@ -95,6 +97,8 @@ func (f *TableDisplayFormat) String() string {
return "sql"
case TableDisplayNDJSON:
return "ndjson"
case TableDisplayJSON:
return "json"
case TableDisplayHTML:
return "html"
case TableDisplayRawHTML:
Expand All @@ -120,6 +124,8 @@ func (f *TableDisplayFormat) Set(s string) error {
*f = TableDisplaySQL
case "ndjson":
*f = TableDisplayNDJSON
case "json":
*f = TableDisplayJSON
case "html":
*f = TableDisplayHTML
case "rawhtml":
Expand Down
4 changes: 2 additions & 2 deletions pkg/cli/clisqlshell/sql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -163,10 +163,10 @@ func Example_sql_config() {
// HINT: Try \? for help.
// sql --set display_format=invalidvalue -e select 123 as "123"
// ERROR: -e: \set display_format=invalidvalue: invalid table display format: invalidvalue
// HINT: Possible values: tsv, csv, table, records, ndjson, sql, html, raw.
// HINT: Possible values: tsv, csv, table, records, ndjson, json, sql, html, raw.
// sql -e \set display_format=invalidvalue -e select 123 as "123"
// ERROR: -e: \set display_format=invalidvalue: invalid table display format: invalidvalue
// HINT: Possible values: tsv, csv, table, records, ndjson, sql, html, raw.
// HINT: Possible values: tsv, csv, table, records, ndjson, json, sql, html, raw.
}

func Example_sql_watch() {
Expand Down

0 comments on commit 06201b9

Please sign in to comment.