diff --git a/docs/cli.rst b/docs/cli.rst index c07a74f..b3fbc1a 100644 --- a/docs/cli.rst +++ b/docs/cli.rst @@ -122,7 +122,21 @@ You can use the ``--json-cols`` option to automatically detect these JSON column } ] } - ]- + ] + +.. cli_attach: + +Attaching additional databases +------------------------------ + +SQLite supports cross-database SQL queries, which can join data from tables in more than one database file. + +You can attach one or more additional databases using the ``--attach`` option, providing an alias to use for that database and the path to the SQLite file on disk. + +This example attaches the ``books.db`` database under the alias ``books`` and then runs a query that combines data from that database with the default ``dogs.db`` database:: + + sqlite-utils dogs.db --attach books books.db \ + 'select * from sqlite_master union all select * from books.sqlite_master' .. _cli_query_csv: diff --git a/docs/python-api.rst b/docs/python-api.rst index d55f1f0..def0b14 100644 --- a/docs/python-api.rst +++ b/docs/python-api.rst @@ -50,7 +50,9 @@ Connections use ``PRAGMA recursive_triggers=on`` by default. If you don't want t Attaching additional databases ------------------------------ -SQLite supports cross-database SQL queries, which can join data from tables in more than one database file. You can attach an additional database using the ``.attach()`` method, providing an alias to use for that database and the path to the SQLite file on disk. +SQLite supports cross-database SQL queries, which can join data from tables in more than one database file. + +You can attach an additional database using the ``.attach()`` method, providing an alias to use for that database and the path to the SQLite file on disk. .. code-block:: python diff --git a/sqlite_utils/cli.py b/sqlite_utils/cli.py index 4cb080f..4b51565 100644 --- a/sqlite_utils/cli.py +++ b/sqlite_utils/cli.py @@ -1020,6 +1020,12 @@ def drop_view(path, view, load_extension): required=True, ) @click.argument("sql") +@click.option( + "--attach", + type=(str, click.Path(file_okay=True, dir_okay=False, allow_dash=False)), + multiple=True, + help="Additional databases to attach - specify alias and filepath", +) @output_options @click.option("-r", "--raw", is_flag=True, help="Raw output, first column of first row") @click.option( @@ -1033,6 +1039,7 @@ def drop_view(path, view, load_extension): def query( path, sql, + attach, nl, arrays, csv, @@ -1047,6 +1054,8 @@ def query( ): "Execute SQL query and return the results as JSON" db = sqlite_utils.Database(path) + for alias, attach_path in attach: + db.attach(alias, attach_path) _load_extensions(db, load_extension) db.register_fts4_bm25() with db.conn: diff --git a/tests/test_cli.py b/tests/test_cli.py index ce6e7d5..988b41f 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1855,3 +1855,25 @@ def test_csv_import_no_headers(tmpdir, args): {"untitled_1": "Cleo", "untitled_2": "Dog", "untitled_3": "5"}, {"untitled_1": "Tracy", "untitled_2": "Spider", "untitled_3": "7"}, ] + + +def test_attach(tmpdir): + foo_path = str(tmpdir / "foo.db") + bar_path = str(tmpdir / "bar.db") + db = Database(foo_path) + with db.conn: + db["foo"].insert({"id": 1, "text": "foo"}) + db2 = Database(bar_path) + with db2.conn: + db2["bar"].insert({"id": 1, "text": "bar"}) + db.attach("bar", bar_path) + sql = "select * from foo union all select * from bar.bar" + result = CliRunner().invoke( + cli.cli, + [foo_path, "--attach", "bar", bar_path, sql], + catch_exceptions=False, + ) + assert json.loads(result.output) == [ + {"id": 1, "text": "foo"}, + {"id": 1, "text": "bar"}, + ]