mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-09-07 17:14:09 +02:00
Add --transpose/-x option to rows, query, memory and search for wide records
Displays each row as a psql-style key/value block (\x extended display) instead of a wide table, while leaving JSON/CSV/table output untouched (#535).
This commit is contained in:
parent
f6d83ec8be
commit
050f11f31f
4 changed files with 126 additions and 2 deletions
|
|
@ -136,6 +136,8 @@ See :ref:`cli_query`.
|
|||
escaped strings
|
||||
--ascii Escape non-ASCII characters in JSON output as
|
||||
\uXXXX
|
||||
-x, --transpose Transpose wide rows, one key/value pair per line
|
||||
(like psql's \x)
|
||||
-r, --raw Raw output, first column of first row
|
||||
--raw-lines Raw output, first column of each row
|
||||
-p, --param <TEXT TEXT>... Named :parameters for SQL query
|
||||
|
|
@ -207,6 +209,8 @@ See :ref:`cli_memory`.
|
|||
escaped strings
|
||||
--ascii Escape non-ASCII characters in JSON output as
|
||||
\uXXXX
|
||||
-x, --transpose Transpose wide rows, one key/value pair per line
|
||||
(like psql's \x)
|
||||
-r, --raw Raw output, first column of first row
|
||||
--raw-lines Raw output, first column of each row
|
||||
-p, --param <TEXT TEXT>... Named :parameters for SQL query
|
||||
|
|
@ -478,6 +482,8 @@ See :ref:`cli_search`.
|
|||
--json-cols Detect JSON cols and output them as JSON, not escaped
|
||||
strings
|
||||
--ascii Escape non-ASCII characters in JSON output as \uXXXX
|
||||
-x, --transpose Transpose wide rows, one key/value pair per line (like
|
||||
psql's \x)
|
||||
--load-extension TEXT Path to SQLite extension, with optional :entrypoint
|
||||
-h, --help Show this message and exit.
|
||||
|
||||
|
|
@ -842,6 +848,8 @@ See :ref:`cli_rows`.
|
|||
escaped strings
|
||||
--ascii Escape non-ASCII characters in JSON output as
|
||||
\uXXXX
|
||||
-x, --transpose Transpose wide rows, one key/value pair per line
|
||||
(like psql's \x)
|
||||
--load-extension TEXT Path to SQLite extension, with optional
|
||||
:entrypoint
|
||||
-h, --help Show this message and exit.
|
||||
|
|
|
|||
29
docs/cli.rst
29
docs/cli.rst
|
|
@ -337,6 +337,31 @@ Available ``--fmt`` options are:
|
|||
|
||||
This list can also be found by running ``sqlite-utils query --help``.
|
||||
|
||||
.. _cli_query_transpose:
|
||||
|
||||
Transposed output for wide rows
|
||||
--------------------------------
|
||||
|
||||
If your rows have a lot of columns they can be hard to read as a table.
|
||||
Use the ``--transpose`` option (or the ``-x`` shortcut, matching ``psql``'s ``\x``) to display each row as a block of key/value pairs instead:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
sqlite-utils dogs.db "select * from dogs" --transpose
|
||||
|
||||
.. code-block:: output
|
||||
|
||||
-[ RECORD 1 ]--
|
||||
id | 1
|
||||
age | 4
|
||||
name | Cleo
|
||||
-[ RECORD 2 ]--
|
||||
id | 2
|
||||
age | 2
|
||||
name | Pancakes
|
||||
|
||||
Combine this with ``--no-headers`` to omit the ``-[ RECORD n ]-`` separator lines.
|
||||
|
||||
.. _cli_query_raw:
|
||||
|
||||
Returning raw data, such as binary content
|
||||
|
|
@ -480,7 +505,7 @@ Without any extra arguments, this command executes SQL against the in-memory dat
|
|||
|
||||
[{"sqlite_version()": "3.35.5"}]
|
||||
|
||||
It takes all of the same output formatting options as :ref:`sqlite-utils query <cli_query>`: ``--csv`` and ``--csv`` and ``--table`` and ``--nl``:
|
||||
It takes all of the same output formatting options as :ref:`sqlite-utils query <cli_query>`: ``--csv`` and ``--table`` and ``--nl`` and ``--transpose``:
|
||||
|
||||
.. code-block:: bash
|
||||
|
||||
|
|
@ -690,7 +715,7 @@ You can return every row in a specified table using the ``rows`` command:
|
|||
[{"id": 1, "age": 4, "name": "Cleo"},
|
||||
{"id": 2, "age": 2, "name": "Pancakes"}]
|
||||
|
||||
This command accepts the same output options as ``query`` - so you can pass ``--nl``, ``--csv``, ``--tsv``, ``--no-headers``, ``--table`` and ``--fmt``.
|
||||
This command accepts the same output options as ``query`` - so you can pass ``--nl``, ``--csv``, ``--tsv``, ``--no-headers``, ``--table``, ``--fmt`` and ``--transpose`` (see :ref:`cli_query_transpose`).
|
||||
|
||||
You can use the ``-c`` option to specify a subset of columns to return:
|
||||
|
||||
|
|
|
|||
|
|
@ -160,6 +160,15 @@ def load_extension_option(fn):
|
|||
)(fn)
|
||||
|
||||
|
||||
def transpose_option(fn):
|
||||
return click.option(
|
||||
"-x",
|
||||
"--transpose",
|
||||
is_flag=True,
|
||||
help="Transpose wide rows, one key/value pair per line (like psql's \\x)",
|
||||
)(fn)
|
||||
|
||||
|
||||
def functions_option(fn):
|
||||
return click.option(
|
||||
"--functions",
|
||||
|
|
@ -1992,6 +2001,7 @@ def drop_view(path, view, ignore, load_extension):
|
|||
help="Additional databases to attach - specify alias and filepath",
|
||||
)
|
||||
@output_options
|
||||
@transpose_option
|
||||
@click.option("-r", "--raw", is_flag=True, help="Raw output, first column of first row")
|
||||
@click.option("--raw-lines", is_flag=True, help="Raw output, first column of each row")
|
||||
@click.option(
|
||||
|
|
@ -2016,6 +2026,7 @@ def query(
|
|||
fmt,
|
||||
json_cols,
|
||||
ascii_,
|
||||
transpose,
|
||||
raw,
|
||||
raw_lines,
|
||||
param,
|
||||
|
|
@ -2063,6 +2074,7 @@ def query(
|
|||
arrays,
|
||||
json_cols,
|
||||
ascii_,
|
||||
transpose,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -2087,6 +2099,7 @@ def query(
|
|||
help='Flatten nested JSON objects, so {"foo": {"bar": 1}} becomes {"foo_bar": 1}',
|
||||
)
|
||||
@output_options
|
||||
@transpose_option
|
||||
@click.option("-r", "--raw", is_flag=True, help="Raw output, first column of first row")
|
||||
@click.option("--raw-lines", is_flag=True, help="Raw output, first column of each row")
|
||||
@click.option(
|
||||
|
|
@ -2134,6 +2147,7 @@ def memory(
|
|||
fmt,
|
||||
json_cols,
|
||||
ascii_,
|
||||
transpose,
|
||||
raw,
|
||||
raw_lines,
|
||||
param,
|
||||
|
|
@ -2274,6 +2288,7 @@ def memory(
|
|||
arrays,
|
||||
json_cols,
|
||||
ascii_,
|
||||
transpose,
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -2292,6 +2307,7 @@ def _execute_query(
|
|||
arrays,
|
||||
json_cols,
|
||||
ascii_,
|
||||
transpose=False,
|
||||
):
|
||||
with db.conn:
|
||||
try:
|
||||
|
|
@ -2319,6 +2335,9 @@ def _execute_query(
|
|||
sys.stdout.buffer.write(data + b"\n")
|
||||
else:
|
||||
sys.stdout.write(str(data) + "\n")
|
||||
elif transpose:
|
||||
for line in output_transpose(cursor, headers, no_headers):
|
||||
click.echo(line)
|
||||
elif fmt or table:
|
||||
print(
|
||||
tabulate.tabulate(
|
||||
|
|
@ -2358,6 +2377,7 @@ def _execute_query(
|
|||
)
|
||||
@click.option("--quote", is_flag=True, help="Apply FTS quoting rules to search term")
|
||||
@output_options
|
||||
@transpose_option
|
||||
@load_extension_option
|
||||
@click.pass_context
|
||||
def search(
|
||||
|
|
@ -2379,6 +2399,7 @@ def search(
|
|||
fmt,
|
||||
json_cols,
|
||||
ascii_,
|
||||
transpose,
|
||||
load_extension,
|
||||
):
|
||||
"""Execute a full-text search against this table
|
||||
|
|
@ -2424,6 +2445,7 @@ def search(
|
|||
fmt=fmt,
|
||||
json_cols=json_cols,
|
||||
ascii_=ascii_,
|
||||
transpose=transpose,
|
||||
param=[("query", q)],
|
||||
load_extension=load_extension,
|
||||
)
|
||||
|
|
@ -2464,6 +2486,7 @@ def search(
|
|||
help="SQL offset to use",
|
||||
)
|
||||
@output_options
|
||||
@transpose_option
|
||||
@load_extension_option
|
||||
@click.pass_context
|
||||
def rows(
|
||||
|
|
@ -2485,6 +2508,7 @@ def rows(
|
|||
fmt,
|
||||
json_cols,
|
||||
ascii_,
|
||||
transpose,
|
||||
load_extension,
|
||||
):
|
||||
"""Output all rows in the specified table
|
||||
|
|
@ -2520,6 +2544,7 @@ def rows(
|
|||
param=param,
|
||||
json_cols=json_cols,
|
||||
ascii_=ascii_,
|
||||
transpose=transpose,
|
||||
load_extension=load_extension,
|
||||
)
|
||||
|
||||
|
|
@ -3798,6 +3823,20 @@ def output_rows(iterator, headers, nl, arrays, json_cols, ascii_=False):
|
|||
yield "[]"
|
||||
|
||||
|
||||
def output_transpose(rows, headers, no_headers):
|
||||
# psql-style extended display: one "key = value" block per row
|
||||
headers = [str(h) for h in headers]
|
||||
str_rows = [["" if v is None else str(v) for v in row] for row in rows]
|
||||
key_width = max([len(h) for h in headers], default=0)
|
||||
val_width = max([len(v) for row in str_rows for v in row], default=0)
|
||||
total_width = key_width + 3 + val_width
|
||||
for i, row in enumerate(str_rows, start=1):
|
||||
if not no_headers:
|
||||
yield "-[ RECORD {} ]-".format(i).ljust(total_width, "-")
|
||||
for header, value in zip(headers, row):
|
||||
yield "{} | {}".format(header.ljust(key_width), value)
|
||||
|
||||
|
||||
def maybe_json(value):
|
||||
if not isinstance(value, str):
|
||||
return value
|
||||
|
|
|
|||
|
|
@ -760,6 +760,58 @@ def test_query_csv(db_path, format, expected):
|
|||
assert result.output.strip().replace("\r", "") == expected_rest
|
||||
|
||||
|
||||
def test_query_transpose(db_path):
|
||||
db = Database(db_path)
|
||||
with db.conn:
|
||||
db["dogs"].insert_all(
|
||||
[
|
||||
{"id": 1, "age": 4, "name": "Cleo"},
|
||||
{"id": 2, "age": 2, "name": "Pancakes"},
|
||||
]
|
||||
)
|
||||
result = CliRunner().invoke(
|
||||
cli.cli,
|
||||
[db_path, "select id, name, age from dogs", "--transpose"],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert result.output == (
|
||||
"-[ RECORD 1 ]--\n"
|
||||
"id | 1\n"
|
||||
"name | Cleo\n"
|
||||
"age | 4\n"
|
||||
"-[ RECORD 2 ]--\n"
|
||||
"id | 2\n"
|
||||
"name | Pancakes\n"
|
||||
"age | 2\n"
|
||||
)
|
||||
# -x is a shorthand for --transpose
|
||||
result2 = CliRunner().invoke(
|
||||
cli.cli, [db_path, "select id, name, age from dogs", "-x"]
|
||||
)
|
||||
assert result2.output == result.output
|
||||
# --no-headers drops the "-[ RECORD n ]-" separators
|
||||
result3 = CliRunner().invoke(
|
||||
cli.cli,
|
||||
[db_path, "select id, name, age from dogs", "--transpose", "--no-headers"],
|
||||
)
|
||||
assert result3.exit_code == 0
|
||||
assert (
|
||||
result3.output
|
||||
== "id | 1\nname | Cleo\nage | 4\nid | 2\nname | Pancakes\nage | 2\n"
|
||||
)
|
||||
|
||||
|
||||
def test_rows_transpose(db_path):
|
||||
db = Database(db_path)
|
||||
with db.conn:
|
||||
db["dogs"].insert_all(
|
||||
[{"id": 1, "name": "Cleo", "age": 4}], column_order=("id", "name", "age")
|
||||
)
|
||||
result = CliRunner().invoke(cli.cli, ["rows", db_path, "dogs", "-x"])
|
||||
assert result.exit_code == 0
|
||||
assert result.output == "-[ RECORD 1 ]-\nid | 1\nname | Cleo\nage | 4\n"
|
||||
|
||||
|
||||
_all_query = "select id, name, age from dogs"
|
||||
_one_query = "select id, name, age from dogs where id = 1"
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue