diff --git a/docs/cli-reference.rst b/docs/cli-reference.rst index c53d64222..df8eebb70 100644 --- a/docs/cli-reference.rst +++ b/docs/cli-reference.rst @@ -711,7 +711,7 @@ See :ref:`cli_tables`. :: - Usage: sqlite-utils tables [OPTIONS] PATH + Usage: sqlite-utils tables [OPTIONS] PATH [NAMES]... List the tables in the database @@ -719,6 +719,10 @@ See :ref:`cli_tables`. sqlite-utils tables trees.db + Pass one or more table names to restrict the output to just those tables: + + sqlite-utils tables trees.db plants seeds + Options: --fts4 Just show FTS4 enabled tables --fts5 Just show FTS5 enabled tables @@ -756,7 +760,7 @@ See :ref:`cli_views`. :: - Usage: sqlite-utils views [OPTIONS] PATH + Usage: sqlite-utils views [OPTIONS] PATH [NAMES]... List the views in the database @@ -764,6 +768,10 @@ See :ref:`cli_views`. sqlite-utils views trees.db + Pass one or more view names to restrict the output to just those views: + + sqlite-utils views trees.db recent_plants + Options: --counts Include row counts per view --nl Output newline-delimited JSON diff --git a/docs/cli.rst b/docs/cli.rst index 78c33b8ae..e41e90142 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -747,6 +747,19 @@ You can list the names of tables in a database using the ``tables`` command: {"table": "cats"}, {"table": "chickens"}] +Pass one or more table names to restrict the output to just those tables. This is useful with ``--counts`` against a database that has a large table you want to skip: + +.. code-block:: bash + + sqlite-utils tables mydb.db dogs cats --counts + +.. code-block:: output + + [{"table": "dogs", "count": 12}, + {"table": "cats", "count": 332}] + +The tables are listed in the order you name them. An error is raised, and a non-zero exit code returned, if any of the named tables do not exist. + You can output this list in CSV using the ``--csv`` or ``--tsv`` options: .. code-block:: bash @@ -843,6 +856,8 @@ It takes the same options as the ``tables`` command: * ``--tsv`` * ``--table`` +As with ``tables``, you can pass one or more view names to restrict the output to just those views. + .. note:: In Python: :ref:`db.views or db.view_names() ` CLI reference: :ref:`sqlite-utils views ` diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index c23090283..7ecb2ec38 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -187,6 +187,7 @@ def cli(): type=click.Path(exists=True, file_okay=True, dir_okay=False, allow_dash=False), required=True, ) +@click.argument("names", nargs=-1) @click.option( "--fts4", help="Just show FTS4 enabled tables", default=False, is_flag=True ) @@ -212,6 +213,7 @@ def cli(): @load_extension_option def tables( path, + names, fts4, fts5, counts, @@ -235,6 +237,11 @@ def tables( \b sqlite-utils tables trees.db + + Pass one or more table names to restrict the output to just those tables: + + \b + sqlite-utils tables trees.db plants seeds """ db = sqlite_utils.Database(path) _register_db_for_cleanup(db) @@ -249,8 +256,25 @@ def tables( method = db.view if views else db.table + if names: + existing = set(db.view_names() if views else db.table_names()) + missing = [name for name in names if name not in existing] + if missing: + label = "view" if views else "table" + if len(missing) == 1: + message = "The following {} does not exist: {}".format( + label, missing[0] + ) + else: + message = "The following {}s do not exist: {}".format( + label, ", ".join(missing) + ) + raise click.ClickException(message) + def _iter(): - if views: + if names: + items = list(names) + elif views: items = db.view_names() else: items = db.table_names(fts4=fts4, fts5=fts5) @@ -293,6 +317,7 @@ def _iter(): type=click.Path(exists=True, file_okay=True, dir_okay=False, allow_dash=False), required=True, ) +@click.argument("names", nargs=-1) @click.option( "--counts", help="Include row counts per view", default=False, is_flag=True ) @@ -312,6 +337,7 @@ def _iter(): @load_extension_option def views( path, + names, counts, nl, arrays, @@ -332,10 +358,16 @@ def views( \b sqlite-utils views trees.db + + Pass one or more view names to restrict the output to just those views: + + \b + sqlite-utils views trees.db recent_plants """ assert tables.callback is not None tables.callback( path=path, + names=names, fts4=False, fts5=False, counts=counts, diff --git a/tests/test_cli.py b/tests/test_cli.py index 064026a0b..9c9487255 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -140,6 +140,57 @@ def test_tables_schema(db_path): ) == result.output.strip() +def test_tables_specific_names(db_path): + result = CliRunner().invoke( + cli.cli, ["tables", db_path, "Gosh2"], catch_exceptions=False + ) + assert '[{"table": "Gosh2"}]' == result.output.strip() + + +def test_tables_specific_names_preserve_argument_order(db_path): + result = CliRunner().invoke( + cli.cli, ["tables", db_path, "Gosh2", "Gosh"], catch_exceptions=False + ) + assert '[{"table": "Gosh2"},\n {"table": "Gosh"}]' == result.output.strip() + + +def test_tables_specific_names_with_counts(db_path): + result = CliRunner().invoke( + cli.cli, ["tables", db_path, "Gosh", "--counts"], catch_exceptions=False + ) + assert '[{"table": "Gosh", "count": 0}]' == result.output.strip() + + +def test_tables_missing_name_errors(db_path): + result = CliRunner().invoke(cli.cli, ["tables", db_path, "Gosh", "nope"]) + assert result.exit_code == 1 + assert "The following table does not exist: nope" in result.output + + +def test_tables_multiple_missing_names_errors(db_path): + result = CliRunner().invoke(cli.cli, ["tables", db_path, "nope", "nope2"]) + assert result.exit_code == 1 + assert "The following tables do not exist: nope, nope2" in result.output + + +def test_views_specific_names(db_path): + db = Database(db_path) + db.create_view("v1", "select 1") + db.create_view("v2", "select 2") + result = CliRunner().invoke( + cli.cli, ["views", db_path, "v2"], catch_exceptions=False + ) + assert '[{"view": "v2"}]' == result.output.strip() + + +def test_views_missing_name_errors(db_path): + db = Database(db_path) + db.create_view("v1", "select 1") + result = CliRunner().invoke(cli.cli, ["views", db_path, "nope"]) + assert result.exit_code == 1 + assert "The following view does not exist: nope" in result.output + + @pytest.mark.parametrize( "options,expected", [