sqlite-utils --attach option, closes #236

This commit is contained in:
Simon Willison 2021-02-18 21:08:39 -08:00
commit 2ba5588881
4 changed files with 49 additions and 2 deletions

View file

@ -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:

View file

@ -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

View file

@ -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:

View file

@ -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"},
]