Skip to content

Commit

Permalink
Add insert --truncate option
Browse files Browse the repository at this point in the history
Deletes all rows in the table (if it exists) before inserting new rows.
SQLite doesn't implement a TRUNCATE TABLE statement but does optimize an
unqualified DELETE FROM.

This can be handy if you want to refresh the entire contents of a table
but a) don't have a PK (so can't use --replace) and b) don't want the
table to disappear (even briefly) for other connections.  The
replacement of rows should appear instantaneous to other connections
since the DELETE + INSERT are executed in a transaction.
  • Loading branch information
tsibley committed Jul 7, 2020
1 parent 737a6c7 commit 595acc3
Show file tree
Hide file tree
Showing 3 changed files with 38 additions and 1 deletion.
11 changes: 10 additions & 1 deletion sqlite_utils/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -423,6 +423,7 @@ def insert_upsert_implementation(
upsert,
ignore=False,
replace=False,
truncate=False,
not_null=None,
default=None,
):
Expand All @@ -442,7 +443,7 @@ def insert_upsert_implementation(
docs = json.load(json_file)
if isinstance(docs, dict):
docs = [docs]
extra_kwargs = {"ignore": ignore, "replace": replace}
extra_kwargs = {"ignore": ignore, "replace": replace, "truncate": truncate}
if not_null:
extra_kwargs["not_null"] = set(not_null)
if default:
Expand All @@ -465,6 +466,12 @@ def insert_upsert_implementation(
default=False,
help="Replace records if pk already exists",
)
@click.option(
"--truncate",
is_flag=True,
default=False,
help="Truncate table before inserting records, if table already exists",
)
def insert(
path,
table,
Expand All @@ -477,6 +484,7 @@ def insert(
alter,
ignore,
replace,
truncate,
not_null,
default,
):
Expand All @@ -499,6 +507,7 @@ def insert(
upsert=False,
ignore=ignore,
replace=replace,
truncate=truncate,
not_null=not_null,
default=default,
)
Expand Down
3 changes: 3 additions & 0 deletions sqlite_utils/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -976,6 +976,7 @@ def insert_all(
alter=DEFAULT,
ignore=DEFAULT,
replace=DEFAULT,
truncate=False,
extracts=DEFAULT,
conversions=DEFAULT,
columns=DEFAULT,
Expand Down Expand Up @@ -1032,6 +1033,8 @@ def insert_all(
# issue implicit BEGINs for DDL, only DML. We mix DDL and DML
# below and might execute DDL first, e.g. for table creation.
self.db.conn.execute("BEGIN")
if truncate and self.exists():
self.db.conn.execute("DELETE FROM [{}];".format(self.name))
for chunk in chunks(itertools.chain([first_record], records), batch_size):
chunk = list(chunk)
num_records_processed += len(chunk)
Expand Down
25 changes: 25 additions & 0 deletions tests/test_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -642,6 +642,31 @@ def test_insert_replace(db_path, tmpdir):
)


def test_insert_truncate(db_path):
result = CliRunner().invoke(
cli.cli,
["insert", db_path, "from_json_nl", "-", "--nl", "--batch-size=1"],
input='{"foo": "bar", "n": 1}\n{"foo": "baz", "n": 2}',
)
assert 0 == result.exit_code, result.output
db = Database(db_path)
assert [
{"foo": "bar", "n": 1},
{"foo": "baz", "n": 2},
] == db.execute_returning_dicts("select foo, n from from_json_nl")
# Truncate and insert new rows
result = CliRunner().invoke(
cli.cli,
["insert", db_path, "from_json_nl", "-", "--nl", "--truncate", "--batch-size=1"],
input='{"foo": "bam", "n": 3}\n{"foo": "bat", "n": 4}',
)
assert 0 == result.exit_code, result.output
assert [
{"foo": "bam", "n": 3},
{"foo": "bat", "n": 4},
] == db.execute_returning_dicts("select foo, n from from_json_nl")


def test_insert_alter(db_path, tmpdir):
result = CliRunner().invoke(
cli.cli,
Expand Down

0 comments on commit 595acc3

Please sign in to comment.