sqlite-utils duplicate command, closes #454, refs #449

Also made it so .duplicate() raises new NoTable exception rather than raising
an AssertionError if the source table does not exist.
This commit is contained in:
Simon Willison 2022-07-15 14:45:14 -07:00
commit c710ade644
7 changed files with 80 additions and 3 deletions

View file

@ -1176,6 +1176,20 @@ reset-counts
-h, --help Show this message and exit.
duplicate
=========
::
Usage: sqlite-utils duplicate [OPTIONS] PATH TABLE NEW_TABLE
Create a duplicate of this table, copying across the schema and all row data.
Options:
--load-extension TEXT SQLite extensions to load
-h, --help Show this message and exit.
drop-table
==========

View file

@ -1476,6 +1476,15 @@ You can specify foreign key relationships between the tables you are creating us
If a table with the same name already exists, you will get an error. You can choose to silently ignore this error with ``--ignore``, or you can replace the existing table with a new, empty table using ``--replace``.
.. _cli_duplicate_table:
Duplicating tables
==================
The ``duplicate`` command duplicates a table - creating a new table with the same schema and a copy of all of the rows::
$ sqlite-utils duplicate books.db authors authors_copy
.. _cli_drop_table:
Dropping tables

View file

@ -691,6 +691,8 @@ The ``table.duplicate()`` method creates a copy of the table, copying both the t
The new ``authors_copy`` table will now contain a duplicate copy of the data from ``authors``.
This method raises ``sqlite_utils.db.NoTable`` if the table does not exist.
.. _python_api_bulk_inserts:
Bulk inserts

View file

@ -5,7 +5,7 @@ from datetime import datetime
import hashlib
import pathlib
import sqlite_utils
from sqlite_utils.db import AlterError, BadMultiValues, DescIndex
from sqlite_utils.db import AlterError, BadMultiValues, DescIndex, NoTable
from sqlite_utils.utils import maximize_csv_field_size_limit
from sqlite_utils import recipes
import textwrap
@ -1465,6 +1465,27 @@ def create_table(
)
@cli.command(name="duplicate")
@click.argument(
"path",
type=click.Path(file_okay=True, dir_okay=False, allow_dash=False),
required=True,
)
@click.argument("table")
@click.argument("new_table")
@load_extension_option
def create_table(path, table, new_table, load_extension):
"""
Create a duplicate of this table, copying across the schema and all row data.
"""
db = sqlite_utils.Database(path)
_load_extensions(db, load_extension)
try:
db[table].duplicate(new_table)
except NoTable:
raise click.ClickException('Table "{}" does not exist'.format(table))
@cli.command(name="drop-table")
@click.argument(
"path",

View file

@ -1,3 +1,4 @@
from ast import Not
from .utils import (
chunks,
hash_record,
@ -224,6 +225,11 @@ class NoObviousTable(Exception):
pass
class NoTable(Exception):
"Specified table does not exist"
pass
class BadPrimaryKey(Exception):
"Table does not have a single obvious primary key"
pass
@ -1482,7 +1488,8 @@ class Table(Queryable):
:param new_name: Name of the new table
"""
assert self.exists()
if not self.exists():
raise NoTable(f"Table {self.name} does not exist")
with self.db.conn:
sql = "CREATE TABLE [{new_table}] AS SELECT * FROM [{table}];".format(
new_table=new_name,

View file

@ -2129,3 +2129,26 @@ def test_analyze(tmpdir, options, expected):
result = CliRunner().invoke(cli.cli, ["analyze", db_path] + options)
assert result.exit_code == 0
assert list(db["sqlite_stat1"].rows) == expected
def test_duplicate_table(tmpdir):
db_path = str(tmpdir / "test.db")
db = Database(db_path)
db["one"].insert({"id": 1, "name": "Cleo"}, pk="id")
# First try a non-existent table
result_error = CliRunner().invoke(
cli.cli,
["duplicate", db_path, "missing", "two"],
catch_exceptions=False,
)
assert result_error.exit_code == 1
assert result_error.output == 'Error: Table "missing" does not exist\n'
# Now try for a table that exists
result = CliRunner().invoke(
cli.cli,
["duplicate", db_path, "one", "two"],
catch_exceptions=False,
)
assert result.exit_code == 0
assert db["one"].columns_dict == db["two"].columns_dict
assert list(db["one"].rows) == list(db["two"].rows)

View file

@ -1,3 +1,4 @@
from sqlite_utils.db import NoTable
import datetime
import pytest
@ -38,5 +39,5 @@ def test_duplicate(fresh_db):
def test_duplicate_fails_if_table_does_not_exist(fresh_db):
with pytest.raises(AssertionError):
with pytest.raises(NoTable):
fresh_db["not_a_table"].duplicate("duplicated")