Use db.table() and db.view() in tests, closes #838

This commit is contained in:
Simon Willison 2026-08-12 13:40:55 -07:00
commit 99fd585daf
43 changed files with 1321 additions and 1254 deletions

View file

@ -3,11 +3,13 @@ import pytest
@pytest.fixture @pytest.fixture
def db(fresh_db): def db(fresh_db):
fresh_db["one_index"].insert({"id": 1, "name": "Cleo"}, pk="id") fresh_db.table("one_index").insert({"id": 1, "name": "Cleo"}, pk="id")
fresh_db["one_index"].create_index(["name"]) fresh_db.table("one_index").create_index(["name"])
fresh_db["two_indexes"].insert({"id": 1, "name": "Cleo", "species": "dog"}, pk="id") fresh_db.table("two_indexes").insert(
fresh_db["two_indexes"].create_index(["name"]) {"id": 1, "name": "Cleo", "species": "dog"}, pk="id"
fresh_db["two_indexes"].create_index(["species"]) )
fresh_db.table("two_indexes").create_index(["name"])
fresh_db.table("two_indexes").create_index(["species"])
return fresh_db return fresh_db
@ -17,7 +19,7 @@ def test_analyze_whole_database(db):
assert set(db.table_names()).issuperset( assert set(db.table_names()).issuperset(
{"one_index", "two_indexes", "sqlite_stat1"} {"one_index", "two_indexes", "sqlite_stat1"}
) )
assert list(db["sqlite_stat1"].rows) == [ assert list(db.table("sqlite_stat1").rows) == [
{"tbl": "two_indexes", "idx": "idx_two_indexes_species", "stat": "1 1"}, {"tbl": "two_indexes", "idx": "idx_two_indexes_species", "stat": "1 1"},
{"tbl": "two_indexes", "idx": "idx_two_indexes_name", "stat": "1 1"}, {"tbl": "two_indexes", "idx": "idx_two_indexes_name", "stat": "1 1"},
{"tbl": "one_index", "idx": "idx_one_index_name", "stat": "1 1"}, {"tbl": "one_index", "idx": "idx_one_index_name", "stat": "1 1"},
@ -30,12 +32,12 @@ def test_analyze_one_table(db, method):
if method == "db_method_with_name": if method == "db_method_with_name":
db.analyze("one_index") db.analyze("one_index")
elif method == "table_method": elif method == "table_method":
db["one_index"].analyze() db.table("one_index").analyze()
assert set(db.table_names()).issuperset( assert set(db.table_names()).issuperset(
{"one_index", "two_indexes", "sqlite_stat1"} {"one_index", "two_indexes", "sqlite_stat1"}
) )
assert list(db["sqlite_stat1"].rows) == [ assert list(db.table("sqlite_stat1").rows) == [
{"tbl": "one_index", "idx": "idx_one_index_name", "stat": "1 1"} {"tbl": "one_index", "idx": "idx_one_index_name", "stat": "1 1"}
] ]
@ -46,6 +48,6 @@ def test_analyze_index_by_name(db):
assert set(db.table_names()).issuperset( assert set(db.table_names()).issuperset(
{"one_index", "two_indexes", "sqlite_stat1"} {"one_index", "two_indexes", "sqlite_stat1"}
) )
assert list(db["sqlite_stat1"].rows) == [ assert list(db.table("sqlite_stat1").rows) == [
{"tbl": "two_indexes", "idx": "idx_two_indexes_species", "stat": "1 1"}, {"tbl": "two_indexes", "idx": "idx_two_indexes_species", "stat": "1 1"},
] ]

View file

@ -9,7 +9,7 @@ from sqlite_utils.db import ColumnDetails, Database
@pytest.fixture @pytest.fixture
def db_to_analyze(fresh_db): def db_to_analyze(fresh_db):
stuff = fresh_db["stuff"] stuff = fresh_db.table("stuff")
stuff.insert_all( stuff.insert_all(
[ [
{"id": 1, "owner": "Terryterryterry", "size": 5}, {"id": 1, "owner": "Terryterryterry", "size": 5},
@ -45,7 +45,7 @@ def big_db_to_analyze_path(tmpdir):
"all_null": None, "all_null": None,
} }
) )
db["stuff"].insert_all(to_insert) db.table("stuff").insert_all(to_insert)
return path return path
@ -126,7 +126,7 @@ def big_db_to_analyze_path(tmpdir):
) )
def test_analyze_column(db_to_analyze, column, extra_kwargs, expected): def test_analyze_column(db_to_analyze, column, extra_kwargs, expected):
assert ( assert (
db_to_analyze["stuff"].analyze_column( db_to_analyze.table("stuff").analyze_column(
column, common_limit=2, value_truncate=5, **extra_kwargs column, common_limit=2, value_truncate=5, **extra_kwargs
) )
== expected == expected
@ -186,7 +186,7 @@ def test_analyze_table_save(db_to_analyze_path):
cli.cli, ["analyze-tables", db_to_analyze_path, "--save"] cli.cli, ["analyze-tables", db_to_analyze_path, "--save"]
) )
assert result.exit_code == 0 assert result.exit_code == 0
rows = list(Database(db_to_analyze_path)["_analyze_tables_"].rows) rows = list(Database(db_to_analyze_path).table("_analyze_tables_").rows)
assert rows == [ assert rows == [
{ {
"table": "stuff", "table": "stuff",
@ -248,7 +248,7 @@ def test_analyze_table_save_no_most_no_least_options(
args.append("--no-least") args.append("--no-least")
result = CliRunner().invoke(cli.cli, args) result = CliRunner().invoke(cli.cli, args)
assert result.exit_code == 0 assert result.exit_code == 0
rows = list(Database(big_db_to_analyze_path)["_analyze_tables_"].rows) rows = list(Database(big_db_to_analyze_path).table("_analyze_tables_").rows)
expected = { expected = {
"table": "stuff", "table": "stuff",
"column": "category", "column": "category",
@ -297,13 +297,13 @@ def test_analyze_table_column_all_nulls(big_db_to_analyze_path):
def test_analyze_table_validate_columns(tmpdir, args, expected_error): def test_analyze_table_validate_columns(tmpdir, args, expected_error):
path = str(tmpdir / "test_validate_columns.db") path = str(tmpdir / "test_validate_columns.db")
db = Database(path) db = Database(path)
db["one"].insert( db.table("one").insert(
{ {
"id": 1, "id": 1,
"name": "one", "name": "one",
} }
) )
db["two"].insert( db.table("two").insert(
{ {
"id": 1, "id": 1,
"age": 5, "age": 5,

View file

@ -45,30 +45,30 @@ def test_iter_complete_sql_statements(sql, expected):
def test_atomic_commits(fresh_db): def test_atomic_commits(fresh_db):
with fresh_db.atomic(): with fresh_db.atomic():
fresh_db["dogs"].insert({"id": 1, "name": "Cleo"}, pk="id") fresh_db.table("dogs").insert({"id": 1, "name": "Cleo"}, pk="id")
assert list(fresh_db["dogs"].rows) == [{"id": 1, "name": "Cleo"}] assert list(fresh_db.table("dogs").rows) == [{"id": 1, "name": "Cleo"}]
def test_atomic_rolls_back(fresh_db): def test_atomic_rolls_back(fresh_db):
with pytest.raises(RuntimeError), fresh_db.atomic(): with pytest.raises(RuntimeError), fresh_db.atomic():
fresh_db["dogs"].insert({"id": 1, "name": "Cleo"}, pk="id") fresh_db.table("dogs").insert({"id": 1, "name": "Cleo"}, pk="id")
raise RuntimeError("boom") raise RuntimeError("boom")
assert not fresh_db["dogs"].exists() assert not fresh_db.table("dogs").exists()
def test_nested_atomic_rolls_back_to_savepoint(fresh_db): def test_nested_atomic_rolls_back_to_savepoint(fresh_db):
fresh_db["dogs"].create({"id": int, "name": str}, pk="id") fresh_db.table("dogs").create({"id": int, "name": str}, pk="id")
with fresh_db.atomic(): with fresh_db.atomic():
fresh_db["dogs"].insert({"id": 1, "name": "Cleo"}) fresh_db.table("dogs").insert({"id": 1, "name": "Cleo"})
with pytest.raises(RuntimeError), fresh_db.atomic(): with pytest.raises(RuntimeError), fresh_db.atomic():
fresh_db["dogs"].insert({"id": 2, "name": "Pancakes"}) fresh_db.table("dogs").insert({"id": 2, "name": "Pancakes"})
raise RuntimeError("boom") raise RuntimeError("boom")
fresh_db["dogs"].insert({"id": 3, "name": "Marnie"}) fresh_db.table("dogs").insert({"id": 3, "name": "Marnie"})
assert list(fresh_db["dogs"].rows) == [ assert list(fresh_db.table("dogs").rows) == [
{"id": 1, "name": "Cleo"}, {"id": 1, "name": "Cleo"},
{"id": 3, "name": "Marnie"}, {"id": 3, "name": "Marnie"},
] ]
@ -76,12 +76,12 @@ def test_nested_atomic_rolls_back_to_savepoint(fresh_db):
def test_outer_atomic_rolls_back_released_savepoint(fresh_db): def test_outer_atomic_rolls_back_released_savepoint(fresh_db):
with pytest.raises(RuntimeError), fresh_db.atomic(): with pytest.raises(RuntimeError), fresh_db.atomic():
fresh_db["dogs"].insert({"id": 1, "name": "Cleo"}, pk="id") fresh_db.table("dogs").insert({"id": 1, "name": "Cleo"}, pk="id")
with fresh_db.atomic(): with fresh_db.atomic():
fresh_db["dogs"].insert({"id": 2, "name": "Pancakes"}) fresh_db.table("dogs").insert({"id": 2, "name": "Pancakes"})
raise RuntimeError("boom") raise RuntimeError("boom")
assert not fresh_db["dogs"].exists() assert not fresh_db.table("dogs").exists()
def test_executescript_does_not_commit_open_atomic_block(fresh_db): def test_executescript_does_not_commit_open_atomic_block(fresh_db):
@ -97,41 +97,41 @@ def test_executescript_does_not_commit_open_atomic_block(fresh_db):
""") """)
raise RuntimeError("boom") raise RuntimeError("boom")
assert not fresh_db["dogs"].exists() assert not fresh_db.table("dogs").exists()
def test_transform_does_not_commit_open_atomic_block(fresh_db): def test_transform_does_not_commit_open_atomic_block(fresh_db):
fresh_db["dogs"].insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id") fresh_db.table("dogs").insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id")
with pytest.raises(RuntimeError), fresh_db.atomic(): with pytest.raises(RuntimeError), fresh_db.atomic():
fresh_db["dogs"].insert({"id": 2, "name": "Pancakes", "age": "6"}) fresh_db.table("dogs").insert({"id": 2, "name": "Pancakes", "age": "6"})
fresh_db["dogs"].transform(rename={"age": "dog_age"}) fresh_db.table("dogs").transform(rename={"age": "dog_age"})
raise RuntimeError("boom") raise RuntimeError("boom")
assert ( assert (
fresh_db["dogs"].schema fresh_db.table("dogs").schema
== 'CREATE TABLE "dogs" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "age" TEXT\n)' == 'CREATE TABLE "dogs" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT,\n "age" TEXT\n)'
) )
assert list(fresh_db["dogs"].rows) == [ assert list(fresh_db.table("dogs").rows) == [
{"id": 1, "name": "Cleo", "age": "5"}, {"id": 1, "name": "Cleo", "age": "5"},
] ]
def test_transform_parent_table_with_foreign_keys_in_atomic(fresh_db): def test_transform_parent_table_with_foreign_keys_in_atomic(fresh_db):
fresh_db.conn.execute("PRAGMA foreign_keys=ON") fresh_db.conn.execute("PRAGMA foreign_keys=ON")
fresh_db["authors"].insert({"id": 1, "name": "Tina"}, pk="id") fresh_db.table("authors").insert({"id": 1, "name": "Tina"}, pk="id")
fresh_db["books"].insert( fresh_db.table("books").insert(
{"id": 1, "title": "Book", "author_id": 1}, {"id": 1, "title": "Book", "author_id": 1},
pk="id", pk="id",
foreign_keys={"author_id"}, foreign_keys={"author_id"},
) )
with fresh_db.atomic(): with fresh_db.atomic():
fresh_db["authors"].transform(rename={"name": "full_name"}) fresh_db.table("authors").transform(rename={"name": "full_name"})
assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0] assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0]
assert ( assert (
fresh_db["authors"].schema fresh_db.table("authors").schema
== 'CREATE TABLE "authors" (\n "id" INTEGER PRIMARY KEY,\n "full_name" TEXT\n)' == 'CREATE TABLE "authors" (\n "id" INTEGER PRIMARY KEY,\n "full_name" TEXT\n)'
) )
assert fresh_db.execute("PRAGMA foreign_key_check").fetchall() == [] assert fresh_db.execute("PRAGMA foreign_key_check").fetchall() == []
@ -139,19 +139,19 @@ def test_transform_parent_table_with_foreign_keys_in_atomic(fresh_db):
def test_transform_parent_table_with_foreign_keys_rolls_back(fresh_db): def test_transform_parent_table_with_foreign_keys_rolls_back(fresh_db):
fresh_db.conn.execute("PRAGMA foreign_keys=ON") fresh_db.conn.execute("PRAGMA foreign_keys=ON")
fresh_db["authors"].insert({"id": 1, "name": "Tina"}, pk="id") fresh_db.table("authors").insert({"id": 1, "name": "Tina"}, pk="id")
fresh_db["books"].insert( fresh_db.table("books").insert(
{"id": 1, "title": "Book", "author_id": 1}, {"id": 1, "title": "Book", "author_id": 1},
pk="id", pk="id",
foreign_keys={"author_id"}, foreign_keys={"author_id"},
) )
with pytest.raises(RuntimeError), fresh_db.atomic(): with pytest.raises(RuntimeError), fresh_db.atomic():
fresh_db["authors"].transform(rename={"name": "full_name"}) fresh_db.table("authors").transform(rename={"name": "full_name"})
raise RuntimeError("boom") raise RuntimeError("boom")
assert ( assert (
fresh_db["authors"].schema fresh_db.table("authors").schema
== 'CREATE TABLE "authors" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT\n)' == 'CREATE TABLE "authors" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT\n)'
) )
assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0] assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0]
@ -160,49 +160,51 @@ def test_transform_parent_table_with_foreign_keys_rolls_back(fresh_db):
def test_transform_detects_foreign_key_check_violations(fresh_db): def test_transform_detects_foreign_key_check_violations(fresh_db):
fresh_db.conn.execute("PRAGMA foreign_keys=ON") fresh_db.conn.execute("PRAGMA foreign_keys=ON")
fresh_db["authors"].insert({"id": 1, "name": "Tina"}, pk="id") fresh_db.table("authors").insert({"id": 1, "name": "Tina"}, pk="id")
fresh_db["books"].insert({"id": 1, "author_id": 2}, pk="id") fresh_db.table("books").insert({"id": 1, "author_id": 2}, pk="id")
with pytest.raises(sqlite3.IntegrityError): with pytest.raises(sqlite3.IntegrityError):
fresh_db["books"].transform(add_foreign_keys=(("author_id", "authors", "id"),)) fresh_db.table("books").transform(
add_foreign_keys=(("author_id", "authors", "id"),)
)
assert fresh_db["books"].foreign_keys == [] assert fresh_db.table("books").foreign_keys == []
assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0] assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0]
def test_atomic_inside_manual_transaction_uses_savepoint(fresh_db): def test_atomic_inside_manual_transaction_uses_savepoint(fresh_db):
fresh_db["t"].insert({"id": 1}, pk="id") fresh_db.table("t").insert({"id": 1}, pk="id")
fresh_db.execute("begin") fresh_db.execute("begin")
with fresh_db.atomic(): with fresh_db.atomic():
fresh_db["t"].insert({"id": 2}, pk="id") fresh_db.table("t").insert({"id": 2}, pk="id")
# Nothing is committed until the user's own transaction commits # Nothing is committed until the user's own transaction commits
assert fresh_db.conn.in_transaction assert fresh_db.conn.in_transaction
fresh_db.rollback() fresh_db.rollback()
assert [r["id"] for r in fresh_db["t"].rows] == [1] assert [r["id"] for r in fresh_db.table("t").rows] == [1]
# And with a commit instead, the atomic block's writes persist # And with a commit instead, the atomic block's writes persist
fresh_db.execute("begin") fresh_db.execute("begin")
with fresh_db.atomic(): with fresh_db.atomic():
fresh_db["t"].insert({"id": 3}, pk="id") fresh_db.table("t").insert({"id": 3}, pk="id")
fresh_db.commit() fresh_db.commit()
assert [r["id"] for r in fresh_db["t"].rows] == [1, 3] assert [r["id"] for r in fresh_db.table("t").rows] == [1, 3]
def test_begin_commit_rollback(tmpdir): def test_begin_commit_rollback(tmpdir):
path = str(tmpdir / "test.db") path = str(tmpdir / "test.db")
db = Database(path) db = Database(path)
db["t"].insert({"id": 1}, pk="id") db.table("t").insert({"id": 1}, pk="id")
db.begin() db.begin()
db["t"].insert({"id": 2}, pk="id") db.table("t").insert({"id": 2}, pk="id")
assert db.conn.in_transaction assert db.conn.in_transaction
db.rollback() db.rollback()
assert not db.conn.in_transaction assert not db.conn.in_transaction
assert [r["id"] for r in db["t"].rows] == [1] assert [r["id"] for r in db.table("t").rows] == [1]
db.begin() db.begin()
db["t"].insert({"id": 3}, pk="id") db.table("t").insert({"id": 3}, pk="id")
db.commit() db.commit()
db.close() db.close()
db2 = Database(path) db2 = Database(path)
assert [r["id"] for r in db2["t"].rows] == [1, 3] assert [r["id"] for r in db2.table("t").rows] == [1, 3]
db2.close() db2.close()
@ -222,7 +224,7 @@ def test_commit_and_rollback_without_transaction_are_noops(fresh_db):
def test_execute_write_commits_immediately(tmpdir): def test_execute_write_commits_immediately(tmpdir):
path = str(tmpdir / "test.db") path = str(tmpdir / "test.db")
db = Database(path) db = Database(path)
db["t"].insert({"id": 1}, pk="id") db.table("t").insert({"id": 1}, pk="id")
db.execute("insert into t (id) values (2)") db.execute("insert into t (id) values (2)")
# No implicit transaction is left open # No implicit transaction is left open
assert not db.conn.in_transaction assert not db.conn.in_transaction
@ -234,24 +236,24 @@ def test_execute_write_commits_immediately(tmpdir):
def test_execute_write_respects_explicit_transaction(fresh_db): def test_execute_write_respects_explicit_transaction(fresh_db):
fresh_db["t"].insert({"id": 1}, pk="id") fresh_db.table("t").insert({"id": 1}, pk="id")
fresh_db.begin() fresh_db.begin()
fresh_db.execute("insert into t (id) values (2)") fresh_db.execute("insert into t (id) values (2)")
# Still inside the explicit transaction - not committed # Still inside the explicit transaction - not committed
assert fresh_db.conn.in_transaction assert fresh_db.conn.in_transaction
fresh_db.rollback() fresh_db.rollback()
assert [r["id"] for r in fresh_db["t"].rows] == [1] assert [r["id"] for r in fresh_db.table("t").rows] == [1]
def test_execute_comment_prefixed_begin_leaves_transaction_open(fresh_db): def test_execute_comment_prefixed_begin_leaves_transaction_open(fresh_db):
# A BEGIN hidden behind a leading comment must not be auto-committed # A BEGIN hidden behind a leading comment must not be auto-committed
# out from under the caller # out from under the caller
fresh_db["t"].insert({"id": 1}, pk="id") fresh_db.table("t").insert({"id": 1}, pk="id")
fresh_db.execute("-- start a transaction\nbegin") fresh_db.execute("-- start a transaction\nbegin")
assert fresh_db.conn.in_transaction assert fresh_db.conn.in_transaction
fresh_db.execute("insert into t (id) values (2)") fresh_db.execute("insert into t (id) values (2)")
fresh_db.rollback() fresh_db.rollback()
assert [r["id"] for r in fresh_db["t"].rows] == [1] assert [r["id"] for r in fresh_db.table("t").rows] == [1]
def _sqlite_accepts_bom(): def _sqlite_accepts_bom():
@ -269,12 +271,12 @@ def test_execute_prefixed_begin_leaves_transaction_open(fresh_db, begin_sql):
# out from under the caller # out from under the caller
if begin_sql.startswith("\ufeff") and not _sqlite_accepts_bom(): if begin_sql.startswith("\ufeff") and not _sqlite_accepts_bom():
pytest.skip("This SQLite version rejects a leading byte order mark") pytest.skip("This SQLite version rejects a leading byte order mark")
fresh_db["t"].insert({"id": 1}, pk="id") fresh_db.table("t").insert({"id": 1}, pk="id")
fresh_db.execute(begin_sql) fresh_db.execute(begin_sql)
assert fresh_db.conn.in_transaction assert fresh_db.conn.in_transaction
fresh_db.execute("insert into t (id) values (2)") fresh_db.execute("insert into t (id) values (2)")
fresh_db.rollback() fresh_db.rollback()
assert [r["id"] for r in fresh_db["t"].rows] == [1] assert [r["id"] for r in fresh_db.table("t").rows] == [1]
def test_execute_failed_write_rolls_back_implicit_transaction(tmpdir): def test_execute_failed_write_rolls_back_implicit_transaction(tmpdir):
@ -282,40 +284,40 @@ def test_execute_failed_write_rolls_back_implicit_transaction(tmpdir):
# that would silently disable auto-commit for every subsequent write # that would silently disable auto-commit for every subsequent write
path = str(tmpdir / "test.db") path = str(tmpdir / "test.db")
db = Database(path) db = Database(path)
db["t"].insert({"id": 1}, pk="id") db.table("t").insert({"id": 1}, pk="id")
with pytest.raises(sqlite3.IntegrityError): with pytest.raises(sqlite3.IntegrityError):
db.execute("insert into t (id) values (1)") db.execute("insert into t (id) values (1)")
assert not db.conn.in_transaction assert not db.conn.in_transaction
# Subsequent writes commit as normal and survive closing the connection # Subsequent writes commit as normal and survive closing the connection
db["other"].insert({"id": 2}) db.table("other").insert({"id": 2})
db.close() db.close()
db2 = Database(path) db2 = Database(path)
assert db2["other"].exists() assert db2.table("other").exists()
db2.close() db2.close()
def test_execute_failed_write_preserves_explicit_transaction(fresh_db): def test_execute_failed_write_preserves_explicit_transaction(fresh_db):
# A failed write inside an explicit transaction must not roll back # A failed write inside an explicit transaction must not roll back
# the caller's earlier work - only the caller decides that # the caller's earlier work - only the caller decides that
fresh_db["t"].insert({"id": 1}, pk="id") fresh_db.table("t").insert({"id": 1}, pk="id")
fresh_db.begin() fresh_db.begin()
fresh_db.execute("insert into t (id) values (2)") fresh_db.execute("insert into t (id) values (2)")
with pytest.raises(sqlite3.IntegrityError): with pytest.raises(sqlite3.IntegrityError):
fresh_db.execute("insert into t (id) values (1)") fresh_db.execute("insert into t (id) values (1)")
assert fresh_db.conn.in_transaction assert fresh_db.conn.in_transaction
fresh_db.commit() fresh_db.commit()
assert [r["id"] for r in fresh_db["t"].rows] == [1, 2] assert [r["id"] for r in fresh_db.table("t").rows] == [1, 2]
def test_execute_failed_write_inside_atomic_preserves_block(fresh_db): def test_execute_failed_write_inside_atomic_preserves_block(fresh_db):
# A caught failure inside an atomic() block must leave the block's # A caught failure inside an atomic() block must leave the block's
# transaction open so its other work still commits # transaction open so its other work still commits
fresh_db["t"].insert({"id": 1}, pk="id") fresh_db.table("t").insert({"id": 1}, pk="id")
with fresh_db.atomic(): with fresh_db.atomic():
fresh_db.execute("insert into t (id) values (2)") fresh_db.execute("insert into t (id) values (2)")
with pytest.raises(sqlite3.IntegrityError): with pytest.raises(sqlite3.IntegrityError):
fresh_db.execute("insert into t (id) values (1)") fresh_db.execute("insert into t (id) values (1)")
assert [r["id"] for r in fresh_db["t"].rows] == [1, 2] assert [r["id"] for r in fresh_db.table("t").rows] == [1, 2]
def test_query_returning_commits_after_iteration(tmpdir): def test_query_returning_commits_after_iteration(tmpdir):
@ -325,7 +327,7 @@ def test_query_returning_commits_after_iteration(tmpdir):
_pytest.skip("RETURNING requires SQLite 3.35.0 or higher") _pytest.skip("RETURNING requires SQLite 3.35.0 or higher")
path = str(tmpdir / "test.db") path = str(tmpdir / "test.db")
db = Database(path) db = Database(path)
db["t"].insert({"id": 1}, pk="id") db.table("t").insert({"id": 1}, pk="id")
rows = list(db.query("insert into t (id) values (2) returning id")) rows = list(db.query("insert into t (id) values (2) returning id"))
assert rows == [{"id": 2}] assert rows == [{"id": 2}]
assert not db.conn.in_transaction assert not db.conn.in_transaction
@ -375,7 +377,7 @@ def test_nested_atomic_preserves_error_from_transaction_destroying_trigger(
def test_atomic_preserves_error_from_insert_or_rollback(fresh_db): def test_atomic_preserves_error_from_insert_or_rollback(fresh_db):
fresh_db["t"].insert({"id": 1}, pk="id") fresh_db.table("t").insert({"id": 1}, pk="id")
with pytest.raises(sqlite3.IntegrityError), fresh_db.atomic(): with pytest.raises(sqlite3.IntegrityError), fresh_db.atomic():
fresh_db.execute("insert or rollback into t (id) values (1)") fresh_db.execute("insert or rollback into t (id) values (1)")
assert not fresh_db.conn.in_transaction assert not fresh_db.conn.in_transaction

View file

@ -6,10 +6,10 @@ def test_attach(tmpdir):
bar_path = str(tmpdir / "bar.db") bar_path = str(tmpdir / "bar.db")
db = Database(foo_path) db = Database(foo_path)
with db.conn: with db.conn:
db["foo"].insert({"id": 1, "text": "foo"}) db.table("foo").insert({"id": 1, "text": "foo"})
db2 = Database(bar_path) db2 = Database(bar_path)
with db2.conn: with db2.conn:
db2["bar"].insert({"id": 1, "text": "bar"}) db2.table("bar").insert({"id": 1, "text": "bar"})
db.attach("bar", bar_path) db.attach("bar", bar_path)
assert db.execute( assert db.execute(
"select * from foo union all select * from bar.bar" "select * from foo union all select * from bar.bar"

View file

@ -72,13 +72,13 @@ def test_views(db_path):
def test_tables_fts4(db_path): def test_tables_fts4(db_path):
Database(db_path)["Gosh"].enable_fts(["c2"], fts_version="FTS4") Database(db_path).table("Gosh").enable_fts(["c2"], fts_version="FTS4")
result = CliRunner().invoke(cli.cli, ["tables", "--fts4", db_path]) result = CliRunner().invoke(cli.cli, ["tables", "--fts4", db_path])
assert '[{"table": "Gosh_fts"}]' == result.output.strip() assert '[{"table": "Gosh_fts"}]' == result.output.strip()
def test_tables_fts5(db_path): def test_tables_fts5(db_path):
Database(db_path)["Gosh"].enable_fts(["c2"], fts_version="FTS5") Database(db_path).table("Gosh").enable_fts(["c2"], fts_version="FTS5")
result = CliRunner().invoke(cli.cli, ["tables", "--fts5", db_path]) result = CliRunner().invoke(cli.cli, ["tables", "--fts5", db_path])
assert '[{"table": "Gosh_fts"}]' == result.output.strip() assert '[{"table": "Gosh_fts"}]' == result.output.strip()
@ -86,7 +86,7 @@ def test_tables_fts5(db_path):
def test_tables_counts_and_columns(db_path): def test_tables_counts_and_columns(db_path):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["lots"].insert_all([{"id": i, "age": i + 1} for i in range(30)]) db.table("lots").insert_all([{"id": i, "age": i + 1} for i in range(30)])
result = CliRunner().invoke(cli.cli, ["tables", "--counts", "--columns", db_path]) result = CliRunner().invoke(cli.cli, ["tables", "--counts", "--columns", db_path])
assert ( assert (
'[{"table": "Gosh", "count": 0, "columns": ["c1", "c2", "c3"]},\n' '[{"table": "Gosh", "count": 0, "columns": ["c1", "c2", "c3"]},\n'
@ -121,7 +121,7 @@ def test_tables_counts_and_columns(db_path):
def test_tables_counts_and_columns_csv(db_path, format, expected): def test_tables_counts_and_columns_csv(db_path, format, expected):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["lots"].insert_all([{"id": i, "age": i + 1} for i in range(30)]) db.table("lots").insert_all([{"id": i, "age": i + 1} for i in range(30)])
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, ["tables", "--counts", "--columns", format, db_path] cli.cli, ["tables", "--counts", "--columns", format, db_path]
) )
@ -131,7 +131,7 @@ def test_tables_counts_and_columns_csv(db_path, format, expected):
def test_tables_schema(db_path): def test_tables_schema(db_path):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["lots"].insert_all([{"id": i, "age": i + 1} for i in range(30)]) db.table("lots").insert_all([{"id": i, "age": i + 1} for i in range(30)])
result = CliRunner().invoke(cli.cli, ["tables", "--schema", db_path]) result = CliRunner().invoke(cli.cli, ["tables", "--schema", db_path])
assert ( assert (
'[{"table": "Gosh", "schema": "CREATE TABLE Gosh (c1 text, c2 text, c3 text)"},\n' '[{"table": "Gosh", "schema": "CREATE TABLE Gosh (c1 text, c2 text, c3 text)"},\n'
@ -183,7 +183,7 @@ def test_tables_schema(db_path):
def test_output_table(db_path, options, expected): def test_output_table(db_path, options, expected):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["rows"].insert_all( db.table("rows").insert_all(
[ [
{ {
"c1": f"verb{i}", "c1": f"verb{i}",
@ -207,7 +207,7 @@ def test_output_table_no_headers(db_path, fmt_option):
# tabulate formats and the column names were always printed. # tabulate formats and the column names were always printed.
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["dogs"].insert_all( db.table("dogs").insert_all(
[ [
{"id": 1, "name": "Cleo", "age": 4}, {"id": 1, "name": "Cleo", "age": 4},
{"id": 2, "name": "Pancakes", "age": 2}, {"id": 2, "name": "Pancakes", "age": 2},
@ -244,14 +244,14 @@ def test_output_table_no_headers(db_path, fmt_option):
def test_create_index(db_path): def test_create_index(db_path):
db = Database(db_path) db = Database(db_path)
assert [] == db["Gosh"].indexes assert [] == db.table("Gosh").indexes
result = CliRunner().invoke(cli.cli, ["create-index", db_path, "Gosh", "c1"]) result = CliRunner().invoke(cli.cli, ["create-index", db_path, "Gosh", "c1"])
assert result.exit_code == 0 assert result.exit_code == 0
assert [ assert [
Index( Index(
seq=0, name="idx_Gosh_c1", unique=0, origin="c", partial=0, columns=["c1"] seq=0, name="idx_Gosh_c1", unique=0, origin="c", partial=0, columns=["c1"]
) )
] == db["Gosh"].indexes ] == db.table("Gosh").indexes
# Try with a custom name # Try with a custom name
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, ["create-index", db_path, "Gosh", "c2", "--name", "blah"] cli.cli, ["create-index", db_path, "Gosh", "c2", "--name", "blah"]
@ -262,7 +262,7 @@ def test_create_index(db_path):
Index( Index(
seq=1, name="idx_Gosh_c1", unique=0, origin="c", partial=0, columns=["c1"] seq=1, name="idx_Gosh_c1", unique=0, origin="c", partial=0, columns=["c1"]
), ),
] == db["Gosh"].indexes ] == db.table("Gosh").indexes
# Try a two-column unique index # Try a two-column unique index
create_index_unique_args = [ create_index_unique_args = [
"create-index", "create-index",
@ -283,7 +283,7 @@ def test_create_index(db_path):
partial=0, partial=0,
columns=["c1", "c2"], columns=["c1", "c2"],
) )
] == db["Gosh2"].indexes ] == db.table("Gosh2").indexes
# Trying to create the same index should fail # Trying to create the same index should fail
assert CliRunner().invoke(cli.cli, create_index_unique_args).exit_code != 0 assert CliRunner().invoke(cli.cli, create_index_unique_args).exit_code != 0
# ... unless we use --if-not-exists or --ignore # ... unless we use --if-not-exists or --ignore
@ -296,11 +296,11 @@ def test_create_index(db_path):
def test_drop_index(db_path): def test_drop_index(db_path):
db = Database(db_path) db = Database(db_path)
db["Gosh"].create_index(["c1"]) db.table("Gosh").create_index(["c1"])
assert [index.name for index in db["Gosh"].indexes] == ["idx_Gosh_c1"] assert [index.name for index in db.table("Gosh").indexes] == ["idx_Gosh_c1"]
result = CliRunner().invoke(cli.cli, ["drop-index", db_path, "Gosh", "idx_Gosh_c1"]) result = CliRunner().invoke(cli.cli, ["drop-index", db_path, "Gosh", "idx_Gosh_c1"])
assert result.exit_code == 0 assert result.exit_code == 0
assert db["Gosh"].indexes == [] assert db.table("Gosh").indexes == []
result = CliRunner().invoke(cli.cli, ["drop-index", db_path, "Gosh", "idx_Gosh_c1"]) result = CliRunner().invoke(cli.cli, ["drop-index", db_path, "Gosh", "idx_Gosh_c1"])
assert result.exit_code == 1 assert result.exit_code == 1
@ -315,7 +315,7 @@ def test_drop_index(db_path):
def test_create_index_analyze(db_path): def test_create_index_analyze(db_path):
db = Database(db_path) db = Database(db_path)
assert "sqlite_stat1" not in db.table_names() assert "sqlite_stat1" not in db.table_names()
assert [] == db["Gosh"].indexes assert [] == db.table("Gosh").indexes
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, ["create-index", db_path, "Gosh", "c1", "--analyze"] cli.cli, ["create-index", db_path, "Gosh", "c1", "--analyze"]
) )
@ -325,7 +325,7 @@ def test_create_index_analyze(db_path):
def test_create_index_desc(db_path): def test_create_index_desc(db_path):
db = Database(db_path) db = Database(db_path)
assert [] == db["Gosh"].indexes assert [] == db.table("Gosh").indexes
result = CliRunner().invoke(cli.cli, ["create-index", db_path, "Gosh", "--", "-c1"]) result = CliRunner().invoke(cli.cli, ["create-index", db_path, "Gosh", "--", "-c1"])
assert result.exit_code == 0 assert result.exit_code == 0
assert ( assert (
@ -361,12 +361,12 @@ def test_create_index_desc(db_path):
def test_add_column(db_path, col_name, col_type, expected_schema): def test_add_column(db_path, col_name, col_type, expected_schema):
db = Database(db_path) db = Database(db_path)
db.create_table("dogs", {"name": str}) db.create_table("dogs", {"name": str})
assert db["dogs"].schema == 'CREATE TABLE "dogs" (\n "name" TEXT\n)' assert db.table("dogs").schema == 'CREATE TABLE "dogs" (\n "name" TEXT\n)'
args = ["add-column", db_path, "dogs", col_name] args = ["add-column", db_path, "dogs", col_name]
if col_type is not None: if col_type is not None:
args.append(col_type) args.append(col_type)
assert CliRunner().invoke(cli.cli, args).exit_code == 0 assert CliRunner().invoke(cli.cli, args).exit_code == 0
assert db["dogs"].schema == expected_schema assert db.table("dogs").schema == expected_schema
@pytest.mark.parametrize("ignore", (True, False)) @pytest.mark.parametrize("ignore", (True, False))
@ -385,7 +385,7 @@ def test_add_column_ignore(db_path, ignore):
def test_add_column_not_null_default(db_path): def test_add_column_not_null_default(db_path):
db = Database(db_path) db = Database(db_path)
db.create_table("dogs", {"name": str}) db.create_table("dogs", {"name": str})
assert db["dogs"].schema == 'CREATE TABLE "dogs" (\n "name" TEXT\n)' assert db.table("dogs").schema == 'CREATE TABLE "dogs" (\n "name" TEXT\n)'
args = [ args = [
"add-column", "add-column",
db_path, db_path,
@ -395,7 +395,7 @@ def test_add_column_not_null_default(db_path):
"dogs'dawg", "dogs'dawg",
] ]
assert CliRunner().invoke(cli.cli, args).exit_code == 0 assert CliRunner().invoke(cli.cli, args).exit_code == 0
assert db["dogs"].schema == ( assert db.table("dogs").schema == (
'CREATE TABLE "dogs" (\n' 'CREATE TABLE "dogs" (\n'
' "name" TEXT\n' ' "name" TEXT\n'
", \"nickname\" TEXT NOT NULL DEFAULT 'dogs''dawg')" ", \"nickname\" TEXT NOT NULL DEFAULT 'dogs''dawg')"
@ -415,10 +415,10 @@ def test_add_column_not_null_default(db_path):
) )
def test_add_foreign_key(db_path, args, assert_message): def test_add_foreign_key(db_path, args, assert_message):
db = Database(db_path) db = Database(db_path)
db["authors"].insert_all( db.table("authors").insert_all(
[{"id": 1, "name": "Sally"}, {"id": 2, "name": "Asheesh"}], pk="id" [{"id": 1, "name": "Sally"}, {"id": 2, "name": "Asheesh"}], pk="id"
) )
db["books"].insert_all( db.table("books").insert_all(
[ [
{"title": "Hedgehogs of the world", "author_id": 1}, {"title": "Hedgehogs of the world", "author_id": 1},
{"title": "How to train your wolf", "author_id": 2}, {"title": "How to train your wolf", "author_id": 2},
@ -431,7 +431,7 @@ def test_add_foreign_key(db_path, args, assert_message):
ForeignKey( ForeignKey(
table="books", column="author_id", other_table="authors", other_column="id" table="books", column="author_id", other_table="authors", other_column="id"
) )
] == db["books"].foreign_keys ] == db.table("books").foreign_keys
# Error if we try to add it twice: # Error if we try to add it twice:
result = CliRunner().invoke( result = CliRunner().invoke(
@ -460,14 +460,14 @@ def test_add_foreign_key(db_path, args, assert_message):
def test_add_column_foreign_key(db_path): def test_add_column_foreign_key(db_path):
db = Database(db_path) db = Database(db_path)
db["authors"].insert({"id": 1, "name": "Sally"}, pk="id") db.table("authors").insert({"id": 1, "name": "Sally"}, pk="id")
db["books"].insert({"title": "Hedgehogs of the world"}) db.table("books").insert({"title": "Hedgehogs of the world"})
# Add an author_id foreign key column to the books table # Add an author_id foreign key column to the books table
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, ["add-column", db_path, "books", "author_id", "--fk", "authors"] cli.cli, ["add-column", db_path, "books", "author_id", "--fk", "authors"]
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert db["books"].schema == ( assert db.table("books").schema == (
'CREATE TABLE "books" (\n' 'CREATE TABLE "books" (\n'
' "title" TEXT,\n' ' "title" TEXT,\n'
' "author_id" INTEGER REFERENCES "authors"("id")\n' ' "author_id" INTEGER REFERENCES "authors"("id")\n'
@ -488,7 +488,7 @@ def test_add_column_foreign_key(db_path):
], ],
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert db["books"].schema == ( assert db.table("books").schema == (
'CREATE TABLE "books" (\n' 'CREATE TABLE "books" (\n'
' "title" TEXT,\n' ' "title" TEXT,\n'
' "author_id" INTEGER REFERENCES "authors"("id"),\n' ' "author_id" INTEGER REFERENCES "authors"("id"),\n'
@ -505,7 +505,7 @@ def test_add_column_foreign_key(db_path):
def test_suggest_alter_if_column_missing(db_path): def test_suggest_alter_if_column_missing(db_path):
db = Database(db_path) db = Database(db_path)
db["authors"].insert({"id": 1, "name": "Sally"}, pk="id") db.table("authors").insert({"id": 1, "name": "Sally"}, pk="id")
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
["insert", db_path, "authors", "-"], ["insert", db_path, "authors", "-"],
@ -521,27 +521,27 @@ def test_suggest_alter_if_column_missing(db_path):
def test_index_foreign_keys(db_path): def test_index_foreign_keys(db_path):
test_add_column_foreign_key(db_path) test_add_column_foreign_key(db_path)
db = Database(db_path) db = Database(db_path)
assert [] == db["books"].indexes assert [] == db.table("books").indexes
result = CliRunner().invoke(cli.cli, ["index-foreign-keys", db_path]) result = CliRunner().invoke(cli.cli, ["index-foreign-keys", db_path])
assert result.exit_code == 0 assert result.exit_code == 0
assert [["author_id"], ["author_name_ref"]] == [ assert [["author_id"], ["author_name_ref"]] == [
i.columns for i in db["books"].indexes i.columns for i in db.table("books").indexes
] ]
def test_enable_fts(db_path): def test_enable_fts(db_path):
db = Database(db_path) db = Database(db_path)
assert db["Gosh"].detect_fts() is None assert db.table("Gosh").detect_fts() is None
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, ["enable-fts", db_path, "Gosh", "c1", "--fts4"] cli.cli, ["enable-fts", db_path, "Gosh", "c1", "--fts4"]
) )
assert result.exit_code == 0 assert result.exit_code == 0
assert "Gosh_fts" == db["Gosh"].detect_fts() assert "Gosh_fts" == db.table("Gosh").detect_fts()
# Table names with restricted chars are handled correctly. # Table names with restricted chars are handled correctly.
# colons and dots are restricted characters for table names. # colons and dots are restricted characters for table names.
db["http://example.com"].create({"c1": str, "c2": str, "c3": str}) db.table("http://example.com").create({"c1": str, "c2": str, "c3": str})
assert db["http://example.com"].detect_fts() is None assert db.table("http://example.com").detect_fts() is None
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
[ [
@ -555,7 +555,7 @@ def test_enable_fts(db_path):
], ],
) )
assert result.exit_code == 0 assert result.exit_code == 0
assert "http://example.com_fts" == db["http://example.com"].detect_fts() assert "http://example.com_fts" == db.table("http://example.com").detect_fts()
# Check tokenize was set to porter # Check tokenize was set to porter
assert ( assert (
'CREATE VIRTUAL TABLE "http://example.com_fts" USING FTS4 (\n' 'CREATE VIRTUAL TABLE "http://example.com_fts" USING FTS4 (\n'
@ -563,19 +563,19 @@ def test_enable_fts(db_path):
" tokenize='porter',\n" " tokenize='porter',\n"
' content="http://example.com"' ' content="http://example.com"'
"\n)" "\n)"
) == db["http://example.com_fts"].schema ) == db.table("http://example.com_fts").schema
db["http://example.com"].drop() db.table("http://example.com").drop()
def test_enable_fts_replace(db_path): def test_enable_fts_replace(db_path):
db = Database(db_path) db = Database(db_path)
assert db["Gosh"].detect_fts() is None assert db.table("Gosh").detect_fts() is None
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, ["enable-fts", db_path, "Gosh", "c1", "--fts4"] cli.cli, ["enable-fts", db_path, "Gosh", "c1", "--fts4"]
) )
assert result.exit_code == 0 assert result.exit_code == 0
assert "Gosh_fts" == db["Gosh"].detect_fts() assert "Gosh_fts" == db.table("Gosh").detect_fts()
assert db["Gosh_fts"].columns_dict == {"c1": str} assert db.table("Gosh_fts").columns_dict == {"c1": str}
# This should throw an error # This should throw an error
result2 = CliRunner().invoke( result2 = CliRunner().invoke(
@ -589,11 +589,11 @@ def test_enable_fts_replace(db_path):
cli.cli, ["enable-fts", db_path, "Gosh", "c2", "--fts4", "--replace"] cli.cli, ["enable-fts", db_path, "Gosh", "c2", "--fts4", "--replace"]
) )
assert result3.exit_code == 0 assert result3.exit_code == 0
assert db["Gosh_fts"].columns_dict == {"c2": str} assert db.table("Gosh_fts").columns_dict == {"c2": str}
def test_enable_fts_with_triggers(db_path): def test_enable_fts_with_triggers(db_path):
Database(db_path)["Gosh"].insert_all([{"c1": "baz"}]) Database(db_path).table("Gosh").insert_all([{"c1": "baz"}])
exit_code = ( exit_code = (
CliRunner() CliRunner()
.invoke( .invoke(
@ -612,12 +612,12 @@ def test_enable_fts_with_triggers(db_path):
) )
assert [("baz",)] == search("baz") assert [("baz",)] == search("baz")
Database(db_path)["Gosh"].insert_all([{"c1": "martha"}]) Database(db_path).table("Gosh").insert_all([{"c1": "martha"}])
assert [("martha",)] == search("martha") assert [("martha",)] == search("martha")
def test_populate_fts(db_path): def test_populate_fts(db_path):
Database(db_path)["Gosh"].insert_all([{"c1": "baz"}]) Database(db_path).table("Gosh").insert_all([{"c1": "baz"}])
exit_code = ( exit_code = (
CliRunner() CliRunner()
.invoke(cli.cli, ["enable-fts", db_path, "Gosh", "c1", "--fts4"]) .invoke(cli.cli, ["enable-fts", db_path, "Gosh", "c1", "--fts4"])
@ -633,7 +633,7 @@ def test_populate_fts(db_path):
) )
assert [("baz",)] == search("baz") assert [("baz",)] == search("baz")
Database(db_path)["Gosh"].insert_all([{"c1": "martha"}]) Database(db_path).table("Gosh").insert_all([{"c1": "martha"}])
assert [] == search("martha") assert [] == search("martha")
exit_code = ( exit_code = (
CliRunner().invoke(cli.cli, ["populate-fts", db_path, "Gosh", "c1"]).exit_code CliRunner().invoke(cli.cli, ["populate-fts", db_path, "Gosh", "c1"]).exit_code
@ -645,7 +645,7 @@ def test_populate_fts(db_path):
def test_disable_fts(db_path): def test_disable_fts(db_path):
db = Database(db_path) db = Database(db_path)
assert {"Gosh", "Gosh2"} == set(db.table_names()) assert {"Gosh", "Gosh2"} == set(db.table_names())
db["Gosh"].enable_fts(["c1"], create_triggers=True) db.table("Gosh").enable_fts(["c1"], create_triggers=True)
assert { assert {
"Gosh_fts", "Gosh_fts",
"Gosh_fts_idx", "Gosh_fts_idx",
@ -677,7 +677,7 @@ def test_optimize(db_path, tables):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
for table in ("Gosh", "Gosh2"): for table in ("Gosh", "Gosh2"):
db[table].insert_all( db.table(table).insert_all(
[ [
{ {
"c1": f"verb{i}", "c1": f"verb{i}",
@ -687,8 +687,8 @@ def test_optimize(db_path, tables):
for i in range(10000) for i in range(10000)
] ]
) )
db["Gosh"].enable_fts(["c1", "c2", "c3"], fts_version="FTS4") db.table("Gosh").enable_fts(["c1", "c2", "c3"], fts_version="FTS4")
db["Gosh2"].enable_fts(["c1", "c2", "c3"], fts_version="FTS5") db.table("Gosh2").enable_fts(["c1", "c2", "c3"], fts_version="FTS5")
size_before_optimize = os.stat(db_path).st_size size_before_optimize = os.stat(db_path).st_size
result = CliRunner().invoke(cli.cli, ["optimize", db_path] + tables) result = CliRunner().invoke(cli.cli, ["optimize", db_path] + tables)
assert result.exit_code == 0 assert result.exit_code == 0
@ -713,22 +713,22 @@ def test_rebuild_fts_fixes_docsize_error(db_path):
for i in range(10000) for i in range(10000)
] ]
with db.conn: with db.conn:
db["fts5_table"].insert_all(records, pk="c1") db.table("fts5_table").insert_all(records, pk="c1")
db["fts5_table"].enable_fts( db.table("fts5_table").enable_fts(
["c1", "c2", "c3"], fts_version="FTS5", create_triggers=True ["c1", "c2", "c3"], fts_version="FTS5", create_triggers=True
) )
# Search should work # Search should work
assert list(db["fts5_table"].search("verb1")) assert list(db.table("fts5_table").search("verb1"))
# Replicate docsize error from this issue for FTS5 # Replicate docsize error from this issue for FTS5
# https://github.com/simonw/sqlite-utils/issues/149 # https://github.com/simonw/sqlite-utils/issues/149
assert db["fts5_table_fts_docsize"].count == 10000 assert db.table("fts5_table_fts_docsize").count == 10000
db["fts5_table"].insert_all(records, replace=True) db.table("fts5_table").insert_all(records, replace=True)
assert db["fts5_table"].count == 10000 assert db.table("fts5_table").count == 10000
assert db["fts5_table_fts_docsize"].count == 20000 assert db.table("fts5_table_fts_docsize").count == 20000
# Running rebuild-fts should fix this # Running rebuild-fts should fix this
result = CliRunner().invoke(cli.cli, ["rebuild-fts", db_path, "fts5_table"]) result = CliRunner().invoke(cli.cli, ["rebuild-fts", db_path, "fts5_table"])
assert result.exit_code == 0 assert result.exit_code == 0
assert db["fts5_table_fts_docsize"].count == 10000 assert db.table("fts5_table_fts_docsize").count == 10000
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -741,7 +741,7 @@ def test_rebuild_fts_fixes_docsize_error(db_path):
def test_query_csv(db_path, format, expected): def test_query_csv(db_path, format, expected):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["dogs"].insert_all( db.table("dogs").insert_all(
[ [
{"id": 1, "age": 4, "name": "Cleo"}, {"id": 1, "age": 4, "name": "Cleo"},
{"id": 2, "age": 2, "name": "Pancakes"}, {"id": 2, "age": 2, "name": "Pancakes"},
@ -793,7 +793,7 @@ _one_query = "select id, name, age from dogs where id = 1"
def test_query_json(db_path, sql, args, expected): def test_query_json(db_path, sql, args, expected):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["dogs"].insert_all( db.table("dogs").insert_all(
[ [
{"id": 1, "age": 4, "name": "Cleo"}, {"id": 1, "age": 4, "name": "Cleo"},
{"id": 2, "age": 2, "name": "Pancakes"}, {"id": 2, "age": 2, "name": "Pancakes"},
@ -807,7 +807,7 @@ def test_query_sql_from_stdin(db_path):
# https://github.com/simonw/sqlite-utils/issues/765 # https://github.com/simonw/sqlite-utils/issues/765
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["dogs"].insert_all( db.table("dogs").insert_all(
[ [
{"id": 1, "age": 4, "name": "Cleo"}, {"id": 1, "age": 4, "name": "Cleo"},
{"id": 2, "age": 2, "name": "Pancakes"}, {"id": 2, "age": 2, "name": "Pancakes"},
@ -1004,7 +1004,7 @@ LOREM_IPSUM_COMPRESSED = (
def test_query_json_binary(db_path): def test_query_json_binary(db_path):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["files"].insert( db.table("files").insert(
{ {
"name": "lorem.txt", "name": "lorem.txt",
"sz": 16984, "sz": 16984,
@ -1059,7 +1059,7 @@ def test_query_params(db_path, sql, params, expected):
def test_query_json_with_json_cols(db_path): def test_query_json_with_json_cols(db_path):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["dogs"].insert( db.table("dogs").insert(
{ {
"id": 1, "id": 1,
"name": "Cleo", "name": "Cleo",
@ -1088,7 +1088,7 @@ def test_query_json_with_json_cols(db_path):
def test_query_json_unicode_not_escaped_by_default(db_path): def test_query_json_unicode_not_escaped_by_default(db_path):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["text"].insert({"id": 1, "text": "Japanese 日本語"}, pk="id") db.table("text").insert({"id": 1, "text": "Japanese 日本語"}, pk="id")
result = CliRunner().invoke(cli.cli, [db_path, "select id, text from text"]) result = CliRunner().invoke(cli.cli, [db_path, "select id, text from text"])
assert result.exit_code == 0 assert result.exit_code == 0
assert result.output.strip() == '[{"id": 1, "text": "Japanese 日本語"}]' assert result.output.strip() == '[{"id": 1, "text": "Japanese 日本語"}]'
@ -1102,7 +1102,7 @@ def test_query_json_unicode_not_escaped_by_default(db_path):
def test_query_json_ascii_option(db_path, command): def test_query_json_ascii_option(db_path, command):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["text"].insert({"id": 1, "text": "Japanese 日本語"}, pk="id") db.table("text").insert({"id": 1, "text": "Japanese 日本語"}, pk="id")
if command == "query": if command == "query":
args = [db_path, "select id, text from text", "--ascii"] args = [db_path, "select id, text from text", "--ascii"]
else: else:
@ -1118,7 +1118,7 @@ def test_query_json_ascii_option(db_path, command):
[(b"\x00\x0fbinary", True), ("this is text", False), (1, False), (1.5, False)], [(b"\x00\x0fbinary", True), ("this is text", False), (1, False), (1.5, False)],
) )
def test_query_raw(db_path, content, is_binary): def test_query_raw(db_path, content, is_binary):
Database(db_path)["files"].insert({"content": content}) Database(db_path).table("files").insert({"content": content})
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, [db_path, "select content from files", "--raw"] cli.cli, [db_path, "select content from files", "--raw"]
) )
@ -1133,7 +1133,7 @@ def test_query_raw(db_path, content, is_binary):
[(b"\x00\x0fbinary", True), ("this is text", False), (1, False), (1.5, False)], [(b"\x00\x0fbinary", True), ("this is text", False), (1, False), (1.5, False)],
) )
def test_query_raw_lines(db_path, content, is_binary): def test_query_raw_lines(db_path, content, is_binary):
Database(db_path)["files"].insert_all({"content": content} for _ in range(3)) Database(db_path).table("files").insert_all({"content": content} for _ in range(3))
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, [db_path, "select content from files", "--raw-lines"] cli.cli, [db_path, "select content from files", "--raw-lines"]
) )
@ -1215,7 +1215,7 @@ def test_query_memory_does_not_create_file(tmpdir):
def test_rows(db_path, args, expected): def test_rows(db_path, args, expected):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["dogs"].insert_all( db.table("dogs").insert_all(
[ [
{"id": 1, "age": 4, "name": "Cleo"}, {"id": 1, "age": 4, "name": "Cleo"},
{"id": 2, "age": 2, "name": "Pancakes"}, {"id": 2, "age": 2, "name": "Pancakes"},
@ -1240,7 +1240,7 @@ def test_upsert(db_path, tmpdir):
catch_exceptions=False, catch_exceptions=False,
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert 2 == db["dogs"].count assert 2 == db.table("dogs").count
# Now run the upsert to update just their ages # Now run the upsert to update just their ages
upsert_dogs = [ upsert_dogs = [
{"id": 1, "age": 5}, {"id": 1, "age": 5},
@ -1295,8 +1295,8 @@ def test_upsert_pk_inferred_from_existing_table(db_path, tmpdir):
def test_upsert_analyze(db_path, tmpdir): def test_upsert_analyze(db_path, tmpdir):
db = Database(db_path) db = Database(db_path)
db["rows"].insert({"id": 1, "foo": "x", "n": 3}, pk="id") db.table("rows").insert({"id": 1, "foo": "x", "n": 3}, pk="id")
db["rows"].create_index(["n"]) db.table("rows").create_index(["n"])
assert "sqlite_stat1" not in db.table_names() assert "sqlite_stat1" not in db.table_names()
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
@ -1310,7 +1310,7 @@ def test_upsert_analyze(db_path, tmpdir):
def test_upsert_flatten(tmpdir): def test_upsert_flatten(tmpdir):
db_path = str(tmpdir / "flat.db") db_path = str(tmpdir / "flat.db")
db = Database(db_path) db = Database(db_path)
db["upsert_me"].insert({"id": 1, "name": "Example"}, pk="id") db.table("upsert_me").insert({"id": 1, "name": "Example"}, pk="id")
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
["upsert", db_path, "upsert_me", "-", "--flatten", "--pk", "id", "--alter"], ["upsert", db_path, "upsert_me", "-", "--flatten", "--pk", "id", "--alter"],
@ -1424,7 +1424,7 @@ def test_create_table(args, schema):
) )
assert result.exit_code == 0 assert result.exit_code == 0
db = Database("test.db") db = Database("test.db")
assert schema == db["t"].schema assert schema == db.table("t").schema
def test_create_table_foreign_key(): def test_create_table_foreign_key():
@ -1459,21 +1459,21 @@ def test_create_table_foreign_key():
' "id" INTEGER PRIMARY KEY,\n' ' "id" INTEGER PRIMARY KEY,\n'
' "name" TEXT\n' ' "name" TEXT\n'
")" ")"
) == db["authors"].schema ) == db.table("authors").schema
assert ( assert (
'CREATE TABLE "books" (\n' 'CREATE TABLE "books" (\n'
' "id" INTEGER PRIMARY KEY,\n' ' "id" INTEGER PRIMARY KEY,\n'
' "title" TEXT,\n' ' "title" TEXT,\n'
' "author_id" INTEGER REFERENCES "authors"("id")\n' ' "author_id" INTEGER REFERENCES "authors"("id")\n'
")" ")"
) == db["books"].schema ) == db.table("books").schema
def test_create_table_error_if_table_exists(): def test_create_table_error_if_table_exists():
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
db = Database("test.db") db = Database("test.db")
db["dogs"].insert({"name": "Cleo"}) db.table("dogs").insert({"name": "Cleo"})
result = runner.invoke( result = runner.invoke(
cli.cli, ["create-table", "test.db", "dogs", "id", "integer"] cli.cli, ["create-table", "test.db", "dogs", "id", "integer"]
) )
@ -1488,24 +1488,24 @@ def test_create_table_ignore():
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
db = Database("test.db") db = Database("test.db")
db["dogs"].insert({"name": "Cleo"}) db.table("dogs").insert({"name": "Cleo"})
result = runner.invoke( result = runner.invoke(
cli.cli, ["create-table", "test.db", "dogs", "id", "integer", "--ignore"] cli.cli, ["create-table", "test.db", "dogs", "id", "integer", "--ignore"]
) )
assert result.exit_code == 0 assert result.exit_code == 0
assert 'CREATE TABLE "dogs" (\n "name" TEXT\n)' == db["dogs"].schema assert 'CREATE TABLE "dogs" (\n "name" TEXT\n)' == db.table("dogs").schema
def test_create_table_replace(): def test_create_table_replace():
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
db = Database("test.db") db = Database("test.db")
db["dogs"].insert({"name": "Cleo"}) db.table("dogs").insert({"name": "Cleo"})
result = runner.invoke( result = runner.invoke(
cli.cli, ["create-table", "test.db", "dogs", "id", "integer", "--replace"] cli.cli, ["create-table", "test.db", "dogs", "id", "integer", "--replace"]
) )
assert result.exit_code == 0 assert result.exit_code == 0
assert 'CREATE TABLE "dogs" (\n "id" INTEGER\n)' == db["dogs"].schema assert 'CREATE TABLE "dogs" (\n "id" INTEGER\n)' == db.table("dogs").schema
def test_create_view(): def test_create_view():
@ -1517,7 +1517,8 @@ def test_create_view():
) )
assert result.exit_code == 0 assert result.exit_code == 0
assert ( assert (
'CREATE VIEW "version" AS select sqlite_version()' == db["version"].schema 'CREATE VIEW "version" AS select sqlite_version()'
== db.view("version").schema
) )
@ -1554,7 +1555,7 @@ def test_create_view_ignore():
assert result.exit_code == 0 assert result.exit_code == 0
assert ( assert (
'CREATE VIEW "version" AS select sqlite_version() + 1' 'CREATE VIEW "version" AS select sqlite_version() + 1'
== db["version"].schema == db.view("version").schema
) )
@ -1575,7 +1576,8 @@ def test_create_view_replace():
) )
assert result.exit_code == 0 assert result.exit_code == 0
assert ( assert (
'CREATE VIEW "version" AS select sqlite_version()' == db["version"].schema 'CREATE VIEW "version" AS select sqlite_version()'
== db.view("version").schema
) )
@ -1583,7 +1585,7 @@ def test_drop_table():
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
db = Database("test.db") db = Database("test.db")
db["t"].create({"pk": int}, pk="pk") db.table("t").create({"pk": int}, pk="pk")
assert "t" in db.table_names() assert "t" in db.table_names()
result = runner.invoke( result = runner.invoke(
cli.cli, cli.cli,
@ -1601,7 +1603,7 @@ def test_drop_table_error():
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
db = Database("test.db") db = Database("test.db")
db["t"].create({"pk": int}, pk="pk") db.table("t").create({"pk": int}, pk="pk")
result = runner.invoke( result = runner.invoke(
cli.cli, cli.cli,
[ [
@ -1624,7 +1626,7 @@ def test_drop_table_on_view_errors():
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
db = Database("test.db") db = Database("test.db")
db["t"].insert({"id": 1}) db.table("t").insert({"id": 1})
db.create_view("v", "select * from t") db.create_view("v", "select * from t")
result = runner.invoke(cli.cli, ["drop-table", "test.db", "v"]) result = runner.invoke(cli.cli, ["drop-table", "test.db", "v"])
assert result.exit_code == 1 assert result.exit_code == 1
@ -1660,7 +1662,7 @@ def test_drop_view_on_table_errors():
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
db = Database("test.db") db = Database("test.db")
db["t"].insert({"id": 1}) db.table("t").insert({"id": 1})
result = runner.invoke(cli.cli, ["drop-view", "test.db", "t"]) result = runner.invoke(cli.cli, ["drop-view", "test.db", "t"])
assert result.exit_code == 1 assert result.exit_code == 1
assert 'Error: "t" is a table, not a view - use drop-table to drop it' == ( assert 'Error: "t" is a table, not a view - use drop-table to drop it' == (
@ -1677,7 +1679,7 @@ def test_drop_view_error():
runner = CliRunner() runner = CliRunner()
with runner.isolated_filesystem(): with runner.isolated_filesystem():
db = Database("test.db") db = Database("test.db")
db["t"].create({"pk": int}, pk="pk") db.table("t").create({"pk": int}, pk="pk")
result = runner.invoke( result = runner.invoke(
cli.cli, cli.cli,
[ [
@ -1702,7 +1704,7 @@ def test_enable_wal():
with runner.isolated_filesystem(): with runner.isolated_filesystem():
for dbname in dbs: for dbname in dbs:
db = Database(dbname) db = Database(dbname)
db["t"].create({"pk": int}, pk="pk") db.table("t").create({"pk": int}, pk="pk")
assert db.journal_mode == "delete" assert db.journal_mode == "delete"
result = runner.invoke(cli.cli, ["enable-wal"] + dbs, catch_exceptions=False) result = runner.invoke(cli.cli, ["enable-wal"] + dbs, catch_exceptions=False)
assert result.exit_code == 0 assert result.exit_code == 0
@ -1717,7 +1719,7 @@ def test_disable_wal():
with runner.isolated_filesystem(): with runner.isolated_filesystem():
for dbname in dbs: for dbname in dbs:
db = Database(dbname) db = Database(dbname)
db["t"].create({"pk": int}, pk="pk") db.table("t").create({"pk": int}, pk="pk")
db.enable_wal() db.enable_wal()
assert db.journal_mode == "wal" assert db.journal_mode == "wal"
result = runner.invoke(cli.cli, ["disable-wal"] + dbs) result = runner.invoke(cli.cli, ["disable-wal"] + dbs)
@ -1740,7 +1742,7 @@ def test_disable_wal():
def test_query_update(db_path, args, expected): def test_query_update(db_path, args, expected):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["dogs"].insert_all( db.table("dogs").insert_all(
[ [
{"id": 1, "age": 4, "name": "Cleo"}, {"id": 1, "age": 4, "name": "Cleo"},
] ]
@ -1756,11 +1758,13 @@ def test_query_update(db_path, args, expected):
def test_add_foreign_keys(db_path): def test_add_foreign_keys(db_path):
db = Database(db_path) db = Database(db_path)
db["countries"].insert({"id": 7, "name": "Panama"}, pk="id") db.table("countries").insert({"id": 7, "name": "Panama"}, pk="id")
db["authors"].insert({"id": 3, "name": "Matilda", "country_id": 7}, pk="id") db.table("authors").insert({"id": 3, "name": "Matilda", "country_id": 7}, pk="id")
db["books"].insert({"id": 2, "title": "Wolf anatomy", "author_id": 3}, pk="id") db.table("books").insert(
assert db["authors"].foreign_keys == [] {"id": 2, "title": "Wolf anatomy", "author_id": 3}, pk="id"
assert db["books"].foreign_keys == [] )
assert db.table("authors").foreign_keys == []
assert db.table("books").foreign_keys == []
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
[ [
@ -1777,7 +1781,7 @@ def test_add_foreign_keys(db_path):
], ],
) )
assert result.exit_code == 0 assert result.exit_code == 0
assert db["authors"].foreign_keys == [ assert db.table("authors").foreign_keys == [
ForeignKey( ForeignKey(
table="authors", table="authors",
column="country_id", column="country_id",
@ -1785,7 +1789,7 @@ def test_add_foreign_keys(db_path):
other_column="id", other_column="id",
) )
] ]
assert db["books"].foreign_keys == [ assert db.table("books").foreign_keys == [
ForeignKey( ForeignKey(
table="books", column="author_id", other_table="authors", other_column="id" table="books", column="author_id", other_table="authors", other_column="id"
) )
@ -1909,7 +1913,7 @@ def test_add_foreign_keys(db_path):
def test_transform(db_path, args, expected_schema): def test_transform(db_path, args, expected_schema):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["dogs"].insert( db.table("dogs").insert(
{"id": 1, "age": 4, "name": "Cleo"}, {"id": 1, "age": 4, "name": "Cleo"},
not_null={"age"}, not_null={"age"},
defaults={"age": 1}, defaults={"age": 1},
@ -1918,20 +1922,20 @@ def test_transform(db_path, args, expected_schema):
result = CliRunner().invoke(cli.cli, ["transform", db_path, "dogs"] + args) result = CliRunner().invoke(cli.cli, ["transform", db_path, "dogs"] + args)
print(result.output) print(result.output)
assert result.exit_code == 0 assert result.exit_code == 0
schema = db["dogs"].schema schema = db.table("dogs").schema
assert schema == expected_schema assert schema == expected_schema
def test_transform_sql(db_path): def test_transform_sql(db_path):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["dogs"].insert( db.table("dogs").insert(
{"id": 1, "age": 4, "name": "Cleo"}, {"id": 1, "age": 4, "name": "Cleo"},
not_null={"age"}, not_null={"age"},
defaults={"age": 1}, defaults={"age": 1},
pk="id", pk="id",
) )
original_schema = db["dogs"].schema original_schema = db.table("dogs").schema
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, ["transform", db_path, "dogs", "--drop", "name", "--sql"] cli.cli, ["transform", db_path, "dogs", "--drop", "name", "--sql"]
@ -1942,7 +1946,7 @@ def test_transform_sql(db_path):
assert '"age" INTEGER NOT NULL DEFAULT' in result.output assert '"age" INTEGER NOT NULL DEFAULT' in result.output
assert 'DROP TABLE "dogs";' in result.output assert 'DROP TABLE "dogs";' in result.output
assert 'ALTER TABLE "dogs_new_' in result.output assert 'ALTER TABLE "dogs_new_' in result.output
assert db["dogs"].schema == original_schema assert db.table("dogs").schema == original_schema
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -1958,12 +1962,12 @@ def test_transform_strict_option(db_path, initial_strict, args, expected_strict)
db = Database(db_path) db = Database(db_path)
if not db.supports_strict: if not db.supports_strict:
pytest.skip("SQLite version does not support strict tables") pytest.skip("SQLite version does not support strict tables")
db["dogs"].create({"id": int}, strict=initial_strict) db.table("dogs").create({"id": int}, strict=initial_strict)
result = CliRunner().invoke(cli.cli, ["transform", db_path, "dogs"] + args) result = CliRunner().invoke(cli.cli, ["transform", db_path, "dogs"] + args)
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert db["dogs"].strict is expected_strict assert db.table("dogs").strict is expected_strict
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -1977,20 +1981,20 @@ def test_transform_strict_option_sql(db_path, initial_strict, flag, sql_is_stric
db = Database(db_path) db = Database(db_path)
if not db.supports_strict: if not db.supports_strict:
pytest.skip("SQLite version does not support strict tables") pytest.skip("SQLite version does not support strict tables")
db["dogs"].create({"id": int}, strict=initial_strict) db.table("dogs").create({"id": int}, strict=initial_strict)
result = CliRunner().invoke(cli.cli, ["transform", db_path, "dogs", flag, "--sql"]) result = CliRunner().invoke(cli.cli, ["transform", db_path, "dogs", flag, "--sql"])
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert (") STRICT;" in result.output) is sql_is_strict assert (") STRICT;" in result.output) is sql_is_strict
assert db["dogs"].strict is initial_strict assert db.table("dogs").strict is initial_strict
def test_transform_strict_option_with_invalid_data(db_path): def test_transform_strict_option_with_invalid_data(db_path):
db = Database(db_path) db = Database(db_path)
if not db.supports_strict: if not db.supports_strict:
pytest.skip("SQLite version does not support strict tables") pytest.skip("SQLite version does not support strict tables")
dogs = db["dogs"] dogs = db.table("dogs")
dogs.create({"id": int}) dogs.create({"id": int})
dogs.insert({"id": "not-an-integer"}) dogs.insert({"id": "not-an-integer"})
@ -2048,10 +2052,10 @@ def test_transform_add_or_drop_foreign_key(db_path, extra_args, expected_schema)
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
# Create table with three foreign keys so we can drop two of them # Create table with three foreign keys so we can drop two of them
db["continent"].insert({"id": 1, "name": "Europe"}, pk="id") db.table("continent").insert({"id": 1, "name": "Europe"}, pk="id")
db["country"].insert({"id": 1, "name": "France"}, pk="id") db.table("country").insert({"id": 1, "name": "France"}, pk="id")
db["city"].insert({"id": 24, "name": "Paris"}, pk="id") db.table("city").insert({"id": 24, "name": "Paris"}, pk="id")
db["places"].insert( db.table("places").insert(
{ {
"id": 32, "id": 32,
"name": "Caveau de la Huchette", "name": "Caveau de la Huchette",
@ -2072,7 +2076,7 @@ def test_transform_add_or_drop_foreign_key(db_path, extra_args, expected_schema)
+ extra_args, + extra_args,
) )
assert result.exit_code == 0 assert result.exit_code == 0
schema = db["places"].schema schema = db.table("places").schema
assert schema == expected_schema assert schema == expected_schema
@ -2133,7 +2137,7 @@ _common_other_schema = (
def test_extract(db_path, args, expected_table_schema, expected_other_schema): def test_extract(db_path, args, expected_table_schema, expected_other_schema):
db = Database(db_path) db = Database(db_path)
with db.conn: with db.conn:
db["trees"].insert( db.table("trees").insert(
{"id": 1, "address": "4 Park Ave", "species": "Palm"}, {"id": 1, "address": "4 Park Ave", "species": "Palm"},
pk="id", pk="id",
) )
@ -2142,7 +2146,7 @@ def test_extract(db_path, args, expected_table_schema, expected_other_schema):
) )
print(result.output) print(result.output)
assert result.exit_code == 0 assert result.exit_code == 0
schema = db["trees"].schema schema = db.table("trees").schema
assert schema == expected_table_schema assert schema == expected_table_schema
other_schema = next( other_schema = next(
t for t in db.tables if t.name not in ("trees", "Gosh", "Gosh2") t for t in db.tables if t.name not in ("trees", "Gosh", "Gosh2")
@ -2190,7 +2194,7 @@ def test_insert_encoding(tmpdir):
) )
assert good_result.exit_code == 0 assert good_result.exit_code == 0
db = Database(db_path) db = Database(db_path)
assert list(db["places"].rows) == [ assert list(db.table("places").rows) == [
{ {
"date": "2020-01-01", "date": "2020-01-01",
"name": "Barra da Lagoa", "name": "Barra da Lagoa",
@ -2226,7 +2230,7 @@ def test_insert_encoding(tmpdir):
def test_search(tmpdir, fts, extra_arg, expected): def test_search(tmpdir, fts, extra_arg, expected):
db_path = str(tmpdir / "test.db") db_path = str(tmpdir / "test.db")
db = Database(db_path) db = Database(db_path)
db["articles"].insert_all( db.table("articles").insert_all(
[ [
{"id": 1, "title": "Title the first"}, {"id": 1, "title": "Title the first"},
{"id": 2, "title": "Title the second"}, {"id": 2, "title": "Title the second"},
@ -2234,7 +2238,7 @@ def test_search(tmpdir, fts, extra_arg, expected):
], ],
pk="id", pk="id",
) )
db["articles"].enable_fts(["title"], fts_version=fts) db.table("articles").enable_fts(["title"], fts_version=fts)
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
["search", db_path, "articles", "second"] + ([extra_arg] if extra_arg else []), ["search", db_path, "articles", "second"] + ([extra_arg] if extra_arg else []),
@ -2247,7 +2251,7 @@ def test_search(tmpdir, fts, extra_arg, expected):
def test_search_quote(tmpdir): def test_search_quote(tmpdir):
db_path = str(tmpdir / "test.db") db_path = str(tmpdir / "test.db")
db = Database(db_path) db = Database(db_path)
db["creatures"].insert({"name": "dog."}).enable_fts(["name"]) db.table("creatures").insert({"name": "dog."}).enable_fts(["name"])
# Without --quote should return an error # Without --quote should return an error
error_result = CliRunner().invoke(cli.cli, ["search", db_path, "creatures", 'dog"']) error_result = CliRunner().invoke(cli.cli, ["search", db_path, "creatures", 'dog"'])
assert error_result.exit_code == 1 assert error_result.exit_code == 1
@ -2355,11 +2359,11 @@ _TRIGGERS_EXPECTED = (
def test_triggers(tmpdir, extra_args, expected): def test_triggers(tmpdir, extra_args, expected):
db_path = str(tmpdir / "test.db") db_path = str(tmpdir / "test.db")
db = Database(db_path) db = Database(db_path)
db["articles"].insert( db.table("articles").insert(
{"id": 1, "title": "Title the first"}, {"id": 1, "title": "Title the first"},
pk="id", pk="id",
) )
db["counter"].insert({"count": 1}) db.table("counter").insert({"count": 1})
db.conn.execute(textwrap.dedent(""" db.conn.execute(textwrap.dedent("""
CREATE TRIGGER blah AFTER INSERT ON articles CREATE TRIGGER blah AFTER INSERT ON articles
BEGIN BEGIN
@ -2420,9 +2424,9 @@ def test_triggers(tmpdir, extra_args, expected):
def test_schema(tmpdir, options, expected): def test_schema(tmpdir, options, expected):
db_path = str(tmpdir / "test.db") db_path = str(tmpdir / "test.db")
db = Database(db_path) db = Database(db_path)
db["dogs"].create({"id": int, "name": str}) db.table("dogs").create({"id": int, "name": str})
db["chickens"].create({"id": int, "name": str, "breed": str}) db.table("chickens").create({"id": int, "name": str, "breed": str})
db["chickens"].create_index(["breed"]) db.table("chickens").create_index(["breed"])
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
["schema", db_path] + options, ["schema", db_path] + options,
@ -2446,7 +2450,7 @@ def test_long_csv_column_value(tmpdir):
) )
assert result.exit_code == 0 assert result.exit_code == 0
db = Database(db_path) db = Database(db_path)
rows = list(db["bigtable"].rows) rows = list(db.table("bigtable").rows)
assert len(rows) == 1 assert len(rows) == 1
assert rows[0]["text"] == long_string assert rows[0]["text"] == long_string
@ -2473,7 +2477,7 @@ def test_import_no_headers(tmpdir, args, tsv):
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
db = Database(db_path) db = Database(db_path)
schema = db["creatures"].schema schema = db.table("creatures").schema
assert schema == ( assert schema == (
'CREATE TABLE "creatures" (\n' 'CREATE TABLE "creatures" (\n'
' "untitled_1" TEXT,\n' ' "untitled_1" TEXT,\n'
@ -2481,7 +2485,7 @@ def test_import_no_headers(tmpdir, args, tsv):
' "untitled_3" TEXT\n' ' "untitled_3" TEXT\n'
")" ")"
) )
rows = list(db["creatures"].rows) rows = list(db.table("creatures").rows)
assert rows == [ assert rows == [
{"untitled_1": "Cleo", "untitled_2": "Dog", "untitled_3": "5"}, {"untitled_1": "Cleo", "untitled_2": "Dog", "untitled_3": "5"},
{"untitled_1": "Tracy", "untitled_2": "Spider", "untitled_3": "7"}, {"untitled_1": "Tracy", "untitled_2": "Spider", "untitled_3": "7"},
@ -2493,10 +2497,10 @@ def test_attach(tmpdir):
bar_path = str(tmpdir / "bar.db") bar_path = str(tmpdir / "bar.db")
db = Database(foo_path) db = Database(foo_path)
with db.conn: with db.conn:
db["foo"].insert({"id": 1, "text": "foo"}) db.table("foo").insert({"id": 1, "text": "foo"})
db2 = Database(bar_path) db2 = Database(bar_path)
with db2.conn: with db2.conn:
db2["bar"].insert({"id": 1, "text": "bar"}) db2.table("bar").insert({"id": 1, "text": "bar"})
db.attach("bar", bar_path) db.attach("bar", bar_path)
sql = "select * from foo union all select * from bar.bar" sql = "select * from foo union all select * from bar.bar"
result = CliRunner().invoke( result = CliRunner().invoke(
@ -2557,7 +2561,7 @@ def test_insert_detect_types(tmpdir):
) )
assert result.exit_code == 0 assert result.exit_code == 0
db = Database(db_path) db = Database(db_path)
assert list(db["creatures"].rows) == [ assert list(db.table("creatures").rows) == [
{"name": "Cleo", "age": 6, "weight": 45.5}, {"name": "Cleo", "age": 6, "weight": 45.5},
{"name": "Dori", "age": 1, "weight": 3.5}, {"name": "Dori", "age": 1, "weight": 3.5},
] ]
@ -2589,7 +2593,7 @@ def test_upsert_detect_types(tmpdir):
) )
assert result.exit_code == 0 assert result.exit_code == 0
db = Database(db_path) db = Database(db_path)
assert list(db["creatures"].rows) == [ assert list(db.table("creatures").rows) == [
{"id": 1, "name": "Cleo", "age": 6, "weight": 45.5}, {"id": 1, "name": "Cleo", "age": 6, "weight": 45.5},
{"id": 2, "name": "Dori", "age": 1, "weight": 3.5}, {"id": 2, "name": "Dori", "age": 1, "weight": 3.5},
] ]
@ -2608,7 +2612,7 @@ def test_csv_detect_types_creates_real_columns(tmpdir):
assert result.exit_code == 0 assert result.exit_code == 0
db = Database(db_path) db = Database(db_path)
# Check that the schema uses REAL for the weight column # Check that the schema uses REAL for the weight column
assert db["creatures"].schema == ( assert db.table("creatures").schema == (
'CREATE TABLE "creatures" (\n' 'CREATE TABLE "creatures" (\n'
' "name" TEXT,\n' ' "name" TEXT,\n'
' "age" INTEGER,\n' ' "age" INTEGER,\n'
@ -2630,11 +2634,11 @@ def test_insert_no_detect_types(tmpdir):
assert result.exit_code == 0 assert result.exit_code == 0
db = Database(db_path) db = Database(db_path)
# All columns should be TEXT when --no-detect-types is used # All columns should be TEXT when --no-detect-types is used
assert list(db["creatures"].rows) == [ assert list(db.table("creatures").rows) == [
{"name": "Cleo", "age": "6", "weight": "45.5"}, {"name": "Cleo", "age": "6", "weight": "45.5"},
{"name": "Dori", "age": "1", "weight": "3.5"}, {"name": "Dori", "age": "1", "weight": "3.5"},
] ]
assert db["creatures"].schema == ( assert db.table("creatures").schema == (
'CREATE TABLE "creatures" (\n' 'CREATE TABLE "creatures" (\n'
' "name" TEXT,\n' ' "name" TEXT,\n'
' "age" TEXT,\n' ' "age" TEXT,\n'
@ -2665,11 +2669,11 @@ def test_upsert_no_detect_types(tmpdir):
assert result.exit_code == 0 assert result.exit_code == 0
db = Database(db_path) db = Database(db_path)
# All columns should be TEXT when --no-detect-types is used # All columns should be TEXT when --no-detect-types is used
assert list(db["creatures"].rows) == [ assert list(db.table("creatures").rows) == [
{"id": "1", "name": "Cleo", "age": "6", "weight": "45.5"}, {"id": "1", "name": "Cleo", "age": "6", "weight": "45.5"},
{"id": "2", "name": "Dori", "age": "1", "weight": "3.5"}, {"id": "2", "name": "Dori", "age": "1", "weight": "3.5"},
] ]
assert db["creatures"].schema == ( assert db.table("creatures").schema == (
'CREATE TABLE "creatures" (\n' 'CREATE TABLE "creatures" (\n'
' "id" TEXT PRIMARY KEY,\n' ' "id" TEXT PRIMARY KEY,\n'
' "name" TEXT,\n' ' "name" TEXT,\n'
@ -2751,20 +2755,20 @@ def test_create_database(tmpdir, enable_wal):
def test_analyze(tmpdir, options, expected): def test_analyze(tmpdir, options, expected):
db_path = str(tmpdir / "test.db") db_path = str(tmpdir / "test.db")
db = Database(db_path) db = Database(db_path)
db["one_index"].insert({"id": 1, "name": "Cleo"}, pk="id") db.table("one_index").insert({"id": 1, "name": "Cleo"}, pk="id")
db["one_index"].create_index(["name"]) db.table("one_index").create_index(["name"])
db["two_indexes"].insert({"id": 1, "name": "Cleo", "species": "dog"}, pk="id") db.table("two_indexes").insert({"id": 1, "name": "Cleo", "species": "dog"}, pk="id")
db["two_indexes"].create_index(["name"]) db.table("two_indexes").create_index(["name"])
db["two_indexes"].create_index(["species"]) db.table("two_indexes").create_index(["species"])
result = CliRunner().invoke(cli.cli, ["analyze", db_path] + options) result = CliRunner().invoke(cli.cli, ["analyze", db_path] + options)
assert result.exit_code == 0 assert result.exit_code == 0
assert list(db["sqlite_stat1"].rows) == expected assert list(db.table("sqlite_stat1").rows) == expected
def test_rename_table(tmpdir): def test_rename_table(tmpdir):
db_path = str(tmpdir / "test.db") db_path = str(tmpdir / "test.db")
db = Database(db_path) db = Database(db_path)
db["one"].insert({"id": 1, "name": "Cleo"}, pk="id") db.table("one").insert({"id": 1, "name": "Cleo"}, pk="id")
# First try a non-existent table # First try a non-existent table
result_error = CliRunner().invoke( result_error = CliRunner().invoke(
cli.cli, cli.cli,
@ -2782,7 +2786,7 @@ def test_rename_table(tmpdir):
catch_exceptions=False, catch_exceptions=False,
) )
assert result_error2.exit_code == 0 assert result_error2.exit_code == 0
previous_columns = db["one"].columns_dict previous_columns = db.table("one").columns_dict
# Now try for a table that exists # Now try for a table that exists
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
@ -2790,13 +2794,13 @@ def test_rename_table(tmpdir):
catch_exceptions=False, catch_exceptions=False,
) )
assert result.exit_code == 0 assert result.exit_code == 0
assert db["two"].columns_dict == previous_columns assert db.table("two").columns_dict == previous_columns
def test_duplicate_table(tmpdir): def test_duplicate_table(tmpdir):
db_path = str(tmpdir / "test.db") db_path = str(tmpdir / "test.db")
db = Database(db_path) db = Database(db_path)
db["one"].insert({"id": 1, "name": "Cleo"}, pk="id") db.table("one").insert({"id": 1, "name": "Cleo"}, pk="id")
# First try a non-existent table # First try a non-existent table
result_error = CliRunner().invoke( result_error = CliRunner().invoke(
cli.cli, cli.cli,
@ -2819,8 +2823,8 @@ def test_duplicate_table(tmpdir):
catch_exceptions=False, catch_exceptions=False,
) )
assert result.exit_code == 0 assert result.exit_code == 0
assert db["one"].columns_dict == db["two"].columns_dict assert db.table("one").columns_dict == db.table("two").columns_dict
assert list(db["one"].rows) == list(db["two"].rows) assert list(db.table("one").rows) == list(db.table("two").rows)
@pytest.mark.skipif(not _has_compiled_ext(), reason="Requires compiled ext.c") @pytest.mark.skipif(not _has_compiled_ext(), reason="Requires compiled ext.c")
@ -2863,9 +2867,9 @@ def test_create_table_strict(strict):
+ (["--strict"] if strict else []), + (["--strict"] if strict else []),
) )
assert result.exit_code == 0 assert result.exit_code == 0
assert db["items"].strict == strict or not db.supports_strict assert db.table("items").strict == strict or not db.supports_strict
# Should have a floating point column # Should have a floating point column
assert db["items"].columns_dict == {"id": int, "w": float} assert db.table("items").columns_dict == {"id": int, "w": float}
@pytest.mark.parametrize("method", ("insert", "upsert")) @pytest.mark.parametrize("method", ("insert", "upsert"))
@ -2880,12 +2884,12 @@ def test_insert_upsert_strict(tmpdir, method, strict):
) )
assert result.exit_code == 0 assert result.exit_code == 0
db = Database(db_path) db = Database(db_path)
assert db["items"].strict == strict or not db.supports_strict assert db.table("items").strict == strict or not db.supports_strict
def test_extract_bad_column_clean_error(db_path): def test_extract_bad_column_clean_error(db_path):
db = Database(db_path) db = Database(db_path)
db["trees"].insert({"id": 1, "species": "Palm"}, pk="id") db.table("trees").insert({"id": 1, "species": "Palm"}, pk="id")
result = CliRunner().invoke(cli.cli, ["extract", db_path, "trees", "nope"]) result = CliRunner().invoke(cli.cli, ["extract", db_path, "trees", "nope"])
assert result.exit_code == 1 assert result.exit_code == 1
assert result.exception is None or isinstance(result.exception, SystemExit) assert result.exception is None or isinstance(result.exception, SystemExit)
@ -2894,7 +2898,7 @@ def test_extract_bad_column_clean_error(db_path):
def test_extract_view_clean_error(db_path): def test_extract_view_clean_error(db_path):
db = Database(db_path) db = Database(db_path)
db["trees"].insert({"id": 1, "species": "Palm"}, pk="id") db.table("trees").insert({"id": 1, "species": "Palm"}, pk="id")
db.create_view("v", "select * from trees") db.create_view("v", "select * from trees")
result = CliRunner().invoke(cli.cli, ["extract", db_path, "v", "species"]) result = CliRunner().invoke(cli.cli, ["extract", db_path, "v", "species"])
assert result.exit_code == 1 assert result.exit_code == 1

View file

@ -13,7 +13,7 @@ from sqlite_utils import Database, cli
def test_db_and_path(tmpdir): def test_db_and_path(tmpdir):
db_path = str(pathlib.Path(tmpdir) / "data.db") db_path = str(pathlib.Path(tmpdir) / "data.db")
db = Database(db_path) db = Database(db_path)
db["example"].insert_all( db.table("example").insert_all(
[ [
{"id": 1, "name": "One"}, {"id": 1, "name": "One"},
{"id": 2, "name": "Two"}, {"id": 2, "name": "Two"},
@ -44,7 +44,7 @@ def test_cli_bulk(test_db_and_path):
{"id": 2, "name": "Two"}, {"id": 2, "name": "Two"},
{"id": 3, "name": "THREE"}, {"id": 3, "name": "THREE"},
{"id": 4, "name": "FOUR"}, {"id": 4, "name": "FOUR"},
] == list(db["example"].rows) ] == list(db.table("example").rows)
def test_cli_bulk_multiple_functions(test_db_and_path): def test_cli_bulk_multiple_functions(test_db_and_path):
@ -70,7 +70,7 @@ def test_cli_bulk_multiple_functions(test_db_and_path):
{"id": 2, "name": "Two"}, {"id": 2, "name": "Two"},
{"id": 3, "name": "THREE"}, {"id": 3, "name": "THREE"},
{"id": 4, "name": "FOUR"}, {"id": 4, "name": "FOUR"},
] == list(db["example"].rows) ] == list(db.table("example").rows)
def test_cli_bulk_batch_size(test_db_and_path): def test_cli_bulk_batch_size(test_db_and_path):
@ -95,13 +95,13 @@ def test_cli_bulk_batch_size(test_db_and_path):
proc.stdin.write(b'{"id": 3, "name": "Three"}\n\n') proc.stdin.write(b'{"id": 3, "name": "Three"}\n\n')
proc.stdin.flush() proc.stdin.flush()
time.sleep(1) time.sleep(1)
assert db["example"].count == 2 assert db.table("example").count == 2
# Writing another should trigger a commit: # Writing another should trigger a commit:
proc.stdin.write(b'{"id": 4, "name": "Four"}\n\n') proc.stdin.write(b'{"id": 4, "name": "Four"}\n\n')
proc.stdin.flush() proc.stdin.flush()
time.sleep(1) time.sleep(1)
assert db["example"].count == 4 assert db.table("example").count == 4
proc.stdin.close() proc.stdin.close()
proc.wait() proc.wait()

View file

@ -12,7 +12,7 @@ from sqlite_utils import cli
@pytest.fixture @pytest.fixture
def test_db_and_path(fresh_db_and_path): def test_db_and_path(fresh_db_and_path):
db, db_path = fresh_db_and_path db, db_path = fresh_db_and_path
db["example"].insert_all( db.table("example").insert_all(
[ [
{"id": 1, "dt": "5th October 2019 12:04"}, {"id": 1, "dt": "5th October 2019 12:04"},
{"id": 2, "dt": "6th October 2019 00:05:06"}, {"id": 2, "dt": "6th October 2019 00:05:06"},
@ -47,12 +47,12 @@ def fresh_db_and_path(tmpdir):
) )
def test_convert_code(fresh_db_and_path, code): def test_convert_code(fresh_db_and_path, code):
db, db_path = fresh_db_and_path db, db_path = fresh_db_and_path
db["t"].insert({"text": "October"}) db.table("t").insert({"text": "October"})
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, ["convert", db_path, "t", "text", code], catch_exceptions=False cli.cli, ["convert", db_path, "t", "text", code], catch_exceptions=False
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
value = next(iter(db["t"].rows))["text"] value = next(iter(db.table("t").rows))["text"]
assert value == "Spooktober" assert value == "Spooktober"
@ -65,7 +65,7 @@ def test_convert_code(fresh_db_and_path, code):
) )
def test_convert_code_errors(fresh_db_and_path, bad_code): def test_convert_code_errors(fresh_db_and_path, bad_code):
db, db_path = fresh_db_and_path db, db_path = fresh_db_and_path
db["t"].insert({"text": "October"}) db.table("t").insert({"text": "October"})
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, ["convert", db_path, "t", "text", bad_code], catch_exceptions=False cli.cli, ["convert", db_path, "t", "text", bad_code], catch_exceptions=False
) )
@ -93,12 +93,12 @@ def test_convert_import(test_db_and_path):
{"id": 2, "dt": "6th OXXober 2019 00:05:06"}, {"id": 2, "dt": "6th OXXober 2019 00:05:06"},
{"id": 3, "dt": ""}, {"id": 3, "dt": ""},
{"id": 4, "dt": None}, {"id": 4, "dt": None},
] == list(db["example"].rows) ] == list(db.table("example").rows)
def test_convert_import_nested(fresh_db_and_path): def test_convert_import_nested(fresh_db_and_path):
db, db_path = fresh_db_and_path db, db_path = fresh_db_and_path
db["example"].insert({"xml": '<item name="Cleo" />'}) db.table("example").insert({"xml": '<item name="Cleo" />'})
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
[ [
@ -114,7 +114,7 @@ def test_convert_import_nested(fresh_db_and_path):
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert [ assert [
{"xml": "Cleo"}, {"xml": "Cleo"},
] == list(db["example"].rows) ] == list(db.table("example").rows)
def test_convert_dryrun(test_db_and_path): def test_convert_dryrun(test_db_and_path):
@ -152,7 +152,7 @@ def test_convert_dryrun(test_db_and_path):
"Would affect 4 rows" "Would affect 4 rows"
) )
# But it should not have actually modified the table data # But it should not have actually modified the table data
assert list(db["example"].rows) == [ assert list(db.table("example").rows) == [
{"id": 1, "dt": "5th October 2019 12:04"}, {"id": 1, "dt": "5th October 2019 12:04"},
{"id": 2, "dt": "6th October 2019 00:05:06"}, {"id": 2, "dt": "6th October 2019 00:05:06"},
{"id": 3, "dt": ""}, {"id": 3, "dt": ""},
@ -269,7 +269,7 @@ def test_convert_output_column(test_db_and_path, drop):
if drop: if drop:
for row in expected: for row in expected:
del row["dt"] del row["dt"]
assert list(db["example"].rows) == expected assert list(db.table("example").rows) == expected
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -352,7 +352,7 @@ def test_convert_output_error(test_db_and_path, options, expected_error):
@pytest.mark.parametrize("drop", (True, False)) @pytest.mark.parametrize("drop", (True, False))
def test_convert_multi(fresh_db_and_path, drop): def test_convert_multi(fresh_db_and_path, drop):
db, db_path = fresh_db_and_path db, db_path = fresh_db_and_path
db["creatures"].insert_all( db.table("creatures").insert_all(
[ [
{"id": 1, "name": "Simon"}, {"id": 1, "name": "Simon"},
{"id": 2, "name": "Cleo"}, {"id": 2, "name": "Cleo"},
@ -378,12 +378,12 @@ def test_convert_multi(fresh_db_and_path, drop):
if drop: if drop:
for row in expected: for row in expected:
del row["name"] del row["name"]
assert list(db["creatures"].rows) == expected assert list(db.table("creatures").rows) == expected
def test_convert_multi_complex_column_types(fresh_db_and_path): def test_convert_multi_complex_column_types(fresh_db_and_path):
db, db_path = fresh_db_and_path db, db_path = fresh_db_and_path
db["rows"].insert_all( db.table("rows").insert_all(
[ [
{"id": 1}, {"id": 1},
{"id": 2}, {"id": 2},
@ -412,7 +412,7 @@ def test_convert_multi_complex_column_types(fresh_db_and_path):
], ],
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert list(db["rows"].rows) == [ assert list(db.table("rows").rows) == [
{"id": 1, "is_str": "", "is_float": 1.2, "is_int": None, "is_bytes": None}, {"id": 1, "is_str": "", "is_float": 1.2, "is_int": None, "is_bytes": None},
{"id": 2, "is_str": None, "is_float": 1.0, "is_int": 12, "is_bytes": None}, {"id": 2, "is_str": None, "is_float": 1.0, "is_int": 12, "is_bytes": None},
{ {
@ -424,7 +424,7 @@ def test_convert_multi_complex_column_types(fresh_db_and_path):
}, },
{"id": 4, "is_str": None, "is_float": None, "is_int": None, "is_bytes": None}, {"id": 4, "is_str": None, "is_float": None, "is_int": None, "is_bytes": None},
] ]
assert db["rows"].schema == ( assert db.table("rows").schema == (
'CREATE TABLE "rows" (\n' 'CREATE TABLE "rows" (\n'
' "id" INTEGER PRIMARY KEY\n' ' "id" INTEGER PRIMARY KEY\n'
', "is_str" TEXT, "is_float" REAL, "is_int" INTEGER, "is_bytes" BLOB)' ', "is_str" TEXT, "is_float" REAL, "is_int" INTEGER, "is_bytes" BLOB)'
@ -435,7 +435,7 @@ def test_convert_multi_complex_column_types(fresh_db_and_path):
def test_recipe_jsonsplit(tmpdir, delimiter): def test_recipe_jsonsplit(tmpdir, delimiter):
db_path = str(pathlib.Path(tmpdir) / "data.db") db_path = str(pathlib.Path(tmpdir) / "data.db")
db = sqlite_utils.Database(db_path) db = sqlite_utils.Database(db_path)
db["example"].insert_all( db.table("example").insert_all(
[ [
{"id": 1, "tags": (delimiter or ",").join(["foo", "bar"])}, {"id": 1, "tags": (delimiter or ",").join(["foo", "bar"])},
{"id": 2, "tags": (delimiter or ",").join(["bar", "baz"])}, {"id": 2, "tags": (delimiter or ",").join(["bar", "baz"])},
@ -448,7 +448,7 @@ def test_recipe_jsonsplit(tmpdir, delimiter):
args = ["convert", db_path, "example", "tags", code] args = ["convert", db_path, "example", "tags", code]
result = CliRunner().invoke(cli.cli, args) result = CliRunner().invoke(cli.cli, args)
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert list(db["example"].rows) == [ assert list(db.table("example").rows) == [
{"id": 1, "tags": '["foo", "bar"]'}, {"id": 1, "tags": '["foo", "bar"]'},
{"id": 2, "tags": '["bar", "baz"]'}, {"id": 2, "tags": '["bar", "baz"]'},
] ]
@ -464,7 +464,7 @@ def test_recipe_jsonsplit(tmpdir, delimiter):
) )
def test_recipe_jsonsplit_type(fresh_db_and_path, type, expected_array): def test_recipe_jsonsplit_type(fresh_db_and_path, type, expected_array):
db, db_path = fresh_db_and_path db, db_path = fresh_db_and_path
db["example"].insert_all( db.table("example").insert_all(
[ [
{"id": 1, "records": "1,2,3"}, {"id": 1, "records": "1,2,3"},
], ],
@ -476,13 +476,13 @@ def test_recipe_jsonsplit_type(fresh_db_and_path, type, expected_array):
args = ["convert", db_path, "example", "records", code] args = ["convert", db_path, "example", "records", code]
result = CliRunner().invoke(cli.cli, args) result = CliRunner().invoke(cli.cli, args)
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert json.loads(db["example"].get(1)["records"]) == expected_array assert json.loads(db.table("example").get(1)["records"]) == expected_array
@pytest.mark.parametrize("drop", (True, False)) @pytest.mark.parametrize("drop", (True, False))
def test_recipe_jsonsplit_output(fresh_db_and_path, drop): def test_recipe_jsonsplit_output(fresh_db_and_path, drop):
db, db_path = fresh_db_and_path db, db_path = fresh_db_and_path
db["example"].insert_all( db.table("example").insert_all(
[ [
{"id": 1, "records": "1,2,3"}, {"id": 1, "records": "1,2,3"},
], ],
@ -501,7 +501,7 @@ def test_recipe_jsonsplit_output(fresh_db_and_path, drop):
} }
if drop: if drop:
del expected["records"] del expected["records"]
assert db["example"].get(1) == expected assert db.table("example").get(1) == expected
def test_cannot_use_drop_without_multi_or_output(fresh_db_and_path): def test_cannot_use_drop_without_multi_or_output(fresh_db_and_path):
@ -558,7 +558,7 @@ def test_convert_where(test_db_and_path):
], ],
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert list(db["example"].rows) == [ assert list(db.table("example").rows) == [
{"id": 1, "dt": "5th October 2019 12:04"}, {"id": 1, "dt": "5th October 2019 12:04"},
{"id": 2, "dt": "6TH OCTOBER 2019 00:05:06"}, {"id": 2, "dt": "6TH OCTOBER 2019 00:05:06"},
{"id": 3, "dt": ""}, {"id": 3, "dt": ""},
@ -568,7 +568,7 @@ def test_convert_where(test_db_and_path):
def test_convert_where_multi(fresh_db_and_path): def test_convert_where_multi(fresh_db_and_path):
db, db_path = fresh_db_and_path db, db_path = fresh_db_and_path
db["names"].insert_all( db.table("names").insert_all(
[{"id": 1, "name": "Cleo"}, {"id": 2, "name": "Bants"}], pk="id" [{"id": 1, "name": "Cleo"}, {"id": 2, "name": "Bants"}], pk="id"
) )
result = CliRunner().invoke( result = CliRunner().invoke(
@ -588,7 +588,7 @@ def test_convert_where_multi(fresh_db_and_path):
], ],
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert list(db["names"].rows) == [ assert list(db.table("names").rows) == [
{"id": 1, "name": "Cleo", "upper": None}, {"id": 1, "name": "Cleo", "upper": None},
{"id": 2, "name": "Bants", "upper": "BANTS"}, {"id": 2, "name": "Bants", "upper": "BANTS"},
] ]
@ -596,7 +596,7 @@ def test_convert_where_multi(fresh_db_and_path):
def test_convert_code_standard_input(fresh_db_and_path): def test_convert_code_standard_input(fresh_db_and_path):
db, db_path = fresh_db_and_path db, db_path = fresh_db_and_path
db["names"].insert_all([{"id": 1, "name": "Cleo"}], pk="id") db.table("names").insert_all([{"id": 1, "name": "Cleo"}], pk="id")
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
[ [
@ -609,27 +609,27 @@ def test_convert_code_standard_input(fresh_db_and_path):
input="value.upper()", input="value.upper()",
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert list(db["names"].rows) == [ assert list(db.table("names").rows) == [
{"id": 1, "name": "CLEO"}, {"id": 1, "name": "CLEO"},
] ]
def test_convert_hyphen_workaround(fresh_db_and_path): def test_convert_hyphen_workaround(fresh_db_and_path):
db, db_path = fresh_db_and_path db, db_path = fresh_db_and_path
db["names"].insert_all([{"id": 1, "name": "Cleo"}], pk="id") db.table("names").insert_all([{"id": 1, "name": "Cleo"}], pk="id")
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
["convert", db_path, "names", "name", '"-"'], ["convert", db_path, "names", "name", '"-"'],
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert list(db["names"].rows) == [ assert list(db.table("names").rows) == [
{"id": 1, "name": "-"}, {"id": 1, "name": "-"},
] ]
def test_convert_initialization_pattern(fresh_db_and_path): def test_convert_initialization_pattern(fresh_db_and_path):
db, db_path = fresh_db_and_path db, db_path = fresh_db_and_path
db["names"].insert_all([{"id": 1, "name": "Cleo"}], pk="id") db.table("names").insert_all([{"id": 1, "name": "Cleo"}], pk="id")
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
[ [
@ -642,7 +642,7 @@ def test_convert_initialization_pattern(fresh_db_and_path):
input="import random\nrandom.seed(1)\ndef convert(value): return random.randint(0, 100)", input="import random\nrandom.seed(1)\ndef convert(value): return random.randint(0, 100)",
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert list(db["names"].rows) == [ assert list(db.table("names").rows) == [
{"id": 1, "name": "17"}, {"id": 1, "name": "17"},
] ]
@ -657,13 +657,13 @@ def test_convert_handles_falsey_values(fresh_db_and_path):
"x", "x",
"-", "-",
] ]
db["t"].insert_all([{"x": 0}, {"x": 1}]) db.table("t").insert_all([{"x": 0}, {"x": 1}])
assert db["t"].get(1)["x"] == 0 assert db.table("t").get(1)["x"] == 0
assert db["t"].get(2)["x"] == 1 assert db.table("t").get(2)["x"] == 1
result = CliRunner().invoke(cli.cli, args, input="value + 1") result = CliRunner().invoke(cli.cli, args, input="value + 1")
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert db["t"].get(1)["x"] == 1 assert db.table("t").get(1)["x"] == 1
assert db["t"].get(2)["x"] == 2 assert db.table("t").get(2)["x"] == 2
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -684,7 +684,7 @@ def test_convert_callable_reference(test_db_and_path, code):
cli.cli, ["convert", db_path, "example", "dt", code], catch_exceptions=False cli.cli, ["convert", db_path, "example", "dt", code], catch_exceptions=False
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
rows = list(db["example"].rows) rows = list(db.table("example").rows)
assert rows[0]["dt"] == "2019-10-05" assert rows[0]["dt"] == "2019-10-05"
assert rows[1]["dt"] == "2019-10-06" assert rows[1]["dt"] == "2019-10-06"
assert rows[2]["dt"] == "" assert rows[2]["dt"] == ""
@ -694,7 +694,7 @@ def test_convert_callable_reference(test_db_and_path, code):
def test_convert_callable_reference_with_import(fresh_db_and_path): def test_convert_callable_reference_with_import(fresh_db_and_path):
"""Test callable reference from an imported module""" """Test callable reference from an imported module"""
db, db_path = fresh_db_and_path db, db_path = fresh_db_and_path
db["example"].insert({"id": 1, "data": '{"name": "test"}'}) db.table("example").insert({"id": 1, "data": '{"name": "test"}'})
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
[ [
@ -710,5 +710,5 @@ def test_convert_callable_reference_with_import(fresh_db_and_path):
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
# json.loads returns a dict, which sqlite stores as JSON string # json.loads returns a dict, which sqlite stores as JSON string
row = db["example"].get(1) row = db.table("example").get(1)
assert row["data"] == '{"name": "test"}' assert row["data"] == '{"name": "test"}'

View file

@ -21,7 +21,7 @@ def test_insert_simple(tmpdir):
) )
db = Database(db_path) db = Database(db_path)
assert ["dogs"] == db.table_names() assert ["dogs"] == db.table_names()
assert [] == db["dogs"].indexes assert [] == db.table("dogs").indexes
def test_insert_from_stdin(tmpdir): def test_insert_from_stdin(tmpdir):
@ -96,7 +96,7 @@ def test_insert_with_primary_keys(db_path, tmpdir, args, expected_pks):
Database(db_path).query("select * from dogs") Database(db_path).query("select * from dogs")
) )
db = Database(db_path) db = Database(db_path)
assert db["dogs"].pks == expected_pks assert db.table("dogs").pks == expected_pks
def test_insert_multiple_with_primary_key(db_path, tmpdir): def test_insert_multiple_with_primary_key(db_path, tmpdir):
@ -110,7 +110,7 @@ def test_insert_multiple_with_primary_key(db_path, tmpdir):
assert result.exit_code == 0 assert result.exit_code == 0
db = Database(db_path) db = Database(db_path)
assert dogs == list(db.query("select * from dogs order by id")) assert dogs == list(db.query("select * from dogs order by id"))
assert ["id"] == db["dogs"].pks assert ["id"] == db.table("dogs").pks
def test_insert_multiple_with_compound_primary_key(db_path, tmpdir): def test_insert_multiple_with_compound_primary_key(db_path, tmpdir):
@ -127,7 +127,7 @@ def test_insert_multiple_with_compound_primary_key(db_path, tmpdir):
assert result.exit_code == 0 assert result.exit_code == 0
db = Database(db_path) db = Database(db_path)
assert dogs == list(db.query("select * from dogs order by breed, id")) assert dogs == list(db.query("select * from dogs order by breed, id"))
assert {"breed", "id"} == set(db["dogs"].pks) assert {"breed", "id"} == set(db.table("dogs").pks)
assert ( assert (
'CREATE TABLE "dogs" (\n' 'CREATE TABLE "dogs" (\n'
' "breed" TEXT,\n' ' "breed" TEXT,\n'
@ -136,7 +136,7 @@ def test_insert_multiple_with_compound_primary_key(db_path, tmpdir):
' "age" INTEGER,\n' ' "age" INTEGER,\n'
' PRIMARY KEY ("id", "breed")\n' ' PRIMARY KEY ("id", "breed")\n'
")" ")"
) == db["dogs"].schema ) == db.table("dogs").schema
def test_insert_not_null_default(db_path, tmpdir): def test_insert_not_null_default(db_path, tmpdir):
@ -160,7 +160,7 @@ def test_insert_not_null_default(db_path, tmpdir):
' "name" TEXT NOT NULL,\n' ' "name" TEXT NOT NULL,\n'
" \"age\" INTEGER NOT NULL DEFAULT '1',\n" " \"age\" INTEGER NOT NULL DEFAULT '1',\n"
" \"score\" INTEGER DEFAULT '5'\n)" " \"score\" INTEGER DEFAULT '5'\n)"
) == db["dogs"].schema ) == db.table("dogs").schema
def test_insert_binary_base64(db_path): def test_insert_binary_base64(db_path):
@ -191,7 +191,7 @@ def test_insert_newline_delimited(db_path):
def test_insert_ignore(db_path, tmpdir): def test_insert_ignore(db_path, tmpdir):
db = Database(db_path) db = Database(db_path)
db["dogs"].insert({"id": 1, "name": "Cleo"}, pk="id") db.table("dogs").insert({"id": 1, "name": "Cleo"}, pk="id")
json_path = str(tmpdir / "dogs.json") json_path = str(tmpdir / "dogs.json")
with open(json_path, "w") as fp: with open(json_path, "w") as fp:
fp.write(json.dumps([{"id": 1, "name": "Bailey"}])) fp.write(json.dumps([{"id": 1, "name": "Bailey"}]))
@ -232,7 +232,7 @@ def test_insert_csv_tsv(content, options, db_path, tmpdir):
catch_exceptions=False, catch_exceptions=False,
) )
assert result.exit_code == 0 assert result.exit_code == 0
assert [{"foo": "1", "bar": "2", "baz": "cat,dog"}] == list(db["data"].rows) assert [{"foo": "1", "bar": "2", "baz": "cat,dog"}] == list(db.table("data").rows)
@pytest.mark.parametrize("empty_null", (True, False)) @pytest.mark.parametrize("empty_null", (True, False))
@ -248,7 +248,7 @@ def test_insert_csv_empty_null(db_path, empty_null):
) )
assert result.exit_code == 0 assert result.exit_code == 0
db = Database(db_path) db = Database(db_path)
assert [r for r in db["data"].rows] == [ assert [r for r in db.table("data").rows] == [
{"foo": "1", "bar": None if empty_null else "", "baz": "cat"} {"foo": "1", "bar": None if empty_null else "", "baz": "cat"}
] ]
@ -302,7 +302,7 @@ def test_insert_replace(db_path, tmpdir):
test_insert_multiple_with_primary_key(db_path, tmpdir) test_insert_multiple_with_primary_key(db_path, tmpdir)
json_path = str(tmpdir / "insert-replace.json") json_path = str(tmpdir / "insert-replace.json")
db = Database(db_path) db = Database(db_path)
assert db["dogs"].count == 20 assert db.table("dogs").count == 20
insert_replace_dogs = [ insert_replace_dogs = [
{"id": 1, "name": "Insert replaced 1", "age": 4}, {"id": 1, "name": "Insert replaced 1", "age": 4},
{"id": 2, "name": "Insert replaced 2", "age": 4}, {"id": 2, "name": "Insert replaced 2", "age": 4},
@ -314,7 +314,7 @@ def test_insert_replace(db_path, tmpdir):
cli.cli, ["insert", db_path, "dogs", json_path, "--pk", "id", "--replace"] cli.cli, ["insert", db_path, "dogs", json_path, "--pk", "id", "--replace"]
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert db["dogs"].count == 21 assert db.table("dogs").count == 21
assert ( assert (
list(db.query("select * from dogs where id in (1, 2, 21) order by id")) list(db.query("select * from dogs where id in (1, 2, 21) order by id"))
== insert_replace_dogs == insert_replace_dogs
@ -377,7 +377,7 @@ def test_insert_alter(db_path, tmpdir):
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
# Soundness check the database itself # Soundness check the database itself
db = Database(db_path) db = Database(db_path)
assert {"foo": str, "n": int, "baz": int} == db["from_json_nl"].columns_dict assert {"foo": str, "n": int, "baz": int} == db.table("from_json_nl").columns_dict
assert [ assert [
{"foo": "bar", "n": 1, "baz": None}, {"foo": "bar", "n": 1, "baz": None},
{"foo": "baz", "n": 2, "baz": None}, {"foo": "baz", "n": 2, "baz": None},
@ -387,8 +387,8 @@ def test_insert_alter(db_path, tmpdir):
def test_insert_analyze(db_path): def test_insert_analyze(db_path):
db = Database(db_path) db = Database(db_path)
db["rows"].insert({"foo": "x", "n": 3}) db.table("rows").insert({"foo": "x", "n": 3})
db["rows"].create_index(["n"]) db.table("rows").create_index(["n"])
assert "sqlite_stat1" not in db.table_names() assert "sqlite_stat1" not in db.table_names()
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
@ -583,7 +583,7 @@ def test_insert_streaming_batch_size_1(db_path):
def try_until(expected): def try_until(expected):
tries = 0 tries = 0
while True: while True:
rows = list(Database(db_path)["rows"].rows) rows = list(Database(db_path).table("rows").rows)
if rows == expected: if rows == expected:
return return
tries += 1 tries += 1
@ -615,13 +615,13 @@ def test_insert_csv_headers_only(tmpdir):
assert result.exit_code == 0 assert result.exit_code == 0
# Table should not exist since there were no data rows # Table should not exist since there were no data rows
db = Database(db_path) db = Database(db_path)
assert not db["data"].exists() assert not db.table("data").exists()
def test_insert_into_view_errors(tmpdir): def test_insert_into_view_errors(tmpdir):
db_path = str(tmpdir / "test.db") db_path = str(tmpdir / "test.db")
db = Database(db_path) db = Database(db_path)
db["t"].insert({"id": 1}) db.table("t").insert({"id": 1})
db.create_view("v", "select * from t") db.create_view("v", "select * from t")
db.close() db.close()
result = CliRunner().invoke( result = CliRunner().invoke(
@ -637,7 +637,7 @@ def test_insert_csv_detect_types_leaves_existing_table_alone(db_path):
# table would rewrite its column types and corrupt data such as # table would rewrite its column types and corrupt data such as
# TEXT zip codes with leading zeros # TEXT zip codes with leading zeros
db = Database(db_path) db = Database(db_path)
db["places"].insert({"name": "Boston", "zip": "01234"}) db.table("places").insert({"name": "Boston", "zip": "01234"})
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
["insert", db_path, "places", "-", "--csv"], ["insert", db_path, "places", "-", "--csv"],
@ -645,8 +645,8 @@ def test_insert_csv_detect_types_leaves_existing_table_alone(db_path):
input="name,zip\nSF,94107", input="name,zip\nSF,94107",
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert db["places"].columns_dict["zip"] is str assert db.table("places").columns_dict["zip"] is str
assert list(db["places"].rows) == [ assert list(db.table("places").rows) == [
{"name": "Boston", "zip": "01234"}, {"name": "Boston", "zip": "01234"},
{"name": "SF", "zip": "94107"}, {"name": "SF", "zip": "94107"},
] ]
@ -662,7 +662,7 @@ def test_insert_csv_detect_types_new_table(db_path):
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
db = Database(db_path) db = Database(db_path)
assert db["data"].columns_dict == {"name": str, "age": int, "weight": float} assert db.table("data").columns_dict == {"name": str, "age": int, "weight": float}
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -708,13 +708,13 @@ def test_insert_upsert_csv_type_overrides_detected_types(
expected_columns = {"zipcode": str, "score": float} expected_columns = {"zipcode": str, "score": float}
if command == "upsert": if command == "upsert":
expected_columns = {"id": int, **expected_columns} expected_columns = {"id": int, **expected_columns}
assert db["places"].columns_dict == expected_columns assert db.table("places").columns_dict == expected_columns
assert list(db["places"].rows) == [expected_row] assert list(db.table("places").rows) == [expected_row]
def test_upsert_csv_detect_types_leaves_existing_table_alone(db_path): def test_upsert_csv_detect_types_leaves_existing_table_alone(db_path):
db = Database(db_path) db = Database(db_path)
db["places"].insert({"id": 1, "name": "Boston", "zip": "01234"}, pk="id") db.table("places").insert({"id": 1, "name": "Boston", "zip": "01234"}, pk="id")
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
["upsert", db_path, "places", "-", "--csv", "--pk", "id"], ["upsert", db_path, "places", "-", "--csv", "--pk", "id"],
@ -722,15 +722,15 @@ def test_upsert_csv_detect_types_leaves_existing_table_alone(db_path):
input="id,name,zip\n2,SF,94107", input="id,name,zip\n2,SF,94107",
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert db["places"].columns_dict["zip"] is str assert db.table("places").columns_dict["zip"] is str
assert db["places"].get(1)["zip"] == "01234" assert db.table("places").get(1)["zip"] == "01234"
def test_insert_invalid_pk_clean_error(db_path): def test_insert_invalid_pk_clean_error(db_path):
# An invalid --pk against an existing table should be a clean CLI # An invalid --pk against an existing table should be a clean CLI
# error, not a raw InvalidColumns traceback # error, not a raw InvalidColumns traceback
db = Database(db_path) db = Database(db_path)
db["t"].insert({"a": 1}) db.table("t").insert({"a": 1})
result = CliRunner().invoke( result = CliRunner().invoke(
cli.cli, cli.cli,
["insert", db_path, "t", "-", "--pk", "badcol"], ["insert", db_path, "t", "-", "--pk", "badcol"],
@ -765,8 +765,8 @@ def test_insert_code(tmpdir, code):
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
db = Database(db_path) db = Database(db_path)
assert db["creatures"].pks == ["id"] assert db.table("creatures").pks == ["id"]
assert list(db["creatures"].rows) == [ assert list(db.table("creatures").rows) == [
{"id": 1, "name": "Cleo"}, {"id": 1, "name": "Cleo"},
{"id": 2, "name": "Suna"}, {"id": 2, "name": "Suna"},
] ]
@ -782,7 +782,7 @@ def test_insert_code_from_file(tmpdir):
["insert", db_path, "creatures", "--code", code_path], ["insert", db_path, "creatures", "--code", code_path],
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert list(Database(db_path)["creatures"].rows) == [ assert list(Database(db_path).table("creatures").rows) == [
{"id": 1, "name": "Cleo"}, {"id": 1, "name": "Cleo"},
{"id": 2, "name": "Suna"}, {"id": 2, "name": "Suna"},
] ]
@ -791,7 +791,7 @@ def test_insert_code_from_file(tmpdir):
def test_upsert_code(tmpdir): def test_upsert_code(tmpdir):
db_path = str(tmpdir / "dogs.db") db_path = str(tmpdir / "dogs.db")
db = Database(db_path) db = Database(db_path)
db["creatures"].insert_all( db.table("creatures").insert_all(
[{"id": 1, "name": "old"}, {"id": 2, "name": "Suna"}], pk="id" [{"id": 1, "name": "old"}, {"id": 2, "name": "Suna"}], pk="id"
) )
result = CliRunner().invoke( result = CliRunner().invoke(
@ -799,7 +799,7 @@ def test_upsert_code(tmpdir):
["upsert", db_path, "creatures", "--code", CODE_ROWS_FUNCTION, "--pk", "id"], ["upsert", db_path, "creatures", "--code", CODE_ROWS_FUNCTION, "--pk", "id"],
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert list(db["creatures"].rows) == [ assert list(db.table("creatures").rows) == [
{"id": 1, "name": "Cleo"}, {"id": 1, "name": "Cleo"},
{"id": 2, "name": "Suna"}, {"id": 2, "name": "Suna"},
] ]
@ -858,7 +858,9 @@ def test_insert_code_single_dict(tmpdir):
], ],
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
assert list(Database(db_path)["creatures"].rows) == [{"id": 1, "name": "Cleo"}] assert list(Database(db_path).table("creatures").rows) == [
{"id": 1, "name": "Cleo"}
]
def test_insert_code_not_iterable(tmpdir): def test_insert_code_not_iterable(tmpdir):

View file

@ -228,7 +228,7 @@ def test_memory_save(tmpdir, extra_args):
) )
assert result.exit_code == 0 assert result.exit_code == 0
db = Database(save_to) db = Database(save_to)
assert list(db["stdin"].rows) == [ assert list(db.table("stdin").rows) == [
{"id": 1, "name": "Cleo"}, {"id": 1, "name": "Cleo"},
{"id": 2, "name": "Bants"}, {"id": 2, "name": "Bants"},
] ]

View file

@ -13,11 +13,11 @@ m = Migrations("hello")
@m() @m()
def foo(db): def foo(db):
db["foo"].insert({"hello": "world"}) db.table("foo").insert({"hello": "world"})
@m() @m()
def bar(db): def bar(db):
db["bar"].insert({"hello": "world"}) db.table("bar").insert({"hello": "world"})
""" """
@ -42,21 +42,21 @@ creatures = Migrations("creatures")
@creatures() @creatures()
def create_table(db): def create_table(db):
db["creatures"].insert({"name": "Cleo"}) db.table("creatures").insert({"name": "Cleo"})
@creatures() @creatures()
def add_weight(db): def add_weight(db):
db["creature_weights"].insert({"weight": 4.2}) db.table("creature_weights").insert({"weight": 4.2})
sales = Migrations("sales") sales = Migrations("sales")
@sales() @sales()
def create_table(db): def create_table(db):
db["sales"].insert({"id": 1}) db.table("sales").insert({"id": 1})
@sales() @sales()
def add_weight(db): def add_weight(db):
db["sales_weights"].insert({"weight": 10}) db.table("sales_weights").insert({"weight": 10})
""", """,
"utf-8", "utf-8",
) )
@ -99,10 +99,10 @@ def test_basic(two_migrations, arg):
assert " Pending:\n (none)" in list_output assert " Pending:\n (none)" in list_output
db = sqlite_utils.Database(db_path) db = sqlite_utils.Database(db_path)
assert db["foo"].exists() assert db.table("foo").exists()
assert db["bar"].exists() assert db.table("bar").exists()
assert db["_sqlite_migrations"].exists() assert db.table("_sqlite_migrations").exists()
rows = list(db["_sqlite_migrations"].rows) rows = list(db.table("_sqlite_migrations").rows)
assert len(rows) == 2 assert len(rows) == 2
assert rows[0]["name"] == "foo" assert rows[0]["name"] == "foo"
assert rows[1]["name"] == "bar" assert rows[1]["name"] == "bar"
@ -113,13 +113,13 @@ def test_list_same_migration_names_in_different_sets(capsys):
@applied(name="foo") @applied(name="foo")
def applied_foo(db): def applied_foo(db):
db["applied"].insert({"hello": "world"}) db.table("applied").insert({"hello": "world"})
pending = sqlite_utils.Migrations("pending") pending = sqlite_utils.Migrations("pending")
@pending(name="foo") @pending(name="foo")
def pending_foo(db): def pending_foo(db):
db["pending"].insert({"hello": "world"}) db.table("pending").insert({"hello": "world"})
db = sqlite_utils.Database(memory=True) db = sqlite_utils.Database(memory=True)
applied.apply(db) applied.apply(db)
@ -144,7 +144,7 @@ m = Migrations("hello")
@m() @m()
def foo(db): def foo(db):
db["dogs"].insert({"id": 1, "name": "Cleo"}) db.table("dogs").insert({"id": 1, "name": "Cleo"})
""", """,
"utf-8", "utf-8",
) )
@ -184,9 +184,9 @@ Schema after:
new_migration = """ new_migration = """
@m() @m()
def bar(db): def bar(db):
db["dogs"].add_column("age", int) db.table("dogs").add_column("age", int)
db["dogs"].add_column("weight", float) db.table("dogs").add_column("weight", float)
db["dogs"].transform() db.table("dogs").transform()
""" """
migrations_py.write_text(migrations_py.read_text("utf-8") + new_migration) migrations_py.write_text(migrations_py.read_text("utf-8") + new_migration)
@ -224,8 +224,8 @@ def test_stop_before(two_migrations):
) )
assert result.exit_code == 0 assert result.exit_code == 0
db = sqlite_utils.Database(db_path) db = sqlite_utils.Database(db_path)
assert db["foo"].exists() assert db.table("foo").exists()
assert not db["bar"].exists() assert not db.table("bar").exists()
def test_stop_before_multiple_sets_unqualified(two_migrations): def test_stop_before_multiple_sets_unqualified(two_migrations):
@ -239,7 +239,7 @@ m = Migrations("hello2")
@m() @m()
def foo(db): def foo(db):
db["foo"].insert({"hello": "world"}) db.table("foo").insert({"hello": "world"})
""", """,
"utf-8", "utf-8",
) )
@ -257,7 +257,7 @@ def foo(db):
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
db = sqlite_utils.Database(db_path) db = sqlite_utils.Database(db_path)
assert db.table_names() == ["_sqlite_migrations"] assert db.table_names() == ["_sqlite_migrations"]
assert list(db["_sqlite_migrations"].rows) == [] assert list(db.table("_sqlite_migrations").rows) == []
def test_stop_before_qualified_only_affects_named_set(two_sets_same_migration_name): def test_stop_before_qualified_only_affects_named_set(two_sets_same_migration_name):
@ -275,10 +275,10 @@ def test_stop_before_qualified_only_affects_named_set(two_sets_same_migration_na
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
db = sqlite_utils.Database(db_path) db = sqlite_utils.Database(db_path)
assert db["creatures"].exists() assert db.table("creatures").exists()
assert not db["creature_weights"].exists() assert not db.table("creature_weights").exists()
assert db["sales"].exists() assert db.table("sales").exists()
assert db["sales_weights"].exists() assert db.table("sales_weights").exists()
def test_stop_before_multiple_qualified(two_sets_same_migration_name): def test_stop_before_multiple_qualified(two_sets_same_migration_name):
@ -298,10 +298,10 @@ def test_stop_before_multiple_qualified(two_sets_same_migration_name):
) )
assert result.exit_code == 0, result.output assert result.exit_code == 0, result.output
db = sqlite_utils.Database(db_path) db = sqlite_utils.Database(db_path)
assert db["creatures"].exists() assert db.table("creatures").exists()
assert not db["creature_weights"].exists() assert not db.table("creature_weights").exists()
assert db["sales"].exists() assert db.table("sales").exists()
assert not db["sales_weights"].exists() assert not db.table("sales_weights").exists()
LEGACY_MIGRATIONS = """ LEGACY_MIGRATIONS = """
@ -331,7 +331,7 @@ class LegacyMigrations:
return fn return fn
def ensure_migrations_table(self, db): def ensure_migrations_table(self, db):
db[self.migrations_table].create( db.table(self.migrations_table).create(
{"migration_set": str, "name": str, "applied_at": str}, {"migration_set": str, "name": str, "applied_at": str},
pk=("migration_set", "name"), pk=("migration_set", "name"),
if_not_exists=True, if_not_exists=True,
@ -341,7 +341,7 @@ class LegacyMigrations:
self.ensure_migrations_table(db) self.ensure_migrations_table(db)
return [ return [
_Applied(row["name"], row["applied_at"]) _Applied(row["name"], row["applied_at"])
for row in db[self.migrations_table].rows_where( for row in db.table(self.migrations_table).rows_where(
"migration_set = ?", [self.name] "migration_set = ?", [self.name]
) )
] ]
@ -355,7 +355,7 @@ class LegacyMigrations:
if migration.name == stop_before: if migration.name == stop_before:
return return
migration.fn(db) migration.fn(db)
db[self.migrations_table].insert( db.table(self.migrations_table).insert(
{ {
"migration_set": self.name, "migration_set": self.name,
"name": migration.name, "name": migration.name,
@ -369,11 +369,11 @@ legacy = LegacyMigrations("legacy_set")
@legacy @legacy
def first(db): def first(db):
db["first"].insert({"hello": "world"}) db.table("first").insert({"hello": "world"})
@legacy @legacy
def second(db): def second(db):
db["second"].insert({"hello": "world"}) db.table("second").insert({"hello": "world"})
""" """
@ -446,11 +446,11 @@ def test_list_does_not_upgrade_legacy_migrations_table(two_migrations):
path, _ = two_migrations path, _ = two_migrations
db_path = str(path / "test.db") db_path = str(path / "test.db")
db = sqlite_utils.Database(db_path) db = sqlite_utils.Database(db_path)
db["_sqlite_migrations"].create( db.table("_sqlite_migrations").create(
{"migration_set": str, "name": str, "applied_at": str}, {"migration_set": str, "name": str, "applied_at": str},
pk=("migration_set", "name"), pk=("migration_set", "name"),
) )
db["_sqlite_migrations"].insert( db.table("_sqlite_migrations").insert(
{"migration_set": "hello", "name": "foo", "applied_at": "x"} {"migration_set": "hello", "name": "foo", "applied_at": "x"}
) )
db.close() db.close()
@ -462,7 +462,7 @@ def test_list_does_not_upgrade_legacy_migrations_table(two_migrations):
assert "foo - x" in result.output assert "foo - x" in result.output
# --list must not perform the one-way legacy schema upgrade # --list must not perform the one-way legacy schema upgrade
db2 = sqlite_utils.Database(db_path) db2 = sqlite_utils.Database(db_path)
assert db2["_sqlite_migrations"].pks == ["migration_set", "name"] assert db2.table("_sqlite_migrations").pks == ["migration_set", "name"]
db2.close() db2.close()
@ -485,7 +485,7 @@ def test_stop_before_applied_migration_errors(two_migrations):
assert result.exit_code != 0 assert result.exit_code != 0
assert "already been applied" in result.output assert "already been applied" in result.output
db = sqlite_utils.Database(db_path) db = sqlite_utils.Database(db_path)
assert not db["bar"].exists() assert not db.table("bar").exists()
def test_list_with_legacy_class_is_read_only(tmpdir): def test_list_with_legacy_class_is_read_only(tmpdir):
@ -496,7 +496,7 @@ def test_list_with_legacy_class_is_read_only(tmpdir):
(path / "migrations.py").write_text(LEGACY_MIGRATIONS, "utf-8") (path / "migrations.py").write_text(LEGACY_MIGRATIONS, "utf-8")
db_path = str(path / "test.db") db_path = str(path / "test.db")
db = sqlite_utils.Database(db_path) db = sqlite_utils.Database(db_path)
db["existing"].insert({"id": 1}) db.table("existing").insert({"id": 1})
db.close() db.close()
result = CliRunner().invoke( result = CliRunner().invoke(
sqlite_utils.cli.cli, ["migrate", db_path, str(path), "--list"] sqlite_utils.cli.cli, ["migrate", db_path, str(path), "--list"]

View file

@ -43,4 +43,4 @@ def test_column_affinity(column_def, expected_type):
@pytest.mark.parametrize("column_def,expected_type", EXAMPLES) @pytest.mark.parametrize("column_def,expected_type", EXAMPLES)
def test_columns_dict(fresh_db, column_def, expected_type): def test_columns_dict(fresh_db, column_def, expected_type):
fresh_db.execute(f"create table foo (col {column_def})") fresh_db.execute(f"create table foo (col {column_def})")
assert {"col": expected_type} == fresh_db["foo"].columns_dict assert {"col": expected_type} == fresh_db.table("foo").columns_dict

View file

@ -13,14 +13,14 @@ from sqlite_utils.db import ForeignKey
def test_insert_populates_last_pk_case_insensitively(fresh_db): def test_insert_populates_last_pk_case_insensitively(fresh_db):
books = fresh_db["books"] books = fresh_db.table("books")
books.create({"Id": int, "Title": str}, pk="Id") books.create({"Id": int, "Title": str}, pk="Id")
books.insert({"Id": 1, "Title": "One"}, pk="id") books.insert({"Id": 1, "Title": "One"}, pk="id")
assert books.last_pk == 1 assert books.last_pk == 1
def test_insert_populates_last_pk_compound_pk_case_insensitively(fresh_db): def test_insert_populates_last_pk_compound_pk_case_insensitively(fresh_db):
books = fresh_db["books"] books = fresh_db.table("books")
books.create({"Author": str, "Position": int, "Title": str}) books.create({"Author": str, "Position": int, "Title": str})
books.insert( books.insert(
{"Author": "Sue", "Position": 1, "Title": "One"}, pk=("author", "position") {"Author": "Sue", "Position": 1, "Title": "One"}, pk=("author", "position")
@ -31,7 +31,7 @@ def test_insert_populates_last_pk_compound_pk_case_insensitively(fresh_db):
@pytest.mark.parametrize("use_old_upsert", (False, True)) @pytest.mark.parametrize("use_old_upsert", (False, True))
def test_upsert_pk_case_differs_from_schema(use_old_upsert): def test_upsert_pk_case_differs_from_schema(use_old_upsert):
db = Database(memory=True, use_old_upsert=use_old_upsert) db = Database(memory=True, use_old_upsert=use_old_upsert)
books = db["books"] books = db.table("books")
books.create({"Id": int, "Title": str}, pk="Id") books.create({"Id": int, "Title": str}, pk="Id")
books.insert({"Id": 1, "Title": "One"}) books.insert({"Id": 1, "Title": "One"})
books.upsert({"id": 1, "title": "Won"}, pk="id") books.upsert({"id": 1, "title": "Won"}, pk="id")
@ -43,7 +43,7 @@ def test_upsert_pk_case_differs_from_schema(use_old_upsert):
def test_upsert_record_key_case_differs_from_pk(use_old_upsert): def test_upsert_record_key_case_differs_from_pk(use_old_upsert):
# all_columns comes from the record keys, pk= from the caller # all_columns comes from the record keys, pk= from the caller
db = Database(memory=True, use_old_upsert=use_old_upsert) db = Database(memory=True, use_old_upsert=use_old_upsert)
books = db["books"] books = db.table("books")
books.create({"Id": int, "Title": str}, pk="Id") books.create({"Id": int, "Title": str}, pk="Id")
books.upsert({"ID": 1, "Title": "One"}, pk="id") books.upsert({"ID": 1, "Title": "One"}, pk="id")
assert list(books.rows) == [{"Id": 1, "Title": "One"}] assert list(books.rows) == [{"Id": 1, "Title": "One"}]
@ -52,7 +52,7 @@ def test_upsert_record_key_case_differs_from_pk(use_old_upsert):
def test_upsert_inferred_pk_case_differs_from_record_keys(fresh_db): def test_upsert_inferred_pk_case_differs_from_record_keys(fresh_db):
# pk is inferred from the existing schema as "Id", records use "id" # pk is inferred from the existing schema as "Id", records use "id"
books = fresh_db["books"] books = fresh_db.table("books")
books.create({"Id": int, "Title": str}, pk="Id") books.create({"Id": int, "Title": str}, pk="Id")
books.upsert({"id": 1, "title": "One"}) books.upsert({"id": 1, "title": "One"})
assert list(books.rows) == [{"Id": 1, "Title": "One"}] assert list(books.rows) == [{"Id": 1, "Title": "One"}]
@ -60,7 +60,7 @@ def test_upsert_inferred_pk_case_differs_from_record_keys(fresh_db):
def test_upsert_list_mode_pk_case_insensitive(fresh_db): def test_upsert_list_mode_pk_case_insensitive(fresh_db):
books = fresh_db["books"] books = fresh_db.table("books")
books.create({"Id": int, "Title": str}, pk="Id") books.create({"Id": int, "Title": str}, pk="Id")
books.upsert_all([["id", "title"], [1, "One"]], pk="Id") books.upsert_all([["id", "title"], [1, "One"]], pk="Id")
assert list(books.rows) == [{"Id": 1, "Title": "One"}] assert list(books.rows) == [{"Id": 1, "Title": "One"}]
@ -68,84 +68,84 @@ def test_upsert_list_mode_pk_case_insensitive(fresh_db):
def test_lookup_pk_case_insensitive(fresh_db): def test_lookup_pk_case_insensitive(fresh_db):
fresh_db["species"].create({"ID": int, "Name": str}, pk="ID") fresh_db.table("species").create({"ID": int, "Name": str}, pk="ID")
fresh_db["species"].insert({"ID": 5, "Name": "Palm"}) fresh_db.table("species").insert({"ID": 5, "Name": "Palm"})
fresh_db["species"].create_index(["Name"], unique=True) fresh_db.table("species").create_index(["Name"], unique=True)
assert fresh_db["species"].lookup({"Name": "Palm"}, pk="id") == 5 assert fresh_db.table("species").lookup({"Name": "Palm"}, pk="id") == 5
def test_lookup_does_not_create_redundant_index(fresh_db): def test_lookup_does_not_create_redundant_index(fresh_db):
fresh_db["species"].create({"id": int, "Name": str}, pk="id") fresh_db.table("species").create({"id": int, "Name": str}, pk="id")
fresh_db["species"].create_index(["Name"], unique=True) fresh_db.table("species").create_index(["Name"], unique=True)
fresh_db["species"].lookup({"name": "Palm"}) fresh_db.table("species").lookup({"name": "Palm"})
assert len(fresh_db["species"].indexes) == 1 assert len(fresh_db.table("species").indexes) == 1
def test_create_table_transform_same_columns_different_case(fresh_db): def test_create_table_transform_same_columns_different_case(fresh_db):
fresh_db["t"].create({"Name": str, "Age": int}) fresh_db.table("t").create({"Name": str, "Age": int})
fresh_db["t"].insert({"Name": "Cleo", "Age": 5}) fresh_db.table("t").insert({"Name": "Cleo", "Age": 5})
fresh_db.create_table("t", {"name": str, "age": int}, transform=True) fresh_db.create_table("t", {"name": str, "age": int}, transform=True)
# Schema casing is preserved - SQLite considers these the same columns # Schema casing is preserved - SQLite considers these the same columns
assert fresh_db["t"].columns_dict == {"Name": str, "Age": int} assert fresh_db.table("t").columns_dict == {"Name": str, "Age": int}
assert list(fresh_db["t"].rows) == [{"Name": "Cleo", "Age": 5}] assert list(fresh_db.table("t").rows) == [{"Name": "Cleo", "Age": 5}]
def test_create_table_transform_case_insensitive_with_changes(fresh_db): def test_create_table_transform_case_insensitive_with_changes(fresh_db):
fresh_db["t"].create({"Name": str, "Age": int}) fresh_db.table("t").create({"Name": str, "Age": int})
fresh_db.create_table("t", {"name": str, "age": str, "size": int}, transform=True) fresh_db.create_table("t", {"name": str, "age": str, "size": int}, transform=True)
# age changed type, size added, Name untouched # age changed type, size added, Name untouched
assert fresh_db["t"].columns_dict == {"Name": str, "Age": str, "size": int} assert fresh_db.table("t").columns_dict == {"Name": str, "Age": str, "size": int}
def test_transform_types_case_insensitive(fresh_db): def test_transform_types_case_insensitive(fresh_db):
fresh_db["t"].create({"Name": str, "Age": str}) fresh_db.table("t").create({"Name": str, "Age": str})
fresh_db["t"].transform(types={"age": int}) fresh_db.table("t").transform(types={"age": int})
assert fresh_db["t"].columns_dict == {"Name": str, "Age": int} assert fresh_db.table("t").columns_dict == {"Name": str, "Age": int}
def test_transform_rename_case_insensitive(fresh_db): def test_transform_rename_case_insensitive(fresh_db):
fresh_db["t"].create({"Name": str}) fresh_db.table("t").create({"Name": str})
fresh_db["t"].transform(rename={"name": "title"}) fresh_db.table("t").transform(rename={"name": "title"})
assert fresh_db["t"].columns_dict == {"title": str} assert fresh_db.table("t").columns_dict == {"title": str}
def test_transform_drop_case_insensitive(fresh_db): def test_transform_drop_case_insensitive(fresh_db):
fresh_db["t"].create({"Name": str, "Age": int}) fresh_db.table("t").create({"Name": str, "Age": int})
fresh_db["t"].transform(drop=["name"]) fresh_db.table("t").transform(drop=["name"])
assert fresh_db["t"].columns_dict == {"Age": int} assert fresh_db.table("t").columns_dict == {"Age": int}
def test_transform_not_null_and_defaults_case_insensitive(fresh_db): def test_transform_not_null_and_defaults_case_insensitive(fresh_db):
fresh_db["t"].create({"Name": str, "Age": int}) fresh_db.table("t").create({"Name": str, "Age": int})
fresh_db["t"].transform(not_null={"name"}, defaults={"age": 3}) fresh_db.table("t").transform(not_null={"name"}, defaults={"age": 3})
columns = {c.name: c for c in fresh_db["t"].columns} columns = {c.name: c for c in fresh_db.table("t").columns}
assert columns["Name"].notnull assert columns["Name"].notnull
assert fresh_db["t"].default_values == {"Age": 3} assert fresh_db.table("t").default_values == {"Age": 3}
def test_transform_pk_case_insensitive(fresh_db): def test_transform_pk_case_insensitive(fresh_db):
fresh_db["t"].create({"Id": int, "Name": str}) fresh_db.table("t").create({"Id": int, "Name": str})
fresh_db["t"].transform(pk="id") fresh_db.table("t").transform(pk="id")
assert fresh_db["t"].pks == ["Id"] assert fresh_db.table("t").pks == ["Id"]
assert fresh_db["t"].columns_dict == {"Id": int, "Name": str} assert fresh_db.table("t").columns_dict == {"Id": int, "Name": str}
def test_transform_drop_foreign_keys_case_insensitive(fresh_db): def test_transform_drop_foreign_keys_case_insensitive(fresh_db):
fresh_db["parent"].create({"Id": int}, pk="Id") fresh_db.table("parent").create({"Id": int}, pk="Id")
fresh_db["child"].create( fresh_db.table("child").create(
{"id": int, "Parent_ID": int}, {"id": int, "Parent_ID": int},
pk="id", pk="id",
foreign_keys=[("Parent_ID", "parent", "Id")], foreign_keys=[("Parent_ID", "parent", "Id")],
) )
fresh_db["child"].transform(drop_foreign_keys=["parent_id"]) fresh_db.table("child").transform(drop_foreign_keys=["parent_id"])
assert fresh_db["child"].foreign_keys == [] assert fresh_db.table("child").foreign_keys == []
def test_add_foreign_key_case_insensitive(fresh_db): def test_add_foreign_key_case_insensitive(fresh_db):
fresh_db["parent"].create({"Id": int}, pk="Id") fresh_db.table("parent").create({"Id": int}, pk="Id")
fresh_db["child"].create({"id": int, "Parent_ID": int}, pk="id") fresh_db.table("child").create({"id": int, "Parent_ID": int}, pk="id")
fresh_db["child"].add_foreign_key("parent_id", "parent", "id") fresh_db.table("child").add_foreign_key("parent_id", "parent", "id")
fks = fresh_db["child"].foreign_keys fks = fresh_db.table("child").foreign_keys
assert len(fks) == 1 assert len(fks) == 1
# The foreign key should use the schema casing of the columns # The foreign key should use the schema casing of the columns
assert fks[0].column == "Parent_ID" assert fks[0].column == "Parent_ID"
@ -153,79 +153,83 @@ def test_add_foreign_key_case_insensitive(fresh_db):
def test_add_foreign_keys_case_insensitive(fresh_db): def test_add_foreign_keys_case_insensitive(fresh_db):
fresh_db["parent"].create({"Id": int}, pk="Id") fresh_db.table("parent").create({"Id": int}, pk="Id")
fresh_db["child"].create({"id": int, "Parent_ID": int}, pk="id") fresh_db.table("child").create({"id": int, "Parent_ID": int}, pk="id")
fresh_db.add_foreign_keys([("child", "parent_id", "parent", "id")]) fresh_db.add_foreign_keys([("child", "parent_id", "parent", "id")])
fks = fresh_db["child"].foreign_keys fks = fresh_db.table("child").foreign_keys
assert len(fks) == 1 assert len(fks) == 1
assert fks[0].column == "Parent_ID" assert fks[0].column == "Parent_ID"
assert fks[0].other_column == "Id" assert fks[0].other_column == "Id"
def test_add_foreign_key_detects_existing_case_insensitively(fresh_db): def test_add_foreign_key_detects_existing_case_insensitively(fresh_db):
fresh_db["parent"].create({"Id": int}, pk="Id") fresh_db.table("parent").create({"Id": int}, pk="Id")
fresh_db["child"].create( fresh_db.table("child").create(
{"id": int, "Parent_ID": int}, {"id": int, "Parent_ID": int},
pk="id", pk="id",
foreign_keys=[("Parent_ID", "parent", "Id")], foreign_keys=[("Parent_ID", "parent", "Id")],
) )
# ignore=True should treat this as already existing, not add a duplicate # ignore=True should treat this as already existing, not add a duplicate
fresh_db["child"].add_foreign_key("parent_id", "parent", "id", ignore=True) fresh_db.table("child").add_foreign_key("parent_id", "parent", "id", ignore=True)
assert len(fresh_db["child"].foreign_keys) == 1 assert len(fresh_db.table("child").foreign_keys) == 1
def test_add_column_fk_col_case_insensitive(fresh_db): def test_add_column_fk_col_case_insensitive(fresh_db):
fresh_db["parent"].create({"Id": int}, pk="Id") fresh_db.table("parent").create({"Id": int}, pk="Id")
fresh_db["child"].create({"id": int}, pk="id") fresh_db.table("child").create({"id": int}, pk="id")
fresh_db["child"].add_column("parent_id", int, fk="parent", fk_col="id") fresh_db.table("child").add_column("parent_id", int, fk="parent", fk_col="id")
fks = fresh_db["child"].foreign_keys fks = fresh_db.table("child").foreign_keys
assert len(fks) == 1 assert len(fks) == 1
assert fks[0].other_column == "Id" assert fks[0].other_column == "Id"
def test_extract_case_insensitive(fresh_db): def test_extract_case_insensitive(fresh_db):
fresh_db["trees"].insert({"id": 1, "Species": "Palm"}, pk="id") fresh_db.table("trees").insert({"id": 1, "Species": "Palm"}, pk="id")
fresh_db["trees"].extract("species") fresh_db.table("trees").extract("species")
assert fresh_db["trees"].columns_dict == {"id": int, "Species_id": int} assert fresh_db.table("trees").columns_dict == {"id": int, "Species_id": int}
assert list(fresh_db["Species"].rows) == [{"id": 1, "Species": "Palm"}] assert list(fresh_db.table("Species").rows) == [{"id": 1, "Species": "Palm"}]
def test_convert_multi_case_insensitive(fresh_db): def test_convert_multi_case_insensitive(fresh_db):
fresh_db["t"].insert({"id": 1, "Name": "Cleo"}, pk="id") fresh_db.table("t").insert({"id": 1, "Name": "Cleo"}, pk="id")
fresh_db["t"].convert("name", lambda v: {"upper": v.upper()}, multi=True) fresh_db.table("t").convert("name", lambda v: {"upper": v.upper()}, multi=True)
assert list(fresh_db["t"].rows) == [{"id": 1, "Name": "Cleo", "upper": "CLEO"}] assert list(fresh_db.table("t").rows) == [
{"id": 1, "Name": "Cleo", "upper": "CLEO"}
]
def test_convert_output_case_insensitive(fresh_db): def test_convert_output_case_insensitive(fresh_db):
fresh_db["t"].insert({"id": 1, "Name": "Cleo", "Upper": None}, pk="id") fresh_db.table("t").insert({"id": 1, "Name": "Cleo", "Upper": None}, pk="id")
fresh_db["t"].convert("name", lambda v: v.upper(), output="upper") fresh_db.table("t").convert("name", lambda v: v.upper(), output="upper")
assert list(fresh_db["t"].rows) == [{"id": 1, "Name": "Cleo", "Upper": "CLEO"}] assert list(fresh_db.table("t").rows) == [
{"id": 1, "Name": "Cleo", "Upper": "CLEO"}
]
def test_create_table_sql_pk_case_insensitive(fresh_db): def test_create_table_sql_pk_case_insensitive(fresh_db):
fresh_db["t"].create({"Id": int, "Name": str}, pk="id") fresh_db.table("t").create({"Id": int, "Name": str}, pk="id")
# Should not have created an extra lowercase "id" column # Should not have created an extra lowercase "id" column
assert fresh_db["t"].columns_dict == {"Id": int, "Name": str} assert fresh_db.table("t").columns_dict == {"Id": int, "Name": str}
assert fresh_db["t"].pks == ["Id"] assert fresh_db.table("t").pks == ["Id"]
def test_create_table_not_null_and_defaults_case_insensitive(fresh_db): def test_create_table_not_null_and_defaults_case_insensitive(fresh_db):
fresh_db["t"].create( fresh_db.table("t").create(
{"Name": str, "Age": int}, not_null={"name"}, defaults={"age": 1} {"Name": str, "Age": int}, not_null={"name"}, defaults={"age": 1}
) )
columns = {c.name: c for c in fresh_db["t"].columns} columns = {c.name: c for c in fresh_db.table("t").columns}
assert columns["Name"].notnull assert columns["Name"].notnull
assert fresh_db["t"].default_values == {"Age": 1} assert fresh_db.table("t").default_values == {"Age": 1}
def test_create_table_foreign_keys_case_insensitive(fresh_db): def test_create_table_foreign_keys_case_insensitive(fresh_db):
fresh_db["parent"].create({"Id": int}, pk="Id") fresh_db.table("parent").create({"Id": int}, pk="Id")
fresh_db["child"].create( fresh_db.table("child").create(
{"id": int, "Parent_ID": int}, {"id": int, "Parent_ID": int},
pk="id", pk="id",
foreign_keys=[("parent_id", "parent", "id")], foreign_keys=[("parent_id", "parent", "id")],
) )
fks = fresh_db["child"].foreign_keys fks = fresh_db.table("child").foreign_keys
assert fks == [ assert fks == [
ForeignKey( ForeignKey(
table="child", column="Parent_ID", other_table="parent", other_column="Id" table="child", column="Parent_ID", other_table="parent", other_column="Id"

View file

@ -20,8 +20,8 @@ def test_recursive_triggers_off():
def test_memory_name(): def test_memory_name():
db1 = Database(memory_name="shared") db1 = Database(memory_name="shared")
db2 = Database(memory_name="shared") db2 = Database(memory_name="shared")
db1["dogs"].insert({"name": "Cleo"}) db1.table("dogs").insert({"name": "Cleo"})
assert list(db2["dogs"].rows) == [{"name": "Cleo"}] assert list(db2.table("dogs").rows) == [{"name": "Cleo"}]
def test_sqlite_version(): def test_sqlite_version():
@ -36,7 +36,7 @@ def test_sqlite_version():
def test_database_context_manager(tmpdir): def test_database_context_manager(tmpdir):
path = str(tmpdir / "test.db") path = str(tmpdir / "test.db")
with Database(path) as db: with Database(path) as db:
db["t"].insert({"id": 1}) db.table("t").insert({"id": 1})
# Raw writes commit automatically too # Raw writes commit automatically too
db.execute("insert into t (id) values (2)") db.execute("insert into t (id) values (2)")
# An explicitly opened transaction left uncommitted on purpose: # An explicitly opened transaction left uncommitted on purpose:
@ -47,7 +47,7 @@ def test_database_context_manager(tmpdir):
db.execute("select 1") db.execute("select 1")
# ... and the open explicit transaction was rolled back, not committed # ... and the open explicit transaction was rolled back, not committed
db2 = Database(path) db2 = Database(path)
assert [r["id"] for r in db2["t"].rows] == [1, 2] assert [r["id"] for r in db2.table("t").rows] == [1, 2]
db2.close() db2.close()
@ -86,8 +86,8 @@ def test_legacy_transaction_control_connection_is_accepted(tmpdir):
str(tmpdir / "test.db"), autocommit=sqlite3.LEGACY_TRANSACTION_CONTROL str(tmpdir / "test.db"), autocommit=sqlite3.LEGACY_TRANSACTION_CONTROL
) )
db = Database(conn) db = Database(conn)
db["t"].insert({"id": 1}, pk="id") db.table("t").insert({"id": 1}, pk="id")
assert [r["id"] for r in db["t"].rows] == [1] assert [r["id"] for r in db.table("t").rows] == [1]
db.close() db.close()

View file

@ -1,17 +1,17 @@
def test_insert_conversion(fresh_db): def test_insert_conversion(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert({"foo": "bar"}, conversions={"foo": "upper(?)"}) table.insert({"foo": "bar"}, conversions={"foo": "upper(?)"})
assert [{"foo": "BAR"}] == list(table.rows) assert [{"foo": "BAR"}] == list(table.rows)
def test_insert_all_conversion(fresh_db): def test_insert_all_conversion(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert_all([{"foo": "bar"}], conversions={"foo": "upper(?)"}) table.insert_all([{"foo": "bar"}], conversions={"foo": "upper(?)"})
assert [{"foo": "BAR"}] == list(table.rows) assert [{"foo": "BAR"}] == list(table.rows)
def test_upsert_conversion(fresh_db): def test_upsert_conversion(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.upsert({"id": 1, "foo": "bar"}, pk="id", conversions={"foo": "upper(?)"}) table.upsert({"id": 1, "foo": "bar"}, pk="id", conversions={"foo": "upper(?)"})
assert [{"id": 1, "foo": "BAR"}] == list(table.rows) assert [{"id": 1, "foo": "BAR"}] == list(table.rows)
table.upsert( table.upsert(
@ -21,7 +21,7 @@ def test_upsert_conversion(fresh_db):
def test_upsert_all_conversion(fresh_db): def test_upsert_all_conversion(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.upsert_all( table.upsert_all(
[{"id": 1, "foo": "bar"}], pk="id", conversions={"foo": "upper(?)"} [{"id": 1, "foo": "bar"}], pk="id", conversions={"foo": "upper(?)"}
) )
@ -29,7 +29,7 @@ def test_upsert_all_conversion(fresh_db):
def test_update_conversion(fresh_db): def test_update_conversion(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert({"id": 5, "foo": "bar"}, pk="id") table.insert({"id": 5, "foo": "bar"}, pk="id")
table.update(5, {"foo": "baz"}, conversions={"foo": "upper(?)"}) table.update(5, {"foo": "baz"}, conversions={"foo": "upper(?)"})
assert [{"id": 5, "foo": "BAZ"}] == list(table.rows) assert [{"id": 5, "foo": "BAZ"}] == list(table.rows)

View file

@ -27,7 +27,7 @@ from sqlite_utils.db import BadMultiValues
), ),
) )
def test_convert(fresh_db, columns, fn, expected): def test_convert(fresh_db, columns, fn, expected):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert({"title": "Mixed Case", "abstract": "Abstract"}) table.insert({"title": "Mixed Case", "abstract": "Abstract"})
table.convert(columns, fn) table.convert(columns, fn)
assert list(table.rows) == [expected] assert list(table.rows) == [expected]
@ -37,7 +37,7 @@ def test_convert(fresh_db, columns, fn, expected):
"where,where_args", (("id > 1", None), ("id > :id", {"id": 1}), ("id > ?", [1])) "where,where_args", (("id > 1", None), ("id > :id", {"id": 1}), ("id > ?", [1]))
) )
def test_convert_where(fresh_db, where, where_args): def test_convert_where(fresh_db, where, where_args):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert_all( table.insert_all(
[ [
{"id": 1, "title": "One"}, {"id": 1, "title": "One"},
@ -53,7 +53,7 @@ def test_convert_where(fresh_db, where, where_args):
def test_convert_handles_falsey_values(fresh_db): def test_convert_handles_falsey_values(fresh_db):
# Falsey values like 0 should be converted (issue #527) # Falsey values like 0 should be converted (issue #527)
table = fresh_db["table"] table = fresh_db.table("table")
table.insert_all([{"x": 0}, {"x": 1}]) table.insert_all([{"x": 0}, {"x": 1}])
assert table.get(1)["x"] == 0 assert table.get(1)["x"] == 0
assert table.get(2)["x"] == 1 assert table.get(2)["x"] == 1
@ -70,14 +70,14 @@ def test_convert_handles_falsey_values(fresh_db):
), ),
) )
def test_convert_output(fresh_db, drop, expected): def test_convert_output(fresh_db, drop, expected):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert({"title": "Mixed Case"}) table.insert({"title": "Mixed Case"})
table.convert("title", lambda v: v.upper(), output="other", drop=drop) table.convert("title", lambda v: v.upper(), output="other", drop=drop)
assert list(table.rows) == [expected] assert list(table.rows) == [expected]
def test_convert_output_multiple_column_error(fresh_db): def test_convert_output_multiple_column_error(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
with pytest.raises(ValueError) as excinfo: with pytest.raises(ValueError) as excinfo:
table.convert(["title", "other"], lambda v: v, output="out") table.convert(["title", "other"], lambda v: v, output="out")
assert "output= can only be used with a single column" in str(excinfo.value) assert "output= can only be used with a single column" in str(excinfo.value)
@ -91,14 +91,14 @@ def test_convert_output_multiple_column_error(fresh_db):
), ),
) )
def test_convert_output_type(fresh_db, type, expected): def test_convert_output_type(fresh_db, type, expected):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert({"number": "123"}) table.insert({"number": "123"})
table.convert("number", lambda v: v, output="other", output_type=type, drop=True) table.convert("number", lambda v: v, output="other", output_type=type, drop=True)
assert list(table.rows) == [expected] assert list(table.rows) == [expected]
def test_convert_multi(fresh_db): def test_convert_multi(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert({"title": "Mixed Case"}) table.insert({"title": "Mixed Case"})
table.convert( table.convert(
"title", "title",
@ -123,7 +123,7 @@ def test_convert_multi(fresh_db):
def test_convert_multi_where(fresh_db): def test_convert_multi_where(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert_all( table.insert_all(
[ [
{"id": 1, "title": "One"}, {"id": 1, "title": "One"},
@ -145,14 +145,14 @@ def test_convert_multi_where(fresh_db):
def test_convert_multi_exception(fresh_db): def test_convert_multi_exception(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert({"title": "Mixed Case"}) table.insert({"title": "Mixed Case"})
with pytest.raises(BadMultiValues): with pytest.raises(BadMultiValues):
table.convert("title", lambda v: v.upper(), multi=True) table.convert("title", lambda v: v.upper(), multi=True)
def test_convert_repeated(fresh_db): def test_convert_repeated(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
col = "num" col = "num"
table.insert({col: 1}) table.insert({col: 1})
table.convert(col, lambda x: x * 2) table.convert(col, lambda x: x * 2)

File diff suppressed because it is too large Load diff

View file

@ -32,9 +32,9 @@ EXAMPLES = [
@pytest.mark.parametrize("column_def,initial_value,expected_value", EXAMPLES) @pytest.mark.parametrize("column_def,initial_value,expected_value", EXAMPLES)
def test_quote_default_value(fresh_db, column_def, initial_value, expected_value): def test_quote_default_value(fresh_db, column_def, initial_value, expected_value):
fresh_db.execute(f"create table foo (col {column_def})") fresh_db.execute(f"create table foo (col {column_def})")
assert initial_value == fresh_db["foo"].columns[0].default_value assert initial_value == fresh_db.table("foo").columns[0].default_value
assert expected_value == fresh_db.quote_default_value( assert expected_value == fresh_db.quote_default_value(
fresh_db["foo"].columns[0].default_value fresh_db.table("foo").columns[0].default_value
) )
@ -48,7 +48,7 @@ def test_insert_empty_record_uses_default_values(fresh_db):
) )
""") """)
table = fresh_db["has_defaults"] table = fresh_db.table("has_defaults")
table.insert({}) table.insert({})
rows = list(table.rows) rows = list(table.rows)

View file

@ -2,7 +2,7 @@ import sqlite_utils
def test_delete_rowid_table(fresh_db): def test_delete_rowid_table(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert({"foo": 1}) table.insert({"foo": 1})
rowid = table.insert({"foo": 2}).last_pk rowid = table.insert({"foo": 2}).last_pk
table.delete(rowid) table.delete(rowid)
@ -10,7 +10,7 @@ def test_delete_rowid_table(fresh_db):
def test_delete_pk_table(fresh_db): def test_delete_pk_table(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert({"id": 1}, pk="id") table.insert({"id": 1}, pk="id")
table.insert({"id": 2}, pk="id") table.insert({"id": 2}, pk="id")
table.delete(1) table.delete(1)
@ -18,7 +18,7 @@ def test_delete_pk_table(fresh_db):
def test_delete_where(fresh_db): def test_delete_where(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
for i in range(1, 11): for i in range(1, 11):
table.insert({"id": i}, pk="id") table.insert({"id": i}, pk="id")
assert table.count == 10 assert table.count == 10
@ -27,7 +27,7 @@ def test_delete_where(fresh_db):
def test_delete_where_all(fresh_db): def test_delete_where_all(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
for i in range(1, 11): for i in range(1, 11):
table.insert({"id": i}, pk="id") table.insert({"id": i}, pk="id")
assert table.count == 10 assert table.count == 10
@ -38,27 +38,27 @@ def test_delete_where_all(fresh_db):
def test_delete_where_commits(tmpdir): def test_delete_where_commits(tmpdir):
path = str(tmpdir / "test.db") path = str(tmpdir / "test.db")
db = sqlite_utils.Database(path) db = sqlite_utils.Database(path)
db["table"].insert_all([{"id": i} for i in range(5)], pk="id") db.table("table").insert_all([{"id": i} for i in range(5)], pk="id")
db["table"].delete_where("id > ?", [2]) db.table("table").delete_where("id > ?", [2])
# The connection must not be left inside an open transaction, # The connection must not be left inside an open transaction,
# otherwise subsequent atomic() blocks never commit either # otherwise subsequent atomic() blocks never commit either
assert not db.conn.in_transaction assert not db.conn.in_transaction
db["table"].insert({"id": 100}) db.table("table").insert({"id": 100})
db.close() db.close()
db2 = sqlite_utils.Database(path) db2 = sqlite_utils.Database(path)
assert [r["id"] for r in db2["table"].rows] == [0, 1, 2, 100] assert [r["id"] for r in db2.table("table").rows] == [0, 1, 2, 100]
db2.close() db2.close()
def test_delete_where_analyze(fresh_db): def test_delete_where_analyze(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert_all(({"id": i, "i": i} for i in range(10)), pk="id") table.insert_all(({"id": i, "i": i} for i in range(10)), pk="id")
table.create_index(["i"], analyze=True) table.create_index(["i"], analyze=True)
assert "sqlite_stat1" in fresh_db.table_names() assert "sqlite_stat1" in fresh_db.table_names()
assert list(fresh_db["sqlite_stat1"].rows) == [ assert list(fresh_db.table("sqlite_stat1").rows) == [
{"tbl": "table", "idx": "idx_table_i", "stat": "10 1"} {"tbl": "table", "idx": "idx_table_i", "stat": "10 1"}
] ]
table.delete_where("id > ?", [5], analyze=True) table.delete_where("id > ?", [5], analyze=True)
assert list(fresh_db["sqlite_stat1"].rows) == [ assert list(fresh_db.table("sqlite_stat1").rows) == [
{"tbl": "table", "idx": "idx_table_i", "stat": "6 1"} {"tbl": "table", "idx": "idx_table_i", "stat": "6 1"}
] ]

View file

@ -22,7 +22,7 @@ def test_duplicate(fresh_db):
"bool_col": True, "bool_col": True,
"datetime_col": str(dt), "datetime_col": str(dt),
} }
table1 = fresh_db["table1"] table1 = fresh_db.table("table1")
row_id = table1.insert(data).last_rowid row_id = table1.insert(data).last_rowid
# Duplicate table: # Duplicate table:
table2 = table1.duplicate("table2") table2 = table1.duplicate("table2")
@ -40,4 +40,4 @@ def test_duplicate(fresh_db):
def test_duplicate_fails_if_table_does_not_exist(fresh_db): def test_duplicate_fails_if_table_does_not_exist(fresh_db):
with pytest.raises(NoTable): with pytest.raises(NoTable):
fresh_db["not_a_table"].duplicate("duplicated") fresh_db.table("not_a_table").duplicate("duplicated")

View file

@ -5,7 +5,7 @@ from sqlite_utils import Database, cli
def test_enable_counts_specific_table(fresh_db): def test_enable_counts_specific_table(fresh_db):
foo = fresh_db["foo"] foo = fresh_db.table("foo")
assert fresh_db.table_names() == [] assert fresh_db.table_names() == []
for i in range(10): for i in range(10):
foo.insert({"name": f"item {i}"}) foo.insert({"name": f"item {i}"})
@ -41,24 +41,24 @@ def test_enable_counts_specific_table(fresh_db):
), ),
} }
assert fresh_db.table_names() == ["foo", "_counts"] assert fresh_db.table_names() == ["foo", "_counts"]
assert list(fresh_db["_counts"].rows) == [{"count": 10, "table": "foo"}] assert list(fresh_db.table("_counts").rows) == [{"count": 10, "table": "foo"}]
# Add some items to test the triggers # Add some items to test the triggers
for i in range(5): for i in range(5):
foo.insert({"name": f"item {10 + i}"}) foo.insert({"name": f"item {10 + i}"})
assert foo.count == 15 assert foo.count == 15
assert list(fresh_db["_counts"].rows) == [{"count": 15, "table": "foo"}] assert list(fresh_db.table("_counts").rows) == [{"count": 15, "table": "foo"}]
# Delete some items # Delete some items
foo.delete_where("rowid < 7") foo.delete_where("rowid < 7")
assert foo.count == 9 assert foo.count == 9
assert list(fresh_db["_counts"].rows) == [{"count": 9, "table": "foo"}] assert list(fresh_db.table("_counts").rows) == [{"count": 9, "table": "foo"}]
foo.delete_where() foo.delete_where()
assert foo.count == 0 assert foo.count == 0
assert list(fresh_db["_counts"].rows) == [{"count": 0, "table": "foo"}] assert list(fresh_db.table("_counts").rows) == [{"count": 0, "table": "foo"}]
def test_enable_counts_all_tables(fresh_db): def test_enable_counts_all_tables(fresh_db):
foo = fresh_db["foo"] foo = fresh_db.table("foo")
bar = fresh_db["bar"] bar = fresh_db.table("bar")
foo.insert({"name": "Cleo"}) foo.insert({"name": "Cleo"})
bar.insert({"name": "Cleo"}) bar.insert({"name": "Cleo"})
foo.enable_fts(["name"]) foo.enable_fts(["name"])
@ -73,7 +73,7 @@ def test_enable_counts_all_tables(fresh_db):
"foo_fts_config", "foo_fts_config",
"_counts", "_counts",
} }
assert list(fresh_db["_counts"].rows) == [ assert list(fresh_db.table("_counts").rows) == [
{"count": 1, "table": "foo"}, {"count": 1, "table": "foo"},
{"count": 1, "table": "bar"}, {"count": 1, "table": "bar"},
{"count": 3, "table": "foo_fts_data"}, {"count": 3, "table": "foo_fts_data"},
@ -87,10 +87,10 @@ def test_enable_counts_all_tables(fresh_db):
def counts_db_path(tmpdir): def counts_db_path(tmpdir):
path = str(tmpdir / "test.db") path = str(tmpdir / "test.db")
db = Database(path) db = Database(path)
db["foo"].insert({"name": "bar"}) db.table("foo").insert({"name": "bar"})
db["bar"].insert({"name": "bar"}) db.table("bar").insert({"name": "bar"})
db["bar"].insert({"name": "bar"}) db.table("bar").insert({"name": "bar"})
db["baz"].insert({"name": "bar"}) db.table("baz").insert({"name": "bar"})
return path return path
@ -163,25 +163,25 @@ def test_uses_counts_after_enable_counts(counts_db_path):
def test_reset_counts(counts_db_path): def test_reset_counts(counts_db_path):
db = Database(counts_db_path) db = Database(counts_db_path)
db["foo"].enable_counts() db.table("foo").enable_counts()
db["bar"].enable_counts() db.table("bar").enable_counts()
assert db.cached_counts() == {"foo": 1, "bar": 2} assert db.cached_counts() == {"foo": 1, "bar": 2}
# Corrupt the value # Corrupt the value
db["_counts"].update("foo", {"count": 3}) db.table("_counts").update("foo", {"count": 3})
assert db.cached_counts() == {"foo": 3, "bar": 2} assert db.cached_counts() == {"foo": 3, "bar": 2}
assert db["foo"].count == 3 assert db.table("foo").count == 3
# Reset them # Reset them
db.reset_counts() db.reset_counts()
assert db.cached_counts() == {"foo": 1, "bar": 2} assert db.cached_counts() == {"foo": 1, "bar": 2}
assert db["foo"].count == 1 assert db.table("foo").count == 1
def test_reset_counts_cli(counts_db_path): def test_reset_counts_cli(counts_db_path):
db = Database(counts_db_path) db = Database(counts_db_path)
db["foo"].enable_counts() db.table("foo").enable_counts()
db["bar"].enable_counts() db.table("bar").enable_counts()
assert db.cached_counts() == {"foo": 1, "bar": 2} assert db.cached_counts() == {"foo": 1, "bar": 2}
db["_counts"].update("foo", {"count": 3}) db.table("_counts").update("foo", {"count": 3})
result = CliRunner().invoke(cli.cli, ["reset-counts", counts_db_path]) result = CliRunner().invoke(cli.cli, ["reset-counts", counts_db_path])
assert result.exit_code == 0 assert result.exit_code == 0
assert db.cached_counts() == {"foo": 1, "bar": 2} assert db.cached_counts() == {"foo": 1, "bar": 2}

View file

@ -11,7 +11,7 @@ def test_extract_single_column(fresh_db, table, fk_column):
expected_table = table or "species" expected_table = table or "species"
expected_fk = fk_column or f"{expected_table}_id" expected_fk = fk_column or f"{expected_table}_id"
iter_species = itertools.cycle(["Palm", "Spruce", "Mangrove", "Oak"]) iter_species = itertools.cycle(["Palm", "Spruce", "Mangrove", "Oak"])
fresh_db["tree"].insert_all( fresh_db.table("tree").insert_all(
( (
{ {
"id": i, "id": i,
@ -23,8 +23,8 @@ def test_extract_single_column(fresh_db, table, fk_column):
), ),
pk="id", pk="id",
) )
fresh_db["tree"].extract("species", table=table, fk_column=fk_column) fresh_db.table("tree").extract("species", table=table, fk_column=fk_column)
assert fresh_db["tree"].schema == ( assert fresh_db.table("tree").schema == (
'CREATE TABLE "tree" (\n' 'CREATE TABLE "tree" (\n'
' "id" INTEGER PRIMARY KEY,\n' ' "id" INTEGER PRIMARY KEY,\n'
' "name" TEXT,\n' ' "name" TEXT,\n'
@ -32,18 +32,18 @@ def test_extract_single_column(fresh_db, table, fk_column):
+ ' "end" INTEGER\n' + ' "end" INTEGER\n'
+ ")" + ")"
) )
assert fresh_db[expected_table].schema == ( assert fresh_db.table(expected_table).schema == (
f'CREATE TABLE "{expected_table}" (\n' + ' "id" INTEGER PRIMARY KEY,\n' f'CREATE TABLE "{expected_table}" (\n' + ' "id" INTEGER PRIMARY KEY,\n'
' "species" TEXT\n' ' "species" TEXT\n'
")" ")"
) )
assert list(fresh_db[expected_table].rows) == [ assert list(fresh_db.table(expected_table).rows) == [
{"id": 1, "species": "Palm"}, {"id": 1, "species": "Palm"},
{"id": 2, "species": "Spruce"}, {"id": 2, "species": "Spruce"},
{"id": 3, "species": "Mangrove"}, {"id": 3, "species": "Mangrove"},
{"id": 4, "species": "Oak"}, {"id": 4, "species": "Oak"},
] ]
assert list(itertools.islice(fresh_db["tree"].rows, 0, 4)) == [ assert list(itertools.islice(fresh_db.table("tree").rows, 0, 4)) == [
{"id": 1, "name": "Tree 1", expected_fk: 1, "end": 1}, {"id": 1, "name": "Tree 1", expected_fk: 1, "end": 1},
{"id": 2, "name": "Tree 2", expected_fk: 2, "end": 1}, {"id": 2, "name": "Tree 2", expected_fk: 2, "end": 1},
{"id": 3, "name": "Tree 3", expected_fk: 3, "end": 1}, {"id": 3, "name": "Tree 3", expected_fk: 3, "end": 1},
@ -54,7 +54,7 @@ def test_extract_single_column(fresh_db, table, fk_column):
def test_extract_multiple_columns_with_rename(fresh_db): def test_extract_multiple_columns_with_rename(fresh_db):
iter_common = itertools.cycle(["Palm", "Spruce", "Mangrove", "Oak"]) iter_common = itertools.cycle(["Palm", "Spruce", "Mangrove", "Oak"])
iter_latin = itertools.cycle(["Arecaceae", "Picea", "Rhizophora", "Quercus"]) iter_latin = itertools.cycle(["Arecaceae", "Picea", "Rhizophora", "Quercus"])
fresh_db["tree"].insert_all( fresh_db.table("tree").insert_all(
( (
{ {
"id": i, "id": i,
@ -67,30 +67,30 @@ def test_extract_multiple_columns_with_rename(fresh_db):
pk="id", pk="id",
) )
fresh_db["tree"].extract( fresh_db.table("tree").extract(
["common_name", "latin_name"], rename={"common_name": "name"} ["common_name", "latin_name"], rename={"common_name": "name"}
) )
assert fresh_db["tree"].schema == ( assert fresh_db.table("tree").schema == (
'CREATE TABLE "tree" (\n' 'CREATE TABLE "tree" (\n'
' "id" INTEGER PRIMARY KEY,\n' ' "id" INTEGER PRIMARY KEY,\n'
' "name" TEXT,\n' ' "name" TEXT,\n'
' "common_name_latin_name_id" INTEGER REFERENCES "common_name_latin_name"("id")\n' ' "common_name_latin_name_id" INTEGER REFERENCES "common_name_latin_name"("id")\n'
")" ")"
) )
assert fresh_db["common_name_latin_name"].schema == ( assert fresh_db.table("common_name_latin_name").schema == (
'CREATE TABLE "common_name_latin_name" (\n' 'CREATE TABLE "common_name_latin_name" (\n'
' "id" INTEGER PRIMARY KEY,\n' ' "id" INTEGER PRIMARY KEY,\n'
' "name" TEXT,\n' ' "name" TEXT,\n'
' "latin_name" TEXT\n' ' "latin_name" TEXT\n'
")" ")"
) )
assert list(fresh_db["common_name_latin_name"].rows) == [ assert list(fresh_db.table("common_name_latin_name").rows) == [
{"name": "Palm", "id": 1, "latin_name": "Arecaceae"}, {"name": "Palm", "id": 1, "latin_name": "Arecaceae"},
{"name": "Spruce", "id": 2, "latin_name": "Picea"}, {"name": "Spruce", "id": 2, "latin_name": "Picea"},
{"name": "Mangrove", "id": 3, "latin_name": "Rhizophora"}, {"name": "Mangrove", "id": 3, "latin_name": "Rhizophora"},
{"name": "Oak", "id": 4, "latin_name": "Quercus"}, {"name": "Oak", "id": 4, "latin_name": "Quercus"},
] ]
assert list(itertools.islice(fresh_db["tree"].rows, 0, 4)) == [ assert list(itertools.islice(fresh_db.table("tree").rows, 0, 4)) == [
{"id": 1, "name": "Tree 1", "common_name_latin_name_id": 1}, {"id": 1, "name": "Tree 1", "common_name_latin_name_id": 1},
{"id": 2, "name": "Tree 2", "common_name_latin_name_id": 2}, {"id": 2, "name": "Tree 2", "common_name_latin_name_id": 2},
{"id": 3, "name": "Tree 3", "common_name_latin_name_id": 3}, {"id": 3, "name": "Tree 3", "common_name_latin_name_id": 3},
@ -99,7 +99,7 @@ def test_extract_multiple_columns_with_rename(fresh_db):
def test_extract_invalid_columns(fresh_db): def test_extract_invalid_columns(fresh_db):
fresh_db["tree"].insert( fresh_db.table("tree").insert(
{ {
"id": 1, "id": 1,
"name": "Tree 1", "name": "Tree 1",
@ -109,19 +109,19 @@ def test_extract_invalid_columns(fresh_db):
pk="id", pk="id",
) )
with pytest.raises(InvalidColumns): with pytest.raises(InvalidColumns):
fresh_db["tree"].extract(["bad_column"]) fresh_db.table("tree").extract(["bad_column"])
def test_extract_rowid_table(fresh_db): def test_extract_rowid_table(fresh_db):
fresh_db["tree"].insert( fresh_db.table("tree").insert(
{ {
"name": "Tree 1", "name": "Tree 1",
"common_name": "Palm", "common_name": "Palm",
"latin_name": "Arecaceae", "latin_name": "Arecaceae",
} }
) )
fresh_db["tree"].extract(["common_name", "latin_name"]) fresh_db.table("tree").extract(["common_name", "latin_name"])
assert fresh_db["tree"].schema == ( assert fresh_db.table("tree").schema == (
'CREATE TABLE "tree" (\n' 'CREATE TABLE "tree" (\n'
' "name" TEXT,\n' ' "name" TEXT,\n'
' "common_name_latin_name_id" INTEGER REFERENCES "common_name_latin_name"("id")\n' ' "common_name_latin_name_id" INTEGER REFERENCES "common_name_latin_name"("id")\n'
@ -139,68 +139,68 @@ def test_extract_rowid_table(fresh_db):
def test_reuse_lookup_table(fresh_db): def test_reuse_lookup_table(fresh_db):
fresh_db["species"].insert({"id": 1, "name": "Wolf"}, pk="id") fresh_db.table("species").insert({"id": 1, "name": "Wolf"}, pk="id")
fresh_db["sightings"].insert({"id": 10, "species": "Wolf"}, pk="id") fresh_db.table("sightings").insert({"id": 10, "species": "Wolf"}, pk="id")
fresh_db["individuals"].insert( fresh_db.table("individuals").insert(
{"id": 10, "name": "Terriana", "species": "Fox"}, pk="id" {"id": 10, "name": "Terriana", "species": "Fox"}, pk="id"
) )
fresh_db["sightings"].extract("species", rename={"species": "name"}) fresh_db.table("sightings").extract("species", rename={"species": "name"})
fresh_db["individuals"].extract("species", rename={"species": "name"}) fresh_db.table("individuals").extract("species", rename={"species": "name"})
assert fresh_db["sightings"].schema == ( assert fresh_db.table("sightings").schema == (
'CREATE TABLE "sightings" (\n' 'CREATE TABLE "sightings" (\n'
' "id" INTEGER PRIMARY KEY,\n' ' "id" INTEGER PRIMARY KEY,\n'
' "species_id" INTEGER REFERENCES "species"("id")\n' ' "species_id" INTEGER REFERENCES "species"("id")\n'
")" ")"
) )
assert fresh_db["individuals"].schema == ( assert fresh_db.table("individuals").schema == (
'CREATE TABLE "individuals" (\n' 'CREATE TABLE "individuals" (\n'
' "id" INTEGER PRIMARY KEY,\n' ' "id" INTEGER PRIMARY KEY,\n'
' "name" TEXT,\n' ' "name" TEXT,\n'
' "species_id" INTEGER REFERENCES "species"("id")\n' ' "species_id" INTEGER REFERENCES "species"("id")\n'
")" ")"
) )
assert list(fresh_db["species"].rows) == [ assert list(fresh_db.table("species").rows) == [
{"id": 1, "name": "Wolf"}, {"id": 1, "name": "Wolf"},
{"id": 2, "name": "Fox"}, {"id": 2, "name": "Fox"},
] ]
def test_extract_error_on_incompatible_existing_lookup_table(fresh_db): def test_extract_error_on_incompatible_existing_lookup_table(fresh_db):
fresh_db["species"].insert({"id": 1}) fresh_db.table("species").insert({"id": 1})
fresh_db["tree"].insert({"name": "Tree 1", "common_name": "Palm"}) fresh_db.table("tree").insert({"name": "Tree 1", "common_name": "Palm"})
with pytest.raises(InvalidColumns): with pytest.raises(InvalidColumns):
fresh_db["tree"].extract("common_name", table="species") fresh_db.table("tree").extract("common_name", table="species")
# Try again with incompatible existing column type # Try again with incompatible existing column type
fresh_db["species2"].insert({"id": 1, "common_name": 3.5}) fresh_db.table("species2").insert({"id": 1, "common_name": 3.5})
with pytest.raises(InvalidColumns): with pytest.raises(InvalidColumns):
fresh_db["tree"].extract("common_name", table="species2") fresh_db.table("tree").extract("common_name", table="species2")
def test_extract_works_with_null_values(fresh_db): def test_extract_works_with_null_values(fresh_db):
fresh_db["listens"].insert_all( fresh_db.table("listens").insert_all(
[ [
{"id": 1, "track_title": "foo", "album_title": "bar"}, {"id": 1, "track_title": "foo", "album_title": "bar"},
{"id": 2, "track_title": "baz", "album_title": None}, {"id": 2, "track_title": "baz", "album_title": None},
], ],
pk="id", pk="id",
) )
fresh_db["listens"].extract( fresh_db.table("listens").extract(
columns=["album_title"], table="albums", fk_column="album_id" columns=["album_title"], table="albums", fk_column="album_id"
) )
assert list(fresh_db["listens"].rows) == [ assert list(fresh_db.table("listens").rows) == [
{"id": 1, "track_title": "foo", "album_id": 1}, {"id": 1, "track_title": "foo", "album_id": 1},
{"id": 2, "track_title": "baz", "album_id": None}, {"id": 2, "track_title": "baz", "album_id": None},
] ]
assert list(fresh_db["albums"].rows) == [ assert list(fresh_db.table("albums").rows) == [
{"id": 1, "album_title": "bar"}, {"id": 1, "album_title": "bar"},
] ]
def test_extract_null_values_single_column(fresh_db): def test_extract_null_values_single_column(fresh_db):
# https://github.com/simonw/sqlite-utils/issues/186 # https://github.com/simonw/sqlite-utils/issues/186
fresh_db["species"].insert({"id": 1, "species": "Wolf"}, pk="id") fresh_db.table("species").insert({"id": 1, "species": "Wolf"}, pk="id")
fresh_db["individuals"].insert_all( fresh_db.table("individuals").insert_all(
[ [
{"id": 10, "name": "Terriana", "species": "Fox"}, {"id": 10, "name": "Terriana", "species": "Fox"},
{"id": 11, "name": "Spenidorm", "species": None}, {"id": 11, "name": "Spenidorm", "species": None},
@ -210,13 +210,13 @@ def test_extract_null_values_single_column(fresh_db):
], ],
pk="id", pk="id",
) )
fresh_db["individuals"].extract("species") fresh_db.table("individuals").extract("species")
# No null row should have been added to species # No null row should have been added to species
assert list(fresh_db["species"].rows) == [ assert list(fresh_db.table("species").rows) == [
{"id": 1, "species": "Wolf"}, {"id": 1, "species": "Wolf"},
{"id": 2, "species": "Fox"}, {"id": 2, "species": "Fox"},
] ]
assert list(fresh_db["individuals"].rows) == [ assert list(fresh_db.table("individuals").rows) == [
{"id": 10, "name": "Terriana", "species_id": 2}, {"id": 10, "name": "Terriana", "species_id": 2},
{"id": 11, "name": "Spenidorm", "species_id": None}, {"id": 11, "name": "Spenidorm", "species_id": None},
{"id": 12, "name": "Grantheim", "species_id": 1}, {"id": 12, "name": "Grantheim", "species_id": 1},
@ -228,7 +228,7 @@ def test_extract_null_values_single_column(fresh_db):
def test_extract_null_values_multiple_columns(fresh_db): def test_extract_null_values_multiple_columns(fresh_db):
# A row should be extracted if at least one column is not null - # A row should be extracted if at least one column is not null -
# only rows where ALL extracted columns are null are left alone # only rows where ALL extracted columns are null are left alone
fresh_db["circulation"].insert_all( fresh_db.table("circulation").insert_all(
[ [
{"id": 1, "title": "title one", "creator": "creator one", "year": 2018}, {"id": 1, "title": "title one", "creator": "creator one", "year": 2018},
{"id": 2, "title": "title two", "creator": None, "year": 2019}, {"id": 2, "title": "title two", "creator": None, "year": 2019},
@ -237,14 +237,14 @@ def test_extract_null_values_multiple_columns(fresh_db):
], ],
pk="id", pk="id",
) )
fresh_db["circulation"].extract( fresh_db.table("circulation").extract(
["title", "creator"], table="books", fk_column="book_id" ["title", "creator"], table="books", fk_column="book_id"
) )
assert list(fresh_db["books"].rows) == [ assert list(fresh_db.table("books").rows) == [
{"id": 1, "title": "title one", "creator": "creator one"}, {"id": 1, "title": "title one", "creator": "creator one"},
{"id": 2, "title": "title two", "creator": None}, {"id": 2, "title": "title two", "creator": None},
] ]
assert list(fresh_db["circulation"].rows) == [ assert list(fresh_db.table("circulation").rows) == [
{"id": 1, "book_id": 1, "year": 2018}, {"id": 1, "book_id": 1, "year": 2018},
{"id": 2, "book_id": 2, "year": 2019}, {"id": 2, "book_id": 2, "year": 2019},
{"id": 3, "book_id": None, "year": 2020}, {"id": 3, "book_id": None, "year": 2020},
@ -255,20 +255,20 @@ def test_extract_null_values_multiple_columns(fresh_db):
def test_extract_null_values_existing_lookup_table_with_null_row(fresh_db): def test_extract_null_values_existing_lookup_table_with_null_row(fresh_db):
# Even if the lookup table already contains an all-null row, rows where # Even if the lookup table already contains an all-null row, rows where
# every extracted column is null should keep a null foreign key # every extracted column is null should keep a null foreign key
fresh_db["species"].insert({"id": 1, "species": None}, pk="id") fresh_db.table("species").insert({"id": 1, "species": None}, pk="id")
fresh_db["individuals"].insert_all( fresh_db.table("individuals").insert_all(
[ [
{"id": 10, "name": "Terriana", "species": "Fox"}, {"id": 10, "name": "Terriana", "species": "Fox"},
{"id": 11, "name": "Spenidorm", "species": None}, {"id": 11, "name": "Spenidorm", "species": None},
], ],
pk="id", pk="id",
) )
fresh_db["individuals"].extract("species") fresh_db.table("individuals").extract("species")
assert list(fresh_db["species"].rows) == [ assert list(fresh_db.table("species").rows) == [
{"id": 1, "species": None}, {"id": 1, "species": None},
{"id": 2, "species": "Fox"}, {"id": 2, "species": "Fox"},
] ]
assert list(fresh_db["individuals"].rows) == [ assert list(fresh_db.table("individuals").rows) == [
{"id": 10, "name": "Terriana", "species_id": 2}, {"id": 10, "name": "Terriana", "species_id": 2},
{"id": 11, "name": "Spenidorm", "species_id": None}, {"id": 11, "name": "Spenidorm", "species_id": None},
] ]
@ -279,17 +279,19 @@ def test_extract_repeated_into_shared_lookup_with_nulls(fresh_db):
# cannot dedupe NULL-containing rows against the existing lookup # cannot dedupe NULL-containing rows against the existing lookup
# table - extracting a second table into the same lookup previously # table - extracting a second table into the same lookup previously
# inserted duplicate rows that nothing pointed to # inserted duplicate rows that nothing pointed to
fresh_db["t1"].insert_all( fresh_db.table("t1").insert_all(
[ [
{"id": 1, "species": None, "common": "X"}, {"id": 1, "species": None, "common": "X"},
{"id": 2, "species": "Oak", "common": "Oak"}, {"id": 2, "species": "Oak", "common": "Oak"},
], ],
pk="id", pk="id",
) )
fresh_db["t2"].insert_all([{"id": 1, "species": None, "common": "X"}], pk="id") fresh_db.table("t2").insert_all(
fresh_db["t1"].extract(["species", "common"], table="lk") [{"id": 1, "species": None, "common": "X"}], pk="id"
fresh_db["t2"].extract(["species", "common"], table="lk") )
assert fresh_db["lk"].count == 2 fresh_db.table("t1").extract(["species", "common"], table="lk")
fresh_db.table("t2").extract(["species", "common"], table="lk")
assert fresh_db.table("lk").count == 2
# Both tables point at the same lookup row # Both tables point at the same lookup row
t1_fk = fresh_db.execute("select lk_id from t1 where id = 1").fetchone()[0] t1_fk = fresh_db.execute("select lk_id from t1 where id = 1").fetchone()[0]
t2_fk = fresh_db.execute("select lk_id from t2 where id = 1").fetchone()[0] t2_fk = fresh_db.execute("select lk_id from t2 where id = 1").fetchone()[0]
@ -298,8 +300,8 @@ def test_extract_repeated_into_shared_lookup_with_nulls(fresh_db):
def test_extract_repeated_into_shared_lookup_no_nulls(fresh_db): def test_extract_repeated_into_shared_lookup_no_nulls(fresh_db):
# Non-NULL rows were already deduped by the unique index - keep it so # Non-NULL rows were already deduped by the unique index - keep it so
fresh_db["t1"].insert_all([{"id": 1, "species": "Oak"}], pk="id") fresh_db.table("t1").insert_all([{"id": 1, "species": "Oak"}], pk="id")
fresh_db["t2"].insert_all([{"id": 1, "species": "Oak"}], pk="id") fresh_db.table("t2").insert_all([{"id": 1, "species": "Oak"}], pk="id")
fresh_db["t1"].extract(["species"], table="lk") fresh_db.table("t1").extract(["species"], table="lk")
fresh_db["t2"].extract(["species"], table="lk") fresh_db.table("t2").extract(["species"], table="lk")
assert fresh_db["lk"].count == 1 assert fresh_db.table("lk").count == 1

View file

@ -32,15 +32,15 @@ def test_extracts(fresh_db, kwargs, expected_table, use_table_factory):
assert {expected_table, "Trees"} == set(fresh_db.table_names()) assert {expected_table, "Trees"} == set(fresh_db.table_names())
assert ( assert (
f'CREATE TABLE "{expected_table}" (\n "id" INTEGER PRIMARY KEY,\n "value" TEXT\n)' f'CREATE TABLE "{expected_table}" (\n "id" INTEGER PRIMARY KEY,\n "value" TEXT\n)'
== fresh_db[expected_table].schema == fresh_db.table(expected_table).schema
) )
assert ( assert (
f'CREATE TABLE "Trees" (\n "id" INTEGER,\n "species_id" INTEGER REFERENCES "{expected_table}"("id")\n)' f'CREATE TABLE "Trees" (\n "id" INTEGER,\n "species_id" INTEGER REFERENCES "{expected_table}"("id")\n)'
== fresh_db["Trees"].schema == fresh_db.table("Trees").schema
) )
# Should have a foreign key reference # Should have a foreign key reference
assert len(fresh_db["Trees"].foreign_keys) == 1 assert len(fresh_db.table("Trees").foreign_keys) == 1
fk = fresh_db["Trees"].foreign_keys[0] fk = fresh_db.table("Trees").foreign_keys[0]
assert fk.table == "Trees" assert fk.table == "Trees"
assert fk.column == "species_id" assert fk.column == "species_id"
@ -54,22 +54,22 @@ def test_extracts(fresh_db, kwargs, expected_table, use_table_factory):
partial=0, partial=0,
columns=["value"], columns=["value"],
) )
] == fresh_db[expected_table].indexes ] == fresh_db.table(expected_table).indexes
# Finally, check the rows # Finally, check the rows
assert [{"id": 1, "value": "Oak"}, {"id": 2, "value": "Palm"}] == list( assert [{"id": 1, "value": "Oak"}, {"id": 2, "value": "Palm"}] == list(
fresh_db[expected_table].rows fresh_db.table(expected_table).rows
) )
assert [ assert [
{"id": 1, "species_id": 1}, {"id": 1, "species_id": 1},
{"id": 2, "species_id": 1}, {"id": 2, "species_id": 1},
{"id": 3, "species_id": 2}, {"id": 3, "species_id": 2},
] == list(fresh_db["Trees"].rows) ] == list(fresh_db.table("Trees").rows)
def test_extracts_null_values(fresh_db): def test_extracts_null_values(fresh_db):
# https://github.com/simonw/sqlite-utils/issues/186 # https://github.com/simonw/sqlite-utils/issues/186
# Null values should stay null, not be extracted into the lookup table # Null values should stay null, not be extracted into the lookup table
fresh_db["Trees"].insert_all( fresh_db.table("Trees").insert_all(
[ [
{"id": 1, "species_id": "Oak"}, {"id": 1, "species_id": "Oak"},
{"id": 2, "species_id": None}, {"id": 2, "species_id": None},
@ -78,11 +78,11 @@ def test_extracts_null_values(fresh_db):
], ],
extracts={"species_id": "Species"}, extracts={"species_id": "Species"},
) )
assert list(fresh_db["Species"].rows) == [ assert list(fresh_db.table("Species").rows) == [
{"id": 1, "value": "Oak"}, {"id": 1, "value": "Oak"},
{"id": 2, "value": "Palm"}, {"id": 2, "value": "Palm"},
] ]
assert list(fresh_db["Trees"].rows) == [ assert list(fresh_db.table("Trees").rows) == [
{"id": 1, "species_id": 1}, {"id": 1, "species_id": 1},
{"id": 2, "species_id": None}, {"id": 2, "species_id": None},
{"id": 3, "species_id": 2}, {"id": 3, "species_id": 2},
@ -92,7 +92,7 @@ def test_extracts_null_values(fresh_db):
def test_extracts_null_values_list_mode(fresh_db): def test_extracts_null_values_list_mode(fresh_db):
# Same as test_extracts_null_values but for list-based records # Same as test_extracts_null_values but for list-based records
fresh_db["Trees"].insert_all( fresh_db.table("Trees").insert_all(
[ [
["id", "species_id"], ["id", "species_id"],
[1, "Oak"], [1, "Oak"],
@ -102,11 +102,11 @@ def test_extracts_null_values_list_mode(fresh_db):
], ],
extracts={"species_id": "Species"}, extracts={"species_id": "Species"},
) )
assert list(fresh_db["Species"].rows) == [ assert list(fresh_db.table("Species").rows) == [
{"id": 1, "value": "Oak"}, {"id": 1, "value": "Oak"},
{"id": 2, "value": "Palm"}, {"id": 2, "value": "Palm"},
] ]
assert list(fresh_db["Trees"].rows) == [ assert list(fresh_db.table("Trees").rows) == [
{"id": 1, "species_id": 1}, {"id": 1, "species_id": 1},
{"id": 2, "species_id": None}, {"id": 2, "species_id": None},
{"id": 3, "species_id": 2}, {"id": 3, "species_id": 2},

View file

@ -32,7 +32,7 @@ def compound_db():
def test_compound_foreign_key(compound_db): def test_compound_foreign_key(compound_db):
fks = compound_db["courses"].foreign_keys fks = compound_db.table("courses").foreign_keys
assert len(fks) == 1 assert len(fks) == 1
fk = fks[0] fk = fks[0]
assert fk.is_compound is True assert fk.is_compound is True
@ -46,10 +46,10 @@ def test_compound_foreign_key(compound_db):
def test_single_foreign_key_gets_columns_fields(fresh_db): def test_single_foreign_key_gets_columns_fields(fresh_db):
fresh_db["authors"].insert({"id": 1, "name": "Sally"}, pk="id") fresh_db.table("authors").insert({"id": 1, "name": "Sally"}, pk="id")
fresh_db["books"].insert({"title": "Hedgehogs", "author_id": 1}) fresh_db.table("books").insert({"title": "Hedgehogs", "author_id": 1})
fresh_db["books"].add_foreign_key("author_id", "authors", "id") fresh_db.table("books").add_foreign_key("author_id", "authors", "id")
fk = fresh_db["books"].foreign_keys[0] fk = fresh_db.table("books").foreign_keys[0]
assert fk.is_compound is False assert fk.is_compound is False
assert fk.column == "author_id" assert fk.column == "author_id"
assert fk.other_column == "id" assert fk.other_column == "id"
@ -60,10 +60,10 @@ def test_single_foreign_key_gets_columns_fields(fresh_db):
def test_foreign_key_no_longer_unpacks_as_tuple(fresh_db): def test_foreign_key_no_longer_unpacks_as_tuple(fresh_db):
# Clean break in 4.0: ForeignKey is a dataclass, not a namedtuple, so the # Clean break in 4.0: ForeignKey is a dataclass, not a namedtuple, so the
# old tuple unpacking and indexing patterns now fail hard. # old tuple unpacking and indexing patterns now fail hard.
fresh_db["authors"].insert({"id": 1, "name": "Sally"}, pk="id") fresh_db.table("authors").insert({"id": 1, "name": "Sally"}, pk="id")
fresh_db["books"].insert({"title": "Hedgehogs", "author_id": 1}) fresh_db.table("books").insert({"title": "Hedgehogs", "author_id": 1})
fresh_db["books"].add_foreign_key("author_id", "authors", "id") fresh_db.table("books").add_foreign_key("author_id", "authors", "id")
fk = fresh_db["books"].foreign_keys[0] fk = fresh_db.table("books").foreign_keys[0]
with pytest.raises(TypeError): with pytest.raises(TypeError):
_table, _column, _other_table, _other_column = fk _table, _column, _other_table, _other_column = fk
with pytest.raises(TypeError): with pytest.raises(TypeError):
@ -71,16 +71,18 @@ def test_foreign_key_no_longer_unpacks_as_tuple(fresh_db):
def test_foreign_keys_are_sortable(fresh_db): def test_foreign_keys_are_sortable(fresh_db):
fresh_db["authors"].insert({"id": 1, "name": "Sally"}, pk="id") fresh_db.table("authors").insert({"id": 1, "name": "Sally"}, pk="id")
fresh_db["categories"].insert({"id": 1, "name": "Wildlife"}, pk="id") fresh_db.table("categories").insert({"id": 1, "name": "Wildlife"}, pk="id")
fresh_db["books"].insert({"title": "Hedgehogs", "author_id": 1, "category_id": 1}) fresh_db.table("books").insert(
{"title": "Hedgehogs", "author_id": 1, "category_id": 1}
)
fresh_db.add_foreign_keys( fresh_db.add_foreign_keys(
[ [
("books", "author_id", "authors", "id"), ("books", "author_id", "authors", "id"),
("books", "category_id", "categories", "id"), ("books", "category_id", "categories", "id"),
] ]
) )
fks = sorted(fresh_db["books"].foreign_keys) fks = sorted(fresh_db.table("books").foreign_keys)
assert fks[0].column == "author_id" assert fks[0].column == "author_id"
assert fks[1].column == "category_id" assert fks[1].column == "category_id"
@ -105,7 +107,7 @@ def test_mixed_compound_and_single_foreign_keys_are_sortable():
REFERENCES departments(campus_name, dept_code) REFERENCES departments(campus_name, dept_code)
); );
""") """)
fks = db["courses"].foreign_keys fks = db.table("courses").foreign_keys
assert len(fks) == 2 assert len(fks) == 2
assert {fk.is_compound for fk in fks} == {True, False} assert {fk.is_compound for fk in fks} == {True, False}
fks_sorted = sorted(fks) fks_sorted = sorted(fks)
@ -163,8 +165,8 @@ def test_create_table_with_compound_foreign_key(departments_db, foreign_keys):
pk="course_code", pk="course_code",
foreign_keys=foreign_keys, foreign_keys=foreign_keys,
) )
assert departments_db["courses"].schema == EXPECTED_COURSES_SCHEMA assert departments_db.table("courses").schema == EXPECTED_COURSES_SCHEMA
fks = departments_db["courses"].foreign_keys fks = departments_db.table("courses").foreign_keys
assert len(fks) == 1 assert len(fks) == 1
fk = fks[0] fk = fks[0]
assert fk.is_compound is True assert fk.is_compound is True
@ -181,10 +183,10 @@ def test_create_table_compound_foreign_key_enforced(departments_db):
pk="course_code", pk="course_code",
foreign_keys=[(("campus_name", "dept_code"), "departments")], foreign_keys=[(("campus_name", "dept_code"), "departments")],
) )
departments_db["departments"].insert( departments_db.table("departments").insert(
{"campus_name": "Berkeley", "dept_code": "CS", "dept_name": "Computer Science"} {"campus_name": "Berkeley", "dept_code": "CS", "dept_name": "Computer Science"}
) )
departments_db["courses"].insert( departments_db.table("courses").insert(
{"course_code": "CS101", "campus_name": "Berkeley", "dept_code": "CS"} {"course_code": "CS101", "campus_name": "Berkeley", "dept_code": "CS"}
) )
with pytest.raises(sqlite3.IntegrityError): with pytest.raises(sqlite3.IntegrityError):
@ -207,8 +209,8 @@ def test_create_table_compound_foreign_key_missing_other_column(departments_db):
def test_transform_preserves_compound_foreign_key(compound_db): def test_transform_preserves_compound_foreign_key(compound_db):
compound_db["courses"].transform(rename={"course_name": "title"}) compound_db.table("courses").transform(rename={"course_name": "title"})
fks = compound_db["courses"].foreign_keys fks = compound_db.table("courses").foreign_keys
assert len(fks) == 1 assert len(fks) == 1
fk = fks[0] fk = fks[0]
assert fk.is_compound is True assert fk.is_compound is True
@ -218,8 +220,8 @@ def test_transform_preserves_compound_foreign_key(compound_db):
def test_transform_rename_member_column_updates_compound_foreign_key(compound_db): def test_transform_rename_member_column_updates_compound_foreign_key(compound_db):
compound_db["courses"].transform(rename={"campus_name": "campus"}) compound_db.table("courses").transform(rename={"campus_name": "campus"})
fks = compound_db["courses"].foreign_keys fks = compound_db.table("courses").foreign_keys
assert len(fks) == 1 assert len(fks) == 1
fk = fks[0] fk = fks[0]
assert fk.is_compound is True assert fk.is_compound is True
@ -231,9 +233,9 @@ def test_transform_rename_member_column_updates_compound_foreign_key(compound_db
def test_transform_drop_member_column_drops_compound_foreign_key(compound_db): def test_transform_drop_member_column_drops_compound_foreign_key(compound_db):
# Matches single-column behavior: dropping the column silently # Matches single-column behavior: dropping the column silently
# drops the foreign key that used it # drops the foreign key that used it
compound_db["courses"].transform(drop={"dept_code"}) compound_db.table("courses").transform(drop={"dept_code"})
assert compound_db["courses"].foreign_keys == [] assert compound_db.table("courses").foreign_keys == []
assert "FOREIGN KEY" not in compound_db["courses"].schema assert "FOREIGN KEY" not in compound_db.table("courses").schema
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -246,11 +248,11 @@ def test_transform_drop_member_column_drops_compound_foreign_key(compound_db):
), ),
) )
def test_transform_drop_compound_foreign_key(compound_db, drop_foreign_keys): def test_transform_drop_compound_foreign_key(compound_db, drop_foreign_keys):
compound_db["courses"].transform(drop_foreign_keys=drop_foreign_keys) compound_db.table("courses").transform(drop_foreign_keys=drop_foreign_keys)
assert compound_db["courses"].foreign_keys == [] assert compound_db.table("courses").foreign_keys == []
# The columns themselves survive # The columns themselves survive
assert {"campus_name", "dept_code"} <= set( assert {"campus_name", "dept_code"} <= set(
compound_db["courses"].columns_dict.keys() compound_db.table("courses").columns_dict.keys()
) )
@ -265,12 +267,12 @@ def courses_db(departments_db):
def test_add_compound_foreign_key(courses_db): def test_add_compound_foreign_key(courses_db):
t = courses_db["courses"].add_foreign_key( t = courses_db.table("courses").add_foreign_key(
("campus_name", "dept_code"), "departments", ("campus_name", "dept_code") ("campus_name", "dept_code"), "departments", ("campus_name", "dept_code")
) )
# Returns self # Returns self
assert t.name == "courses" assert t.name == "courses"
fks = courses_db["courses"].foreign_keys fks = courses_db.table("courses").foreign_keys
assert len(fks) == 1 assert len(fks) == 1
fk = fks[0] fk = fks[0]
assert fk.is_compound is True assert fk.is_compound is True
@ -281,27 +283,33 @@ def test_add_compound_foreign_key(courses_db):
def test_add_compound_foreign_key_guesses_other_columns(courses_db): def test_add_compound_foreign_key_guesses_other_columns(courses_db):
# Lists work here too, though tuples are the documented form # Lists work here too, though tuples are the documented form
courses_db["courses"].add_foreign_key(["campus_name", "dept_code"], "departments") courses_db.table("courses").add_foreign_key(
fk = courses_db["courses"].foreign_keys[0] ["campus_name", "dept_code"], "departments"
)
fk = courses_db.table("courses").foreign_keys[0]
assert fk.other_columns == ("campus_name", "dept_code") assert fk.other_columns == ("campus_name", "dept_code")
def test_add_compound_foreign_key_error_if_already_exists(courses_db): def test_add_compound_foreign_key_error_if_already_exists(courses_db):
courses_db["courses"].add_foreign_key(("campus_name", "dept_code"), "departments") courses_db.table("courses").add_foreign_key(
("campus_name", "dept_code"), "departments"
)
with pytest.raises(AlterError) as ex: with pytest.raises(AlterError) as ex:
courses_db["courses"].add_foreign_key( courses_db.table("courses").add_foreign_key(
("campus_name", "dept_code"), "departments" ("campus_name", "dept_code"), "departments"
) )
assert "already exists" in ex.value.args[0] assert "already exists" in ex.value.args[0]
# ignore=True should not raise # ignore=True should not raise
courses_db["courses"].add_foreign_key( courses_db.table("courses").add_foreign_key(
("campus_name", "dept_code"), "departments", ignore=True ("campus_name", "dept_code"), "departments", ignore=True
) )
def test_add_compound_foreign_key_error_if_column_missing(courses_db): def test_add_compound_foreign_key_error_if_column_missing(courses_db):
with pytest.raises(AlterError): with pytest.raises(AlterError):
courses_db["courses"].add_foreign_key(("campus_name", "nope"), "departments") courses_db.table("courses").add_foreign_key(
("campus_name", "nope"), "departments"
)
def test_db_add_foreign_keys_compound(courses_db): def test_db_add_foreign_keys_compound(courses_db):
@ -315,14 +323,14 @@ def test_db_add_foreign_keys_compound(courses_db):
) )
] ]
) )
fk = courses_db["courses"].foreign_keys[0] fk = courses_db.table("courses").foreign_keys[0]
assert fk.is_compound is True assert fk.is_compound is True
assert fk.columns == ("campus_name", "dept_code") assert fk.columns == ("campus_name", "dept_code")
def test_index_foreign_keys_compound_creates_composite_index(compound_db): def test_index_foreign_keys_compound_creates_composite_index(compound_db):
compound_db.index_foreign_keys() compound_db.index_foreign_keys()
index_columns = [i.columns for i in compound_db["courses"].indexes] index_columns = [i.columns for i in compound_db.table("courses").indexes]
assert ["campus_name", "dept_code"] in index_columns assert ["campus_name", "dept_code"] in index_columns
# No separate single-column indexes for the members # No separate single-column indexes for the members
assert ["campus_name"] not in index_columns assert ["campus_name"] not in index_columns
@ -339,22 +347,22 @@ def test_foreign_key_captures_on_delete_and_on_update():
ON DELETE CASCADE ON UPDATE RESTRICT ON DELETE CASCADE ON UPDATE RESTRICT
); );
""") """)
fk = db["books"].foreign_keys[0] fk = db.table("books").foreign_keys[0]
assert fk.on_delete == "CASCADE" assert fk.on_delete == "CASCADE"
assert fk.on_update == "RESTRICT" assert fk.on_update == "RESTRICT"
def test_foreign_key_on_delete_defaults_to_no_action(fresh_db): def test_foreign_key_on_delete_defaults_to_no_action(fresh_db):
fresh_db["authors"].insert({"id": 1}, pk="id") fresh_db.table("authors").insert({"id": 1}, pk="id")
fresh_db["books"].insert({"id": 1, "author_id": 1}, pk="id") fresh_db.table("books").insert({"id": 1, "author_id": 1}, pk="id")
fresh_db["books"].add_foreign_key("author_id", "authors", "id") fresh_db.table("books").add_foreign_key("author_id", "authors", "id")
fk = fresh_db["books"].foreign_keys[0] fk = fresh_db.table("books").foreign_keys[0]
assert fk.on_delete == "NO ACTION" assert fk.on_delete == "NO ACTION"
assert fk.on_update == "NO ACTION" assert fk.on_update == "NO ACTION"
def test_create_table_foreign_key_with_on_delete(fresh_db): def test_create_table_foreign_key_with_on_delete(fresh_db):
fresh_db["authors"].insert({"id": 1}, pk="id") fresh_db.table("authors").insert({"id": 1}, pk="id")
fresh_db.create_table( fresh_db.create_table(
"books", "books",
{"id": int, "author_id": int}, {"id": int, "author_id": int},
@ -369,8 +377,8 @@ def test_create_table_foreign_key_with_on_delete(fresh_db):
) )
], ],
) )
assert "ON DELETE CASCADE" in fresh_db["books"].schema assert "ON DELETE CASCADE" in fresh_db.table("books").schema
assert fresh_db["books"].foreign_keys[0].on_delete == "CASCADE" assert fresh_db.table("books").foreign_keys[0].on_delete == "CASCADE"
def test_transform_preserves_on_delete_cascade(): def test_transform_preserves_on_delete_cascade():
@ -383,11 +391,11 @@ def test_transform_preserves_on_delete_cascade():
author_id INTEGER REFERENCES authors(id) ON DELETE CASCADE author_id INTEGER REFERENCES authors(id) ON DELETE CASCADE
); );
""") """)
db["books"].transform(rename={"title": "book_title"}) db.table("books").transform(rename={"title": "book_title"})
fk = db["books"].foreign_keys[0] fk = db.table("books").foreign_keys[0]
assert fk.on_delete == "CASCADE" assert fk.on_delete == "CASCADE"
assert fk.on_update == "NO ACTION" assert fk.on_update == "NO ACTION"
assert "ON DELETE CASCADE" in db["books"].schema assert "ON DELETE CASCADE" in db.table("books").schema
def test_transform_preserves_compound_foreign_key_on_delete(): def test_transform_preserves_compound_foreign_key_on_delete():
@ -406,11 +414,11 @@ def test_transform_preserves_compound_foreign_key_on_delete():
REFERENCES departments(campus_name, dept_code) ON DELETE CASCADE REFERENCES departments(campus_name, dept_code) ON DELETE CASCADE
); );
""") """)
db["courses"].transform(rename={"course_code": "code"}) db.table("courses").transform(rename={"course_code": "code"})
fk = db["courses"].foreign_keys[0] fk = db.table("courses").foreign_keys[0]
assert fk.is_compound is True assert fk.is_compound is True
assert fk.on_delete == "CASCADE" assert fk.on_delete == "CASCADE"
assert "ON DELETE CASCADE" in db["courses"].schema assert "ON DELETE CASCADE" in db.table("courses").schema
def test_implicit_primary_key_reference_is_resolved(): def test_implicit_primary_key_reference_is_resolved():
@ -424,7 +432,7 @@ def test_implicit_primary_key_reference_is_resolved():
author_id INTEGER REFERENCES authors author_id INTEGER REFERENCES authors
); );
""") """)
fk = db["books"].foreign_keys[0] fk = db.table("books").foreign_keys[0]
assert fk.is_compound is False assert fk.is_compound is False
assert fk.other_column == "author_id" assert fk.other_column == "author_id"
assert fk.other_columns == ("author_id",) assert fk.other_columns == ("author_id",)
@ -445,7 +453,7 @@ def test_implicit_compound_primary_key_reference_is_resolved():
FOREIGN KEY (campus_name, dept_code) REFERENCES departments FOREIGN KEY (campus_name, dept_code) REFERENCES departments
); );
""") """)
fk = db["courses"].foreign_keys[0] fk = db.table("courses").foreign_keys[0]
assert fk.is_compound is True assert fk.is_compound is True
assert fk.other_columns == ("campus_name", "dept_code") assert fk.other_columns == ("campus_name", "dept_code")
@ -470,14 +478,14 @@ def test_add_foreign_keys_preserves_actions(fresh_db):
# https://github.com/simonw/sqlite-utils/issues/594 review finding: # https://github.com/simonw/sqlite-utils/issues/594 review finding:
# ForeignKey objects passed to db.add_foreign_keys() were flattened # ForeignKey objects passed to db.add_foreign_keys() were flattened
# to plain tuples, losing on_delete/on_update # to plain tuples, losing on_delete/on_update
fresh_db["authors"].insert({"id": 1}, pk="id") fresh_db.table("authors").insert({"id": 1}, pk="id")
fresh_db["books"].insert({"id": 1, "author_id": 1}, pk="id") fresh_db.table("books").insert({"id": 1, "author_id": 1}, pk="id")
fresh_db.add_foreign_keys( fresh_db.add_foreign_keys(
[ForeignKey("books", "author_id", "authors", "id", on_delete="CASCADE")] [ForeignKey("books", "author_id", "authors", "id", on_delete="CASCADE")]
) )
fk = fresh_db["books"].foreign_keys[0] fk = fresh_db.table("books").foreign_keys[0]
assert fk.on_delete == "CASCADE" assert fk.on_delete == "CASCADE"
assert "ON DELETE CASCADE" in fresh_db["books"].schema assert "ON DELETE CASCADE" in fresh_db.table("books").schema
def test_add_foreign_keys_preserves_actions_compound(courses_db): def test_add_foreign_keys_preserves_actions_compound(courses_db):
@ -495,36 +503,36 @@ def test_add_foreign_keys_preserves_actions_compound(courses_db):
) )
] ]
) )
fk = courses_db["courses"].foreign_keys[0] fk = courses_db.table("courses").foreign_keys[0]
assert fk.is_compound is True assert fk.is_compound is True
assert fk.on_delete == "CASCADE" assert fk.on_delete == "CASCADE"
assert "ON DELETE CASCADE" in courses_db["courses"].schema assert "ON DELETE CASCADE" in courses_db.table("courses").schema
def test_add_foreign_key_on_delete_on_update(fresh_db): def test_add_foreign_key_on_delete_on_update(fresh_db):
fresh_db["authors"].insert({"id": 1}, pk="id") fresh_db.table("authors").insert({"id": 1}, pk="id")
fresh_db["books"].insert({"id": 1, "author_id": 1}, pk="id") fresh_db.table("books").insert({"id": 1, "author_id": 1}, pk="id")
fresh_db["books"].add_foreign_key( fresh_db.table("books").add_foreign_key(
"author_id", "authors", "id", on_delete="CASCADE", on_update="RESTRICT" "author_id", "authors", "id", on_delete="CASCADE", on_update="RESTRICT"
) )
fk = fresh_db["books"].foreign_keys[0] fk = fresh_db.table("books").foreign_keys[0]
assert fk.on_delete == "CASCADE" assert fk.on_delete == "CASCADE"
assert fk.on_update == "RESTRICT" assert fk.on_update == "RESTRICT"
assert "ON UPDATE RESTRICT ON DELETE CASCADE" in fresh_db["books"].schema assert "ON UPDATE RESTRICT ON DELETE CASCADE" in fresh_db.table("books").schema
# The cascade should actually fire # The cascade should actually fire
fresh_db.execute("PRAGMA foreign_keys = ON") fresh_db.execute("PRAGMA foreign_keys = ON")
fresh_db.execute("delete from authors where id = 1") fresh_db.execute("delete from authors where id = 1")
assert fresh_db["books"].count == 0 assert fresh_db.table("books").count == 0
def test_add_compound_foreign_key_on_delete(courses_db): def test_add_compound_foreign_key_on_delete(courses_db):
courses_db["courses"].add_foreign_key( courses_db.table("courses").add_foreign_key(
("campus_name", "dept_code"), "departments", on_delete="SET NULL" ("campus_name", "dept_code"), "departments", on_delete="SET NULL"
) )
fk = courses_db["courses"].foreign_keys[0] fk = courses_db.table("courses").foreign_keys[0]
assert fk.is_compound is True assert fk.is_compound is True
assert fk.on_delete == "SET NULL" assert fk.on_delete == "SET NULL"
assert "ON DELETE SET NULL" in courses_db["courses"].schema assert "ON DELETE SET NULL" in courses_db.table("courses").schema
def test_implicit_compound_foreign_key_resolves_pk_declaration_order(fresh_db): def test_implicit_compound_foreign_key_resolves_pk_declaration_order(fresh_db):
@ -536,7 +544,7 @@ def test_implicit_compound_foreign_key_resolves_pk_declaration_order(fresh_db):
fresh_db.execute( fresh_db.execute(
"create table child (x text, y text, foreign key (x, y) references other)" "create table child (x text, y text, foreign key (x, y) references other)"
) )
fk = fresh_db["child"].foreign_keys[0] fk = fresh_db.table("child").foreign_keys[0]
assert fk.other_columns == ("a", "b") assert fk.other_columns == ("a", "b")
@ -549,46 +557,46 @@ def test_transform_implicit_compound_foreign_key_stays_valid(fresh_db):
"create table child (x text, y text, foreign key (x, y) references other)" "create table child (x text, y text, foreign key (x, y) references other)"
) )
fresh_db.execute("PRAGMA foreign_keys = ON") fresh_db.execute("PRAGMA foreign_keys = ON")
fresh_db["other"].insert({"a": "A", "b": "B"}) fresh_db.table("other").insert({"a": "A", "b": "B"})
fresh_db["child"].insert({"x": "A", "y": "B"}) fresh_db.table("child").insert({"x": "A", "y": "B"})
fresh_db["child"].transform(types={"x": str}) fresh_db.table("child").transform(types={"x": str})
assert fresh_db["child"].foreign_keys[0].other_columns == ("a", "b") assert fresh_db.table("child").foreign_keys[0].other_columns == ("a", "b")
# The constraint still points the right way around # The constraint still points the right way around
fresh_db["child"].insert({"x": "A", "y": "B"}) fresh_db.table("child").insert({"x": "A", "y": "B"})
with pytest.raises(sqlite3.IntegrityError): with pytest.raises(sqlite3.IntegrityError):
fresh_db["child"].insert({"x": "B", "y": "A"}) fresh_db.table("child").insert({"x": "B", "y": "A"})
def test_create_compound_foreign_key_guesses_pk_declaration_order(fresh_db): def test_create_compound_foreign_key_guesses_pk_declaration_order(fresh_db):
fresh_db.execute("create table other (b text, a text, primary key (a, b))") fresh_db.execute("create table other (b text, a text, primary key (a, b))")
fresh_db["other"].insert({"a": "A", "b": "B"}) fresh_db.table("other").insert({"a": "A", "b": "B"})
fresh_db["child"].create( fresh_db.table("child").create(
{"id": int, "x": str, "y": str}, {"id": int, "x": str, "y": str},
pk="id", pk="id",
foreign_keys=[(("x", "y"), "other")], foreign_keys=[(("x", "y"), "other")],
) )
assert fresh_db["child"].foreign_keys[0].other_columns == ("a", "b") assert fresh_db.table("child").foreign_keys[0].other_columns == ("a", "b")
fresh_db.execute("PRAGMA foreign_keys = ON") fresh_db.execute("PRAGMA foreign_keys = ON")
fresh_db["child"].insert({"id": 1, "x": "A", "y": "B"}) fresh_db.table("child").insert({"id": 1, "x": "A", "y": "B"})
with pytest.raises(sqlite3.IntegrityError): with pytest.raises(sqlite3.IntegrityError):
fresh_db["child"].insert({"id": 2, "x": "B", "y": "A"}) fresh_db.table("child").insert({"id": 2, "x": "B", "y": "A"})
def test_add_compound_foreign_key_guesses_pk_declaration_order(fresh_db): def test_add_compound_foreign_key_guesses_pk_declaration_order(fresh_db):
fresh_db.execute("create table other (b text, a text, primary key (a, b))") fresh_db.execute("create table other (b text, a text, primary key (a, b))")
fresh_db["child"].insert({"id": 1, "x": "A", "y": "B"}, pk="id") fresh_db.table("child").insert({"id": 1, "x": "A", "y": "B"}, pk="id")
fresh_db["child"].add_foreign_key(("x", "y"), "other") fresh_db.table("child").add_foreign_key(("x", "y"), "other")
assert fresh_db["child"].foreign_keys[0].other_columns == ("a", "b") assert fresh_db.table("child").foreign_keys[0].other_columns == ("a", "b")
def test_foreign_keys_are_hashable(fresh_db): def test_foreign_keys_are_hashable(fresh_db):
# set() over foreign_keys worked with the 3.x namedtuple and must # set() over foreign_keys worked with the 3.x namedtuple and must
# keep working with the dataclass # keep working with the dataclass
fresh_db["p"].insert({"id": 1}, pk="id") fresh_db.table("p").insert({"id": 1}, pk="id")
fresh_db["c"].insert( fresh_db.table("c").insert(
{"id": 1, "pid": 1}, pk="id", foreign_keys=[("pid", "p", "id")] {"id": 1, "pid": 1}, pk="id", foreign_keys=[("pid", "p", "id")]
) )
fks = set(fresh_db["c"].foreign_keys) fks = set(fresh_db.table("c").foreign_keys)
assert len(fks) == 1 assert len(fks) == 1
assert ForeignKey("c", "pid", "p", "id") in fks assert ForeignKey("c", "pid", "p", "id") in fks
# Usable as dict keys too # Usable as dict keys too
@ -617,9 +625,9 @@ def test_create_table_mixed_foreign_keys_list(fresh_db):
# 3.x accepted a mix of ForeignKey objects, tuples and bare column # 3.x accepted a mix of ForeignKey objects, tuples and bare column
# strings in foreign_keys= (ForeignKey was a namedtuple, so it passed # strings in foreign_keys= (ForeignKey was a namedtuple, so it passed
# the tuple check) - keep accepting the mix # the tuple check) - keep accepting the mix
fresh_db["authors"].insert({"id": 1}, pk="id") fresh_db.table("authors").insert({"id": 1}, pk="id")
fresh_db["publishers"].insert({"id": 1}, pk="id") fresh_db.table("publishers").insert({"id": 1}, pk="id")
fresh_db["books"].create( fresh_db.table("books").create(
{"id": int, "author_id": int, "publisher_id": int}, {"id": int, "author_id": int, "publisher_id": int},
pk="id", pk="id",
foreign_keys=[ foreign_keys=[
@ -627,14 +635,14 @@ def test_create_table_mixed_foreign_keys_list(fresh_db):
("publisher_id", "publishers", "id"), ("publisher_id", "publishers", "id"),
], ],
) )
fks = {fk.column: fk.other_table for fk in fresh_db["books"].foreign_keys} fks = {fk.column: fk.other_table for fk in fresh_db.table("books").foreign_keys}
assert fks == {"author_id": "authors", "publisher_id": "publishers"} assert fks == {"author_id": "authors", "publisher_id": "publishers"}
def test_create_table_mixed_foreign_keys_with_string(fresh_db): def test_create_table_mixed_foreign_keys_with_string(fresh_db):
fresh_db["authors"].insert({"id": 1}, pk="id") fresh_db.table("authors").insert({"id": 1}, pk="id")
fresh_db["publishers"].insert({"id": 1}, pk="id") fresh_db.table("publishers").insert({"id": 1}, pk="id")
fresh_db["books"].create( fresh_db.table("books").create(
{"id": int, "author_id": int, "publisher_id": int}, {"id": int, "author_id": int, "publisher_id": int},
pk="id", pk="id",
foreign_keys=[ foreign_keys=[
@ -642,15 +650,15 @@ def test_create_table_mixed_foreign_keys_with_string(fresh_db):
("publisher_id", "publishers", "id"), ("publisher_id", "publishers", "id"),
], ],
) )
fks = {fk.column: fk.other_table for fk in fresh_db["books"].foreign_keys} fks = {fk.column: fk.other_table for fk in fresh_db.table("books").foreign_keys}
assert fks == {"author_id": "authors", "publisher_id": "publishers"} assert fks == {"author_id": "authors", "publisher_id": "publishers"}
def test_add_foreign_keys_existing_with_different_actions_errors(fresh_db): def test_add_foreign_keys_existing_with_different_actions_errors(fresh_db):
# Requesting an existing foreign key with different ON DELETE/ON UPDATE # Requesting an existing foreign key with different ON DELETE/ON UPDATE
# actions was silently skipped, dropping the requested change # actions was silently skipped, dropping the requested change
fresh_db["authors"].insert({"id": 1}, pk="id") fresh_db.table("authors").insert({"id": 1}, pk="id")
fresh_db["books"].insert( fresh_db.table("books").insert(
{"id": 1, "author_id": 1}, {"id": 1, "author_id": 1},
pk="id", pk="id",
foreign_keys=[("author_id", "authors", "id")], foreign_keys=[("author_id", "authors", "id")],
@ -660,19 +668,21 @@ def test_add_foreign_keys_existing_with_different_actions_errors(fresh_db):
[ForeignKey("books", "author_id", "authors", "id", on_delete="CASCADE")] [ForeignKey("books", "author_id", "authors", "id", on_delete="CASCADE")]
) )
assert "ON DELETE" in str(ex.value) assert "ON DELETE" in str(ex.value)
assert fresh_db["books"].foreign_keys[0].on_delete == "NO ACTION" assert fresh_db.table("books").foreign_keys[0].on_delete == "NO ACTION"
def test_add_foreign_keys_identical_existing_is_noop(fresh_db): def test_add_foreign_keys_identical_existing_is_noop(fresh_db):
# An exact match, including actions, is silently skipped so repeated # An exact match, including actions, is silently skipped so repeated
# calls stay idempotent # calls stay idempotent
fresh_db["authors"].insert({"id": 1}, pk="id") fresh_db.table("authors").insert({"id": 1}, pk="id")
fresh_db["books"].insert({"id": 1, "author_id": 1}, pk="id") fresh_db.table("books").insert({"id": 1, "author_id": 1}, pk="id")
fresh_db["books"].add_foreign_key("author_id", "authors", "id", on_delete="CASCADE") fresh_db.table("books").add_foreign_key(
"author_id", "authors", "id", on_delete="CASCADE"
)
fresh_db.add_foreign_keys( fresh_db.add_foreign_keys(
[ForeignKey("books", "author_id", "authors", "id", on_delete="CASCADE")] [ForeignKey("books", "author_id", "authors", "id", on_delete="CASCADE")]
) )
fks = fresh_db["books"].foreign_keys fks = fresh_db.table("books").foreign_keys
assert len(fks) == 1 assert len(fks) == 1
assert fks[0].on_delete == "CASCADE" assert fks[0].on_delete == "CASCADE"
@ -680,13 +690,13 @@ def test_add_foreign_keys_identical_existing_is_noop(fresh_db):
def test_add_foreign_keys_compound_column_count_mismatch_errors(fresh_db): def test_add_foreign_keys_compound_column_count_mismatch_errors(fresh_db):
# Previously the extra other-column was silently discarded, creating # Previously the extra other-column was silently discarded, creating
# a single-column foreign key to just ("id") # a single-column foreign key to just ("id")
fresh_db["departments"].insert( fresh_db.table("departments").insert(
{"campus": "north", "code": "cs"}, pk=("campus", "code") {"campus": "north", "code": "cs"}, pk=("campus", "code")
) )
fresh_db["courses"].insert({"id": 1, "campus": "north"}, pk="id") fresh_db.table("courses").insert({"id": 1, "campus": "north"}, pk="id")
with pytest.raises(ValueError) as ex: with pytest.raises(ValueError) as ex:
fresh_db.add_foreign_keys( fresh_db.add_foreign_keys(
[("courses", ("campus",), "departments", ("campus", "code"))] [("courses", ("campus",), "departments", ("campus", "code"))]
) )
assert "same number of columns" in str(ex.value) assert "same number of columns" in str(ex.value)
assert fresh_db["courses"].foreign_keys == [] assert fresh_db.table("courses").foreign_keys == []

View file

@ -20,7 +20,7 @@ search_records = [
def test_enable_fts(fresh_db): def test_enable_fts(fresh_db):
table = fresh_db["searchable"] table = fresh_db.table("searchable")
table.insert_all(search_records) table.insert_all(search_records)
assert ["searchable"] == fresh_db.table_names() assert ["searchable"] == fresh_db.table_names()
table.enable_fts(["text", "country"], fts_version="FTS4") table.enable_fts(["text", "country"], fts_version="FTS4")
@ -54,7 +54,7 @@ def test_enable_fts(fresh_db):
def test_enable_fts_escape_table_names(fresh_db): def test_enable_fts_escape_table_names(fresh_db):
# Table names with restricted chars are handled correctly. # Table names with restricted chars are handled correctly.
# colons and dots are restricted characters for table names. # colons and dots are restricted characters for table names.
table = fresh_db["http://example.com"] table = fresh_db.table("http://example.com")
table.insert_all(search_records) table.insert_all(search_records)
assert ["http://example.com"] == fresh_db.table_names() assert ["http://example.com"] == fresh_db.table_names()
table.enable_fts(["text", "country"], fts_version="FTS4") table.enable_fts(["text", "country"], fts_version="FTS4")
@ -87,7 +87,7 @@ def test_enable_fts_escape_table_names(fresh_db):
def test_search_duplicate_columns_are_deduped(fresh_db): def test_search_duplicate_columns_are_deduped(fresh_db):
# https://github.com/simonw/sqlite-utils/issues/624 # https://github.com/simonw/sqlite-utils/issues/624
table = fresh_db["t"] table = fresh_db.table("t")
table.insert_all(search_records) table.insert_all(search_records)
table.enable_fts(["text", "country"], fts_version="FTS4") table.enable_fts(["text", "country"], fts_version="FTS4")
rows = list(table.search("tanuki", columns=["text", "text"])) rows = list(table.search("tanuki", columns=["text", "text"]))
@ -100,7 +100,7 @@ def test_search_duplicate_columns_are_deduped(fresh_db):
def test_search_limit_offset(fresh_db): def test_search_limit_offset(fresh_db):
table = fresh_db["t"] table = fresh_db.table("t")
table.insert_all(search_records) table.insert_all(search_records)
table.enable_fts(["text", "country"], fts_version="FTS4") table.enable_fts(["text", "country"], fts_version="FTS4")
assert len(list(table.search("are"))) == 2 assert len(list(table.search("are"))) == 2
@ -113,7 +113,7 @@ def test_search_limit_offset(fresh_db):
def test_search_offset_without_limit(fresh_db): def test_search_offset_without_limit(fresh_db):
table = fresh_db["t"] table = fresh_db.table("t")
table.insert_all(search_records) table.insert_all(search_records)
table.enable_fts(["text", "country"], fts_version="FTS4") table.enable_fts(["text", "country"], fts_version="FTS4")
assert [row["rowid"] for row in table.search("are", order_by="rowid")] == [1, 2] assert [row["rowid"] for row in table.search("are", order_by="rowid")] == [1, 2]
@ -125,7 +125,7 @@ def test_search_offset_without_limit(fresh_db):
@pytest.mark.parametrize("fts_version", ("FTS4", "FTS5")) @pytest.mark.parametrize("fts_version", ("FTS4", "FTS5"))
def test_search_where(fresh_db, fts_version): def test_search_where(fresh_db, fts_version):
table = fresh_db["t"] table = fresh_db.table("t")
table.insert_all(search_records) table.insert_all(search_records)
table.enable_fts(["text", "country"], fts_version=fts_version) table.enable_fts(["text", "country"], fts_version=fts_version)
results = list( results = list(
@ -142,7 +142,7 @@ def test_search_where(fresh_db, fts_version):
def test_search_where_args_disallows_query(fresh_db): def test_search_where_args_disallows_query(fresh_db):
table = fresh_db["t"] table = fresh_db.table("t")
with pytest.raises(ValueError) as ex: with pytest.raises(ValueError) as ex:
list( list(
table.search( table.search(
@ -156,7 +156,7 @@ def test_search_where_args_disallows_query(fresh_db):
def test_search_include_rank(fresh_db): def test_search_include_rank(fresh_db):
table = fresh_db["t"] table = fresh_db.table("t")
table.insert_all(search_records) table.insert_all(search_records)
table.enable_fts(["text", "country"], fts_version="FTS5") table.enable_fts(["text", "country"], fts_version="FTS5")
results = list(table.search("are", include_rank=True)) results = list(table.search("are", include_rank=True))
@ -182,7 +182,7 @@ def test_search_include_rank(fresh_db):
def test_enable_fts_table_names_containing_spaces(fresh_db): def test_enable_fts_table_names_containing_spaces(fresh_db):
table = fresh_db["test"] table = fresh_db.table("test")
table.insert({"column with spaces": "in its name"}) table.insert({"column with spaces": "in its name"})
table.enable_fts(["column with spaces"]) table.enable_fts(["column with spaces"])
assert [ assert [
@ -196,7 +196,7 @@ def test_enable_fts_table_names_containing_spaces(fresh_db):
def test_populate_fts(fresh_db): def test_populate_fts(fresh_db):
table = fresh_db["populatable"] table = fresh_db.table("populatable")
table.insert(search_records[0]) table.insert(search_records[0])
table.enable_fts(["text", "country"], fts_version="FTS4") table.enable_fts(["text", "country"], fts_version="FTS4")
assert [] == list(table.search("trash pandas")) assert [] == list(table.search("trash pandas"))
@ -217,7 +217,7 @@ def test_populate_fts(fresh_db):
def test_populate_fts_escape_table_names(fresh_db): def test_populate_fts_escape_table_names(fresh_db):
# Restricted characters such as colon and dots should be escaped. # Restricted characters such as colon and dots should be escaped.
table = fresh_db["http://example.com"] table = fresh_db.table("http://example.com")
table.insert(search_records[0]) table.insert(search_records[0])
table.enable_fts(["text", "country"], fts_version="FTS4") table.enable_fts(["text", "country"], fts_version="FTS4")
assert [] == list(table.search("trash pandas")) assert [] == list(table.search("trash pandas"))
@ -238,7 +238,7 @@ def test_populate_fts_escape_table_names(fresh_db):
@pytest.mark.parametrize("fts_version", ("4", "5")) @pytest.mark.parametrize("fts_version", ("4", "5"))
def test_fts_tokenize(fresh_db, fts_version): def test_fts_tokenize(fresh_db, fts_version):
table_name = f"searchable_{fts_version}" table_name = f"searchable_{fts_version}"
table = fresh_db[table_name] table = fresh_db.table(table_name)
table.insert_all(search_records) table.insert_all(search_records)
# Test without porter stemming # Test without porter stemming
table.enable_fts( table.enable_fts(
@ -266,7 +266,7 @@ def test_fts_tokenize(fresh_db, fts_version):
def test_fts_tokenize_escaped(fresh_db): def test_fts_tokenize_escaped(fresh_db):
# A malicious tokenize value must not be able to break out of the # A malicious tokenize value must not be able to break out of the
# string literal in the CREATE VIRTUAL TABLE statement. # string literal in the CREATE VIRTUAL TABLE statement.
table = fresh_db["searchable"] table = fresh_db.table("searchable")
table.insert_all(search_records) table.insert_all(search_records)
malicious = "porter'); CREATE TABLE injected(x); --" malicious = "porter'); CREATE TABLE injected(x); --"
with pytest.raises(Exception): with pytest.raises(Exception):
@ -278,7 +278,7 @@ def test_fts_tokenize_escaped(fresh_db):
def test_optimize_fts(fresh_db): def test_optimize_fts(fresh_db):
for fts_version in ("4", "5"): for fts_version in ("4", "5"):
table_name = f"searchable_{fts_version}" table_name = f"searchable_{fts_version}"
table = fresh_db[table_name] table = fresh_db.table(table_name)
table.insert_all(search_records) table.insert_all(search_records)
table.enable_fts(["text", "country"], fts_version=f"FTS{fts_version}") table.enable_fts(["text", "country"], fts_version=f"FTS{fts_version}")
# You can call optimize successfully against the tables OR their _fts equivalents: # You can call optimize successfully against the tables OR their _fts equivalents:
@ -288,11 +288,11 @@ def test_optimize_fts(fresh_db):
"searchable_4_fts", "searchable_4_fts",
"searchable_5_fts", "searchable_5_fts",
): ):
fresh_db[table_name].optimize() fresh_db.table(table_name).optimize()
def test_enable_fts_with_triggers(fresh_db): def test_enable_fts_with_triggers(fresh_db):
table = fresh_db["searchable"] table = fresh_db.table("searchable")
table.insert(search_records[0]) table.insert(search_records[0])
table.enable_fts(["text", "country"], fts_version="FTS4", create_triggers=True) table.enable_fts(["text", "country"], fts_version="FTS4", create_triggers=True)
rows1 = list(table.search("tanuki")) rows1 = list(table.search("tanuki"))
@ -321,7 +321,7 @@ def test_enable_fts_with_triggers(fresh_db):
@pytest.mark.parametrize("create_triggers", [True, False]) @pytest.mark.parametrize("create_triggers", [True, False])
def test_disable_fts(fresh_db, create_triggers): def test_disable_fts(fresh_db, create_triggers):
table = fresh_db["searchable"] table = fresh_db.table("searchable")
table.insert(search_records[0]) table.insert(search_records[0])
table.enable_fts(["text", "country"], create_triggers=create_triggers) table.enable_fts(["text", "country"], create_triggers=create_triggers)
assert { assert {
@ -354,7 +354,7 @@ def test_disable_fts(fresh_db, create_triggers):
def test_rebuild_fts(fresh_db): def test_rebuild_fts(fresh_db):
table = fresh_db["searchable"] table = fresh_db.table("searchable")
table.insert(search_records[0]) table.insert(search_records[0])
table.enable_fts(["text", "country"]) table.enable_fts(["text", "country"])
# Run a search # Run a search
@ -380,7 +380,7 @@ def test_rebuild_fts(fresh_db):
def test_optimize_and_rebuild_fts_commit(tmpdir, method): def test_optimize_and_rebuild_fts_commit(tmpdir, method):
path = str(tmpdir / "test.db") path = str(tmpdir / "test.db")
db = Database(path) db = Database(path)
table = db["searchable"] table = db.table("searchable")
table.insert(search_records[0]) table.insert(search_records[0])
table.enable_fts(["text", "country"]) table.enable_fts(["text", "country"])
getattr(table, method)() getattr(table, method)()
@ -390,16 +390,16 @@ def test_optimize_and_rebuild_fts_commit(tmpdir, method):
table.insert(search_records[1]) table.insert(search_records[1])
db.close() db.close()
db2 = Database(path) db2 = Database(path)
assert db2["searchable"].count == 2 assert db2.table("searchable").count == 2
db2.close() db2.close()
@pytest.mark.parametrize("invalid_table", ["does_not_exist", "not_searchable"]) @pytest.mark.parametrize("invalid_table", ["does_not_exist", "not_searchable"])
def test_rebuild_fts_invalid(fresh_db, invalid_table): def test_rebuild_fts_invalid(fresh_db, invalid_table):
fresh_db["not_searchable"].insert({"foo": "bar"}) fresh_db.table("not_searchable").insert({"foo": "bar"})
# Raise OperationalError on invalid table # Raise OperationalError on invalid table
with pytest.raises(sqlite3.OperationalError): with pytest.raises(sqlite3.OperationalError):
fresh_db[invalid_table].rebuild_fts() fresh_db.table(invalid_table).rebuild_fts()
@pytest.mark.parametrize("fts_version", ["FTS4", "FTS5"]) @pytest.mark.parametrize("fts_version", ["FTS4", "FTS5"])
@ -408,15 +408,17 @@ def test_rebuild_removes_junk_docsize_rows(tmpdir, fts_version):
path = tmpdir / "test.db" path = tmpdir / "test.db"
db = Database(str(path), recursive_triggers=False) db = Database(str(path), recursive_triggers=False)
licenses = [{"key": "apache2", "name": "Apache 2"}, {"key": "bsd", "name": "BSD"}] licenses = [{"key": "apache2", "name": "Apache 2"}, {"key": "bsd", "name": "BSD"}]
db["licenses"].insert_all(licenses, pk="key", replace=True) db.table("licenses").insert_all(licenses, pk="key", replace=True)
db["licenses"].enable_fts(["name"], create_triggers=True, fts_version=fts_version) db.table("licenses").enable_fts(
assert db["licenses_fts_docsize"].count == 2 ["name"], create_triggers=True, fts_version=fts_version
)
assert db.table("licenses_fts_docsize").count == 2
# Bug: insert with replace increases the number of rows in _docsize: # Bug: insert with replace increases the number of rows in _docsize:
db["licenses"].insert_all(licenses, pk="key", replace=True) db.table("licenses").insert_all(licenses, pk="key", replace=True)
assert db["licenses_fts_docsize"].count == 4 assert db.table("licenses_fts_docsize").count == 4
# rebuild should fix this: # rebuild should fix this:
db["licenses_fts"].rebuild_fts() db.table("licenses_fts").rebuild_fts()
assert db["licenses_fts_docsize"].count == 2 assert db.table("licenses_fts_docsize").count == 2
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -430,7 +432,7 @@ def test_rebuild_removes_junk_docsize_rows(tmpdir, fts_version):
) )
def test_enable_fts_replace(kwargs): def test_enable_fts_replace(kwargs):
db = Database(memory=True) db = Database(memory=True)
db["books"].insert( db.table("books").insert(
{ {
"id": 1, "id": 1,
"title": "Habits of Australian Marsupials", "title": "Habits of Australian Marsupials",
@ -438,31 +440,31 @@ def test_enable_fts_replace(kwargs):
}, },
pk="id", pk="id",
) )
db["books"].enable_fts(["title", "author"]) db.table("books").enable_fts(["title", "author"])
assert not db["books"].triggers assert not db.table("books").triggers
assert db["books_fts"].columns_dict.keys() == {"title", "author"} assert db.table("books_fts").columns_dict.keys() == {"title", "author"}
assert "FTS5" in db["books_fts"].schema assert "FTS5" in db.table("books_fts").schema
assert "porter" not in db["books_fts"].schema assert "porter" not in db.table("books_fts").schema
# Now modify the FTS configuration # Now modify the FTS configuration
should_have_changed_columns = "columns" in kwargs should_have_changed_columns = "columns" in kwargs
if "columns" not in kwargs: if "columns" not in kwargs:
kwargs["columns"] = ["title", "author"] kwargs["columns"] = ["title", "author"]
db["books"].enable_fts(**kwargs, replace=True) db.table("books").enable_fts(**kwargs, replace=True)
# Check that the new configuration is correct # Check that the new configuration is correct
if should_have_changed_columns: if should_have_changed_columns:
assert db["books_fts"].columns_dict.keys() == {"title"} assert db.table("books_fts").columns_dict.keys() == {"title"}
if "create_triggers" in kwargs: if "create_triggers" in kwargs:
assert db["books"].triggers assert db.table("books").triggers
if "fts_version" in kwargs: if "fts_version" in kwargs:
assert "FTS4" in db["books_fts"].schema assert "FTS4" in db.table("books_fts").schema
if "tokenize" in kwargs: if "tokenize" in kwargs:
assert "porter" in db["books_fts"].schema assert "porter" in db.table("books_fts").schema
def test_enable_fts_replace_does_nothing_if_args_the_same(): def test_enable_fts_replace_does_nothing_if_args_the_same():
queries = [] queries = []
db = Database(memory=True, tracer=lambda sql, params: queries.append((sql, params))) db = Database(memory=True, tracer=lambda sql, params: queries.append((sql, params)))
db["books"].insert( db.table("books").insert(
{ {
"id": 1, "id": 1,
"title": "Habits of Australian Marsupials", "title": "Habits of Australian Marsupials",
@ -470,17 +472,19 @@ def test_enable_fts_replace_does_nothing_if_args_the_same():
}, },
pk="id", pk="id",
) )
db["books"].enable_fts(["title", "author"], create_triggers=True) db.table("books").enable_fts(["title", "author"], create_triggers=True)
queries.clear() queries.clear()
# Running that again shouldn't run much SQL: # Running that again shouldn't run much SQL:
db["books"].enable_fts(["title", "author"], create_triggers=True, replace=True) db.table("books").enable_fts(
["title", "author"], create_triggers=True, replace=True
)
# The only SQL that executed should be select statements # The only SQL that executed should be select statements
assert all(q[0].startswith("select ") for q in queries) assert all(q[0].startswith("select ") for q in queries)
def test_enable_fts_replace_handles_legacy_bracket_quoted_content_table(): def test_enable_fts_replace_handles_legacy_bracket_quoted_content_table():
db = Database(memory=True) db = Database(memory=True)
db["books"].insert( db.table("books").insert(
{ {
"id": 1, "id": 1,
"title": "Habits of Australian Marsupials", "title": "Habits of Australian Marsupials",
@ -495,10 +499,10 @@ def test_enable_fts_replace_handles_legacy_bracket_quoted_content_table():
); );
""") """)
db["books"].enable_fts(["title", "author"], replace=True) db.table("books").enable_fts(["title", "author"], replace=True)
assert db["books_fts"].columns_dict.keys() == {"title", "author"} assert db.table("books_fts").columns_dict.keys() == {"title", "author"}
assert 'content="books"' in db["books_fts"].schema assert 'content="books"' in db.table("books_fts").schema
def test_view_has_no_enable_fts(): def test_view_has_no_enable_fts():
@ -506,7 +510,7 @@ def test_view_has_no_enable_fts():
db.create_view("hello", "select 1 + 1") db.create_view("hello", "select 1 + 1")
# Views deliberately do not have an enable_fts() method # Views deliberately do not have an enable_fts() method
with pytest.raises(AttributeError): with pytest.raises(AttributeError):
db["hello"].enable_fts() # type: ignore[union-attr] db.view("hello").enable_fts() # type: ignore[union-attr]
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -712,14 +716,14 @@ def test_view_has_no_enable_fts():
) )
def test_search_sql(kwargs, fts, expected): def test_search_sql(kwargs, fts, expected):
db = Database(memory=True) db = Database(memory=True)
db["books"].insert( db.table("books").insert(
{ {
"title": "Habits of Australian Marsupials", "title": "Habits of Australian Marsupials",
"author": "Marlee Hawkins", "author": "Marlee Hawkins",
} }
) )
db["books"].enable_fts(["title", "author"], fts_version=fts) db.table("books").enable_fts(["title", "author"], fts_version=fts)
sql = db["books"].search_sql(**kwargs) sql = db.table("books").search_sql(**kwargs)
assert sql == expected assert sql == expected
@ -740,7 +744,7 @@ def test_search_sql(kwargs, fts, expected):
), ),
) )
def test_quote_fts_query(fresh_db, input, expected): def test_quote_fts_query(fresh_db, input, expected):
table = fresh_db["searchable"] table = fresh_db.table("searchable")
table.insert_all(search_records) table.insert_all(search_records)
table.enable_fts(["text", "country"]) table.enable_fts(["text", "country"])
quoted = fresh_db.quote_fts(input) quoted = fresh_db.quote_fts(input)
@ -750,7 +754,7 @@ def test_quote_fts_query(fresh_db, input, expected):
def test_search_quote(fresh_db): def test_search_quote(fresh_db):
table = fresh_db["searchable"] table = fresh_db.table("searchable")
table.insert_all(search_records) table.insert_all(search_records)
table.enable_fts(["text", "country"]) table.enable_fts(["text", "country"])
query = "cat's" query = "cat's"
@ -763,7 +767,7 @@ def test_search_quote(fresh_db):
def test_enable_fts_cli_on_view_errors(tmpdir): def test_enable_fts_cli_on_view_errors(tmpdir):
db_path = str(tmpdir / "test.db") db_path = str(tmpdir / "test.db")
db = Database(db_path) db = Database(db_path)
db["t"].insert({"text": "hello"}) db.table("t").insert({"text": "hello"})
db.create_view("v", "select * from t") db.create_view("v", "select * from t")
db.close() db.close()
from click.testing import CliRunner from click.testing import CliRunner

View file

@ -4,14 +4,14 @@ from sqlite_utils.db import NotFoundError
def test_get_rowid(fresh_db): def test_get_rowid(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
cleo = {"name": "Cleo", "age": 4} cleo = {"name": "Cleo", "age": 4}
row_id = dogs.insert(cleo).last_rowid row_id = dogs.insert(cleo).last_rowid
assert cleo == dogs.get(row_id) assert cleo == dogs.get(row_id)
def test_get_primary_key(fresh_db): def test_get_primary_key(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
cleo = {"name": "Cleo", "age": 4, "id": 5} cleo = {"name": "Cleo", "age": 4, "id": 5}
last_pk = dogs.insert(cleo, pk="id").last_pk last_pk = dogs.insert(cleo, pk="id").last_pk
assert 5 == last_pk assert 5 == last_pk
@ -23,10 +23,10 @@ def test_get_primary_key(fresh_db):
[(100, None), (None, None), ((1, 2), "Need 1 primary key value"), ("2", None)], [(100, None), (None, None), ((1, 2), "Need 1 primary key value"), ("2", None)],
) )
def test_get_not_found(argument, expected_msg, fresh_db): def test_get_not_found(argument, expected_msg, fresh_db):
fresh_db["dogs"].insert( fresh_db.table("dogs").insert(
{"id": 1, "name": "Cleo", "age": 4, "is_good": True}, pk="id" {"id": 1, "name": "Cleo", "age": 4, "is_good": True}, pk="id"
) )
with pytest.raises(NotFoundError) as excinfo: with pytest.raises(NotFoundError) as excinfo:
fresh_db["dogs"].get(argument) fresh_db.table("dogs").get(argument)
if expected_msg is not None: if expected_msg is not None:
assert expected_msg == excinfo.value.args[0] assert expected_msg == excinfo.value.args[0]

View file

@ -45,7 +45,7 @@ def test_add_geometry_column():
coord_dimension="XY", coord_dimension="XY",
) )
assert db["geometry_columns"].get(["locations", "geometry"]) == { assert db.table("geometry_columns").get(["locations", "geometry"]) == {
"f_table_name": "locations", "f_table_name": "locations",
"f_geometry_column": "geometry", "f_geometry_column": "geometry",
"geometry_type": 1, # point "geometry_type": 1, # point
@ -133,7 +133,7 @@ def test_cli_add_geometry_column(tmpdir):
db = Database(str(db_path)) db = Database(str(db_path))
db.init_spatialite() db.init_spatialite()
table = db["locations"].create({"name": str}) table = db.table("locations").create({"name": str})
result = CliRunner().invoke( result = CliRunner().invoke(
cli, cli,
@ -149,7 +149,7 @@ def test_cli_add_geometry_column(tmpdir):
assert result.exit_code == 0 assert result.exit_code == 0
assert db["geometry_columns"].get(["locations", "geometry"]) == { assert db.table("geometry_columns").get(["locations", "geometry"]) == {
"f_table_name": "locations", "f_table_name": "locations",
"f_geometry_column": "geometry", "f_geometry_column": "geometry",
"geometry_type": 1, # point "geometry_type": 1, # point
@ -164,7 +164,7 @@ def test_cli_add_geometry_column_options(tmpdir):
db_path = tmpdir / "spatial.db" db_path = tmpdir / "spatial.db"
db = Database(str(db_path)) db = Database(str(db_path))
db.init_spatialite() db.init_spatialite()
table = db["locations"].create({"name": str}) table = db.table("locations").create({"name": str})
result = CliRunner().invoke( result = CliRunner().invoke(
cli, cli,
@ -183,7 +183,7 @@ def test_cli_add_geometry_column_options(tmpdir):
assert result.exit_code == 0 assert result.exit_code == 0
assert db["geometry_columns"].get(["locations", "geometry"]) == { assert db.table("geometry_columns").get(["locations", "geometry"]) == {
"f_table_name": "locations", "f_table_name": "locations",
"f_geometry_column": "geometry", "f_geometry_column": "geometry",
"geometry_type": 3, # polygon "geometry_type": 3, # polygon
@ -202,7 +202,7 @@ def test_cli_add_geometry_column_invalid_type(tmpdir):
db = Database(str(db_path)) db = Database(str(db_path))
db.init_spatialite() db.init_spatialite()
table = db["locations"].create({"name": str}) table = db.table("locations").create({"name": str})
result = CliRunner().invoke( result = CliRunner().invoke(
cli, cli,
@ -225,7 +225,7 @@ def test_cli_create_spatial_index(tmpdir):
db = Database(str(db_path)) db = Database(str(db_path))
db.init_spatialite() db.init_spatialite()
table = db["locations"].create({"name": str}) table = db.table("locations").create({"name": str})
table.add_geometry_column("geometry", "POINT") table.add_geometry_column("geometry", "POINT")
result = CliRunner().invoke( result = CliRunner().invoke(

View file

@ -11,8 +11,8 @@ def test_roundtrip_integers(integer):
row = { row = {
"integer": integer, "integer": integer,
} }
db["test"].insert(row) db.table("test").insert(row)
assert list(db["test"].rows) == [row] assert list(db.table("test").rows) == [row]
@given(st.text()) @given(st.text())
@ -21,8 +21,8 @@ def test_roundtrip_text(text):
row = { row = {
"text": text, "text": text,
} }
db["test"].insert(row) db.table("test").insert(row)
assert list(db["test"].rows) == [row] assert list(db.table("test").rows) == [row]
@given(st.binary(max_size=1024 * 1024)) @given(st.binary(max_size=1024 * 1024))
@ -31,8 +31,8 @@ def test_roundtrip_binary(binary):
row = { row = {
"binary": binary, "binary": binary,
} }
db["test"].insert(row) db.table("test").insert(row)
assert list(db["test"].rows) == [row] assert list(db.table("test").rows) == [row]
@given(st.floats(allow_nan=False)) @given(st.floats(allow_nan=False))
@ -41,5 +41,5 @@ def test_roundtrip_floats(floats):
row = { row = {
"floats": floats, "floats": floats,
} }
db["test"].insert(row) db.table("test").insert(row)
assert list(db["test"].rows) == [row] assert list(db.table("test").rows) == [row]

View file

@ -57,7 +57,7 @@ def test_insert_files(silent, pk_args, expected_pks):
) )
assert result.exit_code == 0, result.stdout assert result.exit_code == 0, result.stdout
db = Database(db_path) db = Database(db_path)
rows_by_path = {r["path"]: r for r in db["files"].rows} rows_by_path = {r["path"]: r for r in db.table("files").rows}
one, two, three = ( one, two, three = (
rows_by_path["one.txt"], rows_by_path["one.txt"],
rows_by_path["two.txt"], rows_by_path["two.txt"],
@ -114,7 +114,7 @@ def test_insert_files(silent, pk_args, expected_pks):
for colname, expected_type in expected_types.items(): for colname, expected_type in expected_types.items():
for row in (one, two, three): for row in (one, two, three):
assert isinstance(row[colname], expected_type) assert isinstance(row[colname], expected_type)
assert set(db["files"].pks) == set(expected_pks) assert set(db.table("files").pks) == set(expected_pks)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -144,7 +144,7 @@ def test_insert_files_stdin(use_text, encoding, input, expected):
) )
assert result.exit_code == 0, result.stdout assert result.exit_code == 0, result.stdout
db = Database(db_path) db = Database(db_path)
row = next(iter(db["files"].rows)) row = next(iter(db.table("files").rows))
key = "content" key = "content"
if use_text: if use_text:
key = "content_text" key = "content_text"

View file

@ -1,6 +1,6 @@
import pytest import pytest
from sqlite_utils.db import Check, Database, Index, View, XIndex, XIndexColumn from sqlite_utils.db import Check, Database, Index, Table, View, XIndex, XIndexColumn
def _check_supports_strict(): def _check_supports_strict():
@ -21,10 +21,10 @@ def test_view_names(fresh_db):
def test_table_names_fts4(existing_db): def test_table_names_fts4(existing_db):
existing_db["woo"].insert({"title": "Hello"}).enable_fts( existing_db.table("woo").insert({"title": "Hello"}).enable_fts(
["title"], fts_version="FTS4" ["title"], fts_version="FTS4"
) )
existing_db["woo2"].insert({"title": "Hello"}).enable_fts( existing_db.table("woo2").insert({"title": "Hello"}).enable_fts(
["title"], fts_version="FTS5" ["title"], fts_version="FTS5"
) )
assert ["woo_fts"] == existing_db.table_names(fts4=True) assert ["woo_fts"] == existing_db.table_names(fts4=True)
@ -32,17 +32,17 @@ def test_table_names_fts4(existing_db):
def test_detect_fts(existing_db): def test_detect_fts(existing_db):
existing_db["woo"].insert({"title": "Hello"}).enable_fts( existing_db.table("woo").insert({"title": "Hello"}).enable_fts(
["title"], fts_version="FTS4" ["title"], fts_version="FTS4"
) )
existing_db["woo2"].insert({"title": "Hello"}).enable_fts( existing_db.table("woo2").insert({"title": "Hello"}).enable_fts(
["title"], fts_version="FTS5" ["title"], fts_version="FTS5"
) )
assert "woo_fts" == existing_db["woo"].detect_fts() assert "woo_fts" == existing_db.table("woo").detect_fts()
assert "woo_fts" == existing_db["woo_fts"].detect_fts() assert "woo_fts" == existing_db.table("woo_fts").detect_fts()
assert "woo2_fts" == existing_db["woo2"].detect_fts() assert "woo2_fts" == existing_db.table("woo2").detect_fts()
assert "woo2_fts" == existing_db["woo2_fts"].detect_fts() assert "woo2_fts" == existing_db.table("woo2_fts").detect_fts()
assert existing_db["foo"].detect_fts() is None assert existing_db.table("foo").detect_fts() is None
@pytest.mark.parametrize("reverse_order", (True, False)) @pytest.mark.parametrize("reverse_order", (True, False))
@ -52,14 +52,14 @@ def test_detect_fts_similar_tables(fresh_db, reverse_order):
if reverse_order: if reverse_order:
table1, table2 = table2, table1 table1, table2 = table2, table1
fresh_db[table1].insert({"title": "Hello"}).enable_fts( fresh_db.table(table1).insert({"title": "Hello"}).enable_fts(
["title"], fts_version="FTS4" ["title"], fts_version="FTS4"
) )
fresh_db[table2].insert({"title": "Hello"}).enable_fts( fresh_db.table(table2).insert({"title": "Hello"}).enable_fts(
["title"], fts_version="FTS4" ["title"], fts_version="FTS4"
) )
assert fresh_db[table1].detect_fts() == f"{table1}_fts" assert fresh_db.table(table1).detect_fts() == f"{table1}_fts"
assert fresh_db[table2].detect_fts() == f"{table2}_fts" assert fresh_db.table(table2).detect_fts() == f"{table2}_fts"
def test_tables(existing_db): def test_tables(existing_db):
@ -77,26 +77,34 @@ def test_views(fresh_db):
assert view.columns_dict == {"1": str} assert view.columns_dict == {"1": str}
def test_getitem_returns_table_or_view(fresh_db):
fresh_db.table("items").insert({"id": 1}, pk="id")
fresh_db.create_view("item_ids", "select id from items")
assert isinstance(fresh_db["items"], Table)
assert isinstance(fresh_db["item_ids"], View)
def test_count(existing_db): def test_count(existing_db):
assert existing_db["foo"].count == 3 assert existing_db.table("foo").count == 3
assert existing_db["foo"].count_where() == 3 assert existing_db.table("foo").count_where() == 3
assert existing_db["foo"].execute_count() == 3 assert existing_db.table("foo").execute_count() == 3
def test_count_where(existing_db): def test_count_where(existing_db):
assert existing_db["foo"].count_where("text != ?", ["two"]) == 2 assert existing_db.table("foo").count_where("text != ?", ["two"]) == 2
assert existing_db["foo"].count_where("text != :t", {"t": "two"}) == 2 assert existing_db.table("foo").count_where("text != :t", {"t": "two"}) == 2
def test_columns(existing_db): def test_columns(existing_db):
table = existing_db["foo"] table = existing_db.table("foo")
assert [{"name": "text", "type": "TEXT"}] == [ assert [{"name": "text", "type": "TEXT"}] == [
{"name": col.name, "type": col.type} for col in table.columns {"name": col.name, "type": col.type} for col in table.columns
] ]
def test_table_schema(existing_db): def test_table_schema(existing_db):
assert existing_db["foo"].schema == "CREATE TABLE foo (text TEXT)" assert existing_db.table("foo").schema == "CREATE TABLE foo (text TEXT)"
def test_database_schema(existing_db): def test_database_schema(existing_db):
@ -104,9 +112,9 @@ def test_database_schema(existing_db):
def test_table_repr(fresh_db): def test_table_repr(fresh_db):
table = fresh_db["dogs"].insert({"name": "Cleo", "age": 4}) table = fresh_db.table("dogs").insert({"name": "Cleo", "age": 4})
assert "<Table dogs (name, age)>" == repr(table) assert "<Table dogs (name, age)>" == repr(table)
assert "<Table cats (does not exist yet)>" == repr(fresh_db["cats"]) assert "<Table cats (does not exist yet)>" == repr(fresh_db.table("cats"))
def test_indexes(fresh_db): def test_indexes(fresh_db):
@ -125,7 +133,7 @@ def test_indexes(fresh_db):
columns=["c2", "c3"], columns=["c2", "c3"],
), ),
Index(seq=1, name="Gosh_c1", unique=0, origin="c", partial=0, columns=["c1"]), Index(seq=1, name="Gosh_c1", unique=0, origin="c", partial=0, columns=["c1"]),
] == fresh_db["Gosh"].indexes ] == fresh_db.table("Gosh").indexes
def test_xindexes(fresh_db): def test_xindexes(fresh_db):
@ -134,7 +142,7 @@ def test_xindexes(fresh_db):
create index Gosh_c1 on Gosh(c1); create index Gosh_c1 on Gosh(c1);
create index Gosh_c2c3 on Gosh(c2, c3 desc); create index Gosh_c2c3 on Gosh(c2, c3 desc);
""") """)
assert fresh_db["Gosh"].xindexes == [ assert fresh_db.table("Gosh").xindexes == [
XIndex( XIndex(
name="Gosh_c2c3", name="Gosh_c2c3",
columns=[ columns=[
@ -166,15 +174,15 @@ def test_xindexes(fresh_db):
def test_guess_foreign_table(fresh_db, column, expected_table_guess): def test_guess_foreign_table(fresh_db, column, expected_table_guess):
fresh_db.create_table("authors", {"name": str}) fresh_db.create_table("authors", {"name": str})
fresh_db.create_table("genre", {"name": str}) fresh_db.create_table("genre", {"name": str})
assert expected_table_guess == fresh_db["books"].guess_foreign_table(column) assert expected_table_guess == fresh_db.table("books").guess_foreign_table(column)
@pytest.mark.parametrize( @pytest.mark.parametrize(
"pk,expected", ((None, ["rowid"]), ("id", ["id"]), (["id", "id2"], ["id", "id2"])) "pk,expected", ((None, ["rowid"]), ("id", ["id"]), (["id", "id2"], ["id", "id2"]))
) )
def test_pks(fresh_db, pk, expected): def test_pks(fresh_db, pk, expected):
fresh_db["foo"].insert_all([{"id": 1, "id2": 2}], pk=pk) fresh_db.table("foo").insert_all([{"id": 1, "id2": 2}], pk=pk)
assert expected == fresh_db["foo"].pks assert expected == fresh_db.table("foo").pks
def test_checks(fresh_db): def test_checks(fresh_db):
@ -185,7 +193,7 @@ def test_checks(fresh_db):
CONSTRAINT within_maximum CHECK(score <= maximum) CONSTRAINT within_maximum CHECK(score <= maximum)
) )
""") """)
scores = fresh_db["scores"] scores = fresh_db.table("scores")
expected_column = Check("score > 0", name="positive", column="score") expected_column = Check("score > 0", name="positive", column="score")
expected_table = Check("score <= maximum", name="within_maximum") expected_table = Check("score <= maximum", name="within_maximum")
assert scores.checks == [expected_column, expected_table] assert scores.checks == [expected_column, expected_table]
@ -195,26 +203,26 @@ def test_checks(fresh_db):
def test_checks_nonexistent_and_virtual_tables(fresh_db): def test_checks_nonexistent_and_virtual_tables(fresh_db):
assert fresh_db["does_not_exist"].checks == [] assert fresh_db.table("does_not_exist").checks == []
fresh_db["searchable"].insert({"text": "hello"}).enable_fts( fresh_db.table("searchable").insert({"text": "hello"}).enable_fts(
["text"], fts_version="FTS5" ["text"], fts_version="FTS5"
) )
assert fresh_db["searchable_fts"].checks == [] assert fresh_db.table("searchable_fts").checks == []
def test_triggers_and_triggers_dict(fresh_db): def test_triggers_and_triggers_dict(fresh_db):
assert [] == fresh_db.triggers assert [] == fresh_db.triggers
authors = fresh_db["authors"] authors = fresh_db.table("authors")
authors.insert_all( authors.insert_all(
[ [
{"name": "Frank Herbert", "famous_works": "Dune"}, {"name": "Frank Herbert", "famous_works": "Dune"},
{"name": "Neal Stephenson", "famous_works": "Cryptonomicon"}, {"name": "Neal Stephenson", "famous_works": "Cryptonomicon"},
] ]
) )
fresh_db["other"].insert({"foo": "bar"}) fresh_db.table("other").insert({"foo": "bar"})
assert authors.triggers == [] assert authors.triggers == []
assert authors.triggers_dict == {} assert authors.triggers_dict == {}
assert fresh_db["other"].triggers == [] assert fresh_db.table("other").triggers == []
assert fresh_db.triggers_dict == {} assert fresh_db.triggers_dict == {}
authors.enable_fts( authors.enable_fts(
["name", "famous_works"], fts_version="FTS4", create_triggers=True ["name", "famous_works"], fts_version="FTS4", create_triggers=True
@ -226,7 +234,7 @@ def test_triggers_and_triggers_dict(fresh_db):
} }
assert expected_triggers == {(t.name, t.table) for t in fresh_db.triggers} assert expected_triggers == {(t.name, t.table) for t in fresh_db.triggers}
assert expected_triggers == { assert expected_triggers == {
(t.name, t.table) for t in fresh_db["authors"].triggers (t.name, t.table) for t in fresh_db.table("authors").triggers
} }
expected_triggers = { expected_triggers = {
"authors_ai": ( "authors_ai": (
@ -246,13 +254,13 @@ def test_triggers_and_triggers_dict(fresh_db):
), ),
} }
assert authors.triggers_dict == expected_triggers assert authors.triggers_dict == expected_triggers
assert fresh_db["other"].triggers == [] assert fresh_db.table("other").triggers == []
assert fresh_db["other"].triggers_dict == {} assert fresh_db.table("other").triggers_dict == {}
assert fresh_db.triggers_dict == expected_triggers assert fresh_db.triggers_dict == expected_triggers
def test_has_counts_triggers(fresh_db): def test_has_counts_triggers(fresh_db):
authors = fresh_db["authors"] authors = fresh_db.table("authors")
authors.insert({"name": "Frank Herbert"}) authors.insert({"name": "Frank Herbert"})
assert not authors.has_counts_triggers assert not authors.has_counts_triggers
authors.enable_counts() authors.enable_counts()
@ -301,14 +309,14 @@ def test_has_counts_triggers(fresh_db):
) )
def test_virtual_table_using(fresh_db, sql, expected_name, expected_using): def test_virtual_table_using(fresh_db, sql, expected_name, expected_using):
fresh_db.execute(sql) fresh_db.execute(sql)
assert fresh_db[expected_name].virtual_table_using == expected_using assert fresh_db.table(expected_name).virtual_table_using == expected_using
def test_use_rowid(fresh_db): def test_use_rowid(fresh_db):
fresh_db["rowid_table"].insert({"name": "Cleo"}) fresh_db.table("rowid_table").insert({"name": "Cleo"})
fresh_db["regular_table"].insert({"id": 1, "name": "Cleo"}, pk="id") fresh_db.table("regular_table").insert({"id": 1, "name": "Cleo"}, pk="id")
assert fresh_db["rowid_table"].use_rowid assert fresh_db.table("rowid_table").use_rowid
assert not fresh_db["regular_table"].use_rowid assert not fresh_db.table("regular_table").use_rowid
@pytest.mark.skipif( @pytest.mark.skipif(
@ -327,7 +335,7 @@ def test_use_rowid(fresh_db):
) )
def test_table_strict(fresh_db, create_table, expected_strict): def test_table_strict(fresh_db, create_table, expected_strict):
fresh_db.execute(create_table) fresh_db.execute(create_table)
table = fresh_db["t"] table = fresh_db.table("t")
assert table.strict == expected_strict assert table.strict == expected_strict
@ -343,10 +351,10 @@ def test_table_strict(fresh_db, create_table, expected_strict):
), ),
) )
def test_table_default_values(fresh_db, value): def test_table_default_values(fresh_db, value):
fresh_db["default_values"].insert( fresh_db.table("default_values").insert(
{"nodefault": 1, "value": value}, defaults={"value": value} {"nodefault": 1, "value": value}, defaults={"value": value}
) )
default_values = fresh_db["default_values"].default_values default_values = fresh_db.table("default_values").default_values
assert default_values == {"value": value} assert default_values == {"value": value}
@ -356,8 +364,8 @@ def test_table_default_values_escaped_quotes(fresh_db):
fresh_db.execute( fresh_db.execute(
"create table t (id integer primary key, name text default 'O''Brien')" "create table t (id integer primary key, name text default 'O''Brien')"
) )
assert "default 'O''Brien'" in fresh_db["t"].schema assert "default 'O''Brien'" in fresh_db.table("t").schema
assert fresh_db["t"].default_values == {"name": "O'Brien"} assert fresh_db.table("t").default_values == {"name": "O'Brien"}
def test_pks_use_primary_key_declaration_order(fresh_db): def test_pks_use_primary_key_declaration_order(fresh_db):
@ -365,11 +373,11 @@ def test_pks_use_primary_key_declaration_order(fresh_db):
# pks must follow the declaration order, which is what SQLite uses to # pks must follow the declaration order, which is what SQLite uses to
# resolve implicit foreign key references and compound pk lookups # resolve implicit foreign key references and compound pk lookups
fresh_db.execute("create table t (b text, a text, primary key (a, b))") fresh_db.execute("create table t (b text, a text, primary key (a, b))")
assert fresh_db["t"].pks == ["a", "b"] assert fresh_db.table("t").pks == ["a", "b"]
def test_transform_preserves_compound_pk_declaration_order(fresh_db): def test_transform_preserves_compound_pk_declaration_order(fresh_db):
fresh_db.execute("create table t (a text, b text, c text, primary key (b, a))") fresh_db.execute("create table t (a text, b text, c text, primary key (b, a))")
fresh_db["t"].transform(drop={"c"}) fresh_db.table("t").transform(drop={"c"})
assert fresh_db["t"].pks == ["b", "a"] assert fresh_db.table("t").pks == ["b", "a"]
assert 'PRIMARY KEY ("b", "a")' in fresh_db["t"].schema assert 'PRIMARY KEY ("b", "a")' in fresh_db.table("t").schema

View file

@ -19,9 +19,9 @@ def test_insert_all_list_mode_basic():
yield [2, "Bob", 25] yield [2, "Bob", 25]
yield [3, "Charlie", 35] yield [3, "Charlie", 35]
db["people"].insert_all(data_generator()) db.table("people").insert_all(data_generator())
rows = list(db["people"].rows) rows = list(db.table("people").rows)
assert len(rows) == 3 assert len(rows) == 3
assert rows[0] == {"id": 1, "name": "Alice", "age": 30} assert rows[0] == {"id": 1, "name": "Alice", "age": 30}
assert rows[1] == {"id": 2, "name": "Bob", "age": 25} assert rows[1] == {"id": 2, "name": "Bob", "age": 25}
@ -37,10 +37,10 @@ def test_insert_all_list_mode_with_pk():
yield [1, "Alice", 95] yield [1, "Alice", 95]
yield [2, "Bob", 87] yield [2, "Bob", 87]
db["scores"].insert_all(data_generator(), pk="id") db.table("scores").insert_all(data_generator(), pk="id")
assert db["scores"].pks == ["id"] assert db.table("scores").pks == ["id"]
rows = list(db["scores"].rows) rows = list(db.table("scores").rows)
assert len(rows) == 2 assert len(rows) == 2
@ -54,7 +54,7 @@ def test_upsert_all_list_mode():
yield [1, "Alice", 100] yield [1, "Alice", 100]
yield [2, "Bob", 200] yield [2, "Bob", 200]
db["data"].insert_all(initial_data(), pk="id") db.table("data").insert_all(initial_data(), pk="id")
# Upsert with some updates and new records # Upsert with some updates and new records
def upsert_data(): def upsert_data():
@ -62,9 +62,9 @@ def test_upsert_all_list_mode():
yield [1, "Alice", 150] # Update existing yield [1, "Alice", 150] # Update existing
yield [3, "Charlie", 300] # Insert new yield [3, "Charlie", 300] # Insert new
db["data"].upsert_all(upsert_data(), pk="id") db.table("data").upsert_all(upsert_data(), pk="id")
rows = list(db["data"].rows_where(order_by="id")) rows = list(db.table("data").rows_where(order_by="id"))
assert len(rows) == 3 assert len(rows) == 3
assert rows[0] == {"id": 1, "name": "Alice", "value": 150} assert rows[0] == {"id": 1, "name": "Alice", "value": 150}
assert rows[1] == {"id": 2, "name": "Bob", "value": 200} assert rows[1] == {"id": 2, "name": "Bob", "value": 200}
@ -81,9 +81,9 @@ def test_list_mode_with_various_types():
yield [2, "Bob", 87.3, False] yield [2, "Bob", 87.3, False]
yield [3, "Charlie", None, True] yield [3, "Charlie", None, True]
db["mixed"].insert_all(data_generator()) db.table("mixed").insert_all(data_generator())
rows = list(db["mixed"].rows) rows = list(db.table("mixed").rows)
assert len(rows) == 3 assert len(rows) == 3
assert rows[0]["score"] == 95.5 assert rows[0]["score"] == 95.5
assert rows[1]["active"] == 0 # SQLite stores boolean as int assert rows[1]["active"] == 0 # SQLite stores boolean as int
@ -99,7 +99,7 @@ def test_list_mode_error_non_string_columns():
yield ["a", "b", "c"] yield ["a", "b", "c"]
with pytest.raises(ValueError, match="must be a list of column name strings"): with pytest.raises(ValueError, match="must be a list of column name strings"):
db["bad"].insert_all(bad_data()) db.table("bad").insert_all(bad_data())
def test_list_mode_error_mixed_types(): def test_list_mode_error_mixed_types():
@ -111,7 +111,7 @@ def test_list_mode_error_mixed_types():
yield {"id": 1, "name": "Alice"} # Should be a list, not dict yield {"id": 1, "name": "Alice"} # Should be a list, not dict
with pytest.raises(ValueError, match="must also be lists"): with pytest.raises(ValueError, match="must also be lists"):
db["bad"].insert_all(bad_data()) db.table("bad").insert_all(bad_data())
def test_list_mode_empty_after_headers(): def test_list_mode_empty_after_headers():
@ -122,9 +122,9 @@ def test_list_mode_empty_after_headers():
yield ["id", "name", "age"] yield ["id", "name", "age"]
# No data rows # No data rows
result = db["people"].insert_all(data_generator()) result = db.table("people").insert_all(data_generator())
assert result is not None assert result is not None
assert not db["people"].exists() assert not db.table("people").exists()
def test_list_mode_batch_processing(): def test_list_mode_batch_processing():
@ -136,7 +136,7 @@ def test_list_mode_batch_processing():
for i in range(1000): for i in range(1000):
yield [i, f"value_{i}"] yield [i, f"value_{i}"]
db["large"].insert_all(large_data(), batch_size=100) db.table("large").insert_all(large_data(), batch_size=100)
count = db.execute("SELECT COUNT(*) as c FROM large").fetchone()[0] count = db.execute("SELECT COUNT(*) as c FROM large").fetchone()[0]
assert count == 1000 assert count == 1000
@ -152,9 +152,9 @@ def test_list_mode_shorter_rows():
yield [2, "Bob"] # Missing age and city yield [2, "Bob"] # Missing age and city
yield [3, "Charlie", 35] # Missing city yield [3, "Charlie", 35] # Missing city
db["people"].insert_all(data_generator()) db.table("people").insert_all(data_generator())
rows = list(db["people"].rows_where(order_by="id")) rows = list(db.table("people").rows_where(order_by="id"))
assert rows[0] == {"id": 1, "name": "Alice", "age": 30, "city": "NYC"} assert rows[0] == {"id": 1, "name": "Alice", "age": 30, "city": "NYC"}
assert rows[1] == {"id": 2, "name": "Bob", "age": None, "city": None} assert rows[1] == {"id": 2, "name": "Bob", "age": None, "city": None}
assert rows[2] == {"id": 3, "name": "Charlie", "age": 35, "city": None} assert rows[2] == {"id": 3, "name": "Charlie", "age": 35, "city": None}
@ -170,9 +170,9 @@ def test_backwards_compatibility_dict_mode():
{"id": 2, "name": "Bob", "age": 25}, {"id": 2, "name": "Bob", "age": 25},
] ]
db["people"].insert_all(data) db.table("people").insert_all(data)
rows = list(db["people"].rows) rows = list(db.table("people").rows)
assert len(rows) == 2 assert len(rows) == 2
assert rows[0] == {"id": 1, "name": "Alice", "age": 30} assert rows[0] == {"id": 1, "name": "Alice", "age": 30}
@ -189,9 +189,9 @@ def test_insert_all_tuple_mode_basic():
yield (2, "Bob", 25) yield (2, "Bob", 25)
yield (3, "Charlie", 35) yield (3, "Charlie", 35)
db["people"].insert_all(data_generator()) db.table("people").insert_all(data_generator())
rows = list(db["people"].rows) rows = list(db.table("people").rows)
assert len(rows) == 3 assert len(rows) == 3
assert rows[0] == {"id": 1, "name": "Alice", "age": 30} assert rows[0] == {"id": 1, "name": "Alice", "age": 30}
assert rows[1] == {"id": 2, "name": "Bob", "age": 25} assert rows[1] == {"id": 2, "name": "Bob", "age": 25}
@ -211,9 +211,9 @@ def test_insert_all_mixed_list_tuple():
yield [3, "Charlie", 35] yield [3, "Charlie", 35]
yield (4, "Diana", 40) yield (4, "Diana", 40)
db["people"].insert_all(data_generator()) db.table("people").insert_all(data_generator())
rows = list(db["people"].rows) rows = list(db.table("people").rows)
assert len(rows) == 4 assert len(rows) == 4
assert rows[0] == {"id": 1, "name": "Alice", "age": 30} assert rows[0] == {"id": 1, "name": "Alice", "age": 30}
assert rows[1] == {"id": 2, "name": "Bob", "age": 25} assert rows[1] == {"id": 2, "name": "Bob", "age": 25}
@ -231,7 +231,7 @@ def test_upsert_all_tuple_mode():
yield (1, "Alice", 100) yield (1, "Alice", 100)
yield (2, "Bob", 200) yield (2, "Bob", 200)
db["data"].insert_all(initial_data(), pk="id") db.table("data").insert_all(initial_data(), pk="id")
# Upsert with tuples # Upsert with tuples
def upsert_data(): def upsert_data():
@ -239,9 +239,9 @@ def test_upsert_all_tuple_mode():
yield (1, "Alice", 150) # Update existing yield (1, "Alice", 150) # Update existing
yield (3, "Charlie", 300) # Insert new yield (3, "Charlie", 300) # Insert new
db["data"].upsert_all(upsert_data(), pk="id") db.table("data").upsert_all(upsert_data(), pk="id")
rows = list(db["data"].rows_where(order_by="id")) rows = list(db.table("data").rows_where(order_by="id"))
assert len(rows) == 3 assert len(rows) == 3
assert rows[0] == {"id": 1, "name": "Alice", "value": 150} assert rows[0] == {"id": 1, "name": "Alice", "value": 150}
assert rows[1] == {"id": 2, "name": "Bob", "value": 200} assert rows[1] == {"id": 2, "name": "Bob", "value": 200}
@ -258,9 +258,9 @@ def test_tuple_mode_shorter_rows():
yield 2, "Bob" # Missing age and city yield 2, "Bob" # Missing age and city
yield 3, "Charlie", 35 # Missing city yield 3, "Charlie", 35 # Missing city
db["people"].insert_all(data_generator()) db.table("people").insert_all(data_generator())
rows = list(db["people"].rows_where(order_by="id")) rows = list(db.table("people").rows_where(order_by="id"))
assert rows[0] == {"id": 1, "name": "Alice", "age": 30, "city": "NYC"} assert rows[0] == {"id": 1, "name": "Alice", "age": 30, "city": "NYC"}
assert rows[1] == {"id": 2, "name": "Bob", "age": None, "city": None} assert rows[1] == {"id": 2, "name": "Bob", "age": None, "city": None}
assert rows[2] == {"id": 3, "name": "Charlie", "age": 35, "city": None} assert rows[2] == {"id": 3, "name": "Charlie", "age": 35, "city": None}
@ -271,18 +271,18 @@ def test_list_mode_single_record_upsert_last_pk():
db = Database(memory=True) db = Database(memory=True)
# Create table first # Create table first
db["data"].insert({"id": 1, "name": "Alice", "value": 100}, pk="id") db.table("data").insert({"id": 1, "name": "Alice", "value": 100}, pk="id")
# Now upsert a single record using list mode # Now upsert a single record using list mode
def upsert_data(): def upsert_data():
yield ["id", "name", "value"] yield ["id", "name", "value"]
yield [1, "Alice", 150] # Update existing yield [1, "Alice", 150] # Update existing
table = db["data"] table = db.table("data")
table.upsert_all(upsert_data(), pk="id") table.upsert_all(upsert_data(), pk="id")
# Verify the data was updated # Verify the data was updated
rows = list(db["data"].rows) rows = list(db.table("data").rows)
assert rows == [{"id": 1, "name": "Alice", "value": 150}] assert rows == [{"id": 1, "name": "Alice", "value": 150}]
# Verify last_pk is populated correctly # Verify last_pk is populated correctly

View file

@ -4,7 +4,7 @@ from sqlite_utils.db import Index
def test_lookup_new_table(fresh_db): def test_lookup_new_table(fresh_db):
species = fresh_db["species"] species = fresh_db.table("species")
palm_id = species.lookup({"name": "Palm"}) palm_id = species.lookup({"name": "Palm"})
oak_id = species.lookup({"name": "Oak"}) oak_id = species.lookup({"name": "Oak"})
cherry_id = species.lookup({"name": "Cherry"}) cherry_id = species.lookup({"name": "Cherry"})
@ -26,7 +26,7 @@ def test_lookup_new_table(fresh_db):
def test_lookup_new_table_compound_key(fresh_db): def test_lookup_new_table_compound_key(fresh_db):
species = fresh_db["species"] species = fresh_db.table("species")
palm_id = species.lookup({"name": "Palm", "type": "Tree"}) palm_id = species.lookup({"name": "Palm", "type": "Tree"})
oak_id = species.lookup({"name": "Oak", "type": "Tree"}) oak_id = species.lookup({"name": "Oak", "type": "Tree"})
assert palm_id == species.lookup({"name": "Palm", "type": "Tree"}) assert palm_id == species.lookup({"name": "Palm", "type": "Tree"})
@ -70,7 +70,7 @@ def test_lookup_fails_if_constraint_cannot_be_added(fresh_db):
def test_lookup_with_extra_values(fresh_db): def test_lookup_with_extra_values(fresh_db):
species = fresh_db["species"] species = fresh_db.table("species")
id = species.lookup({"name": "Palm", "type": "Tree"}, {"first_seen": "2020-01-01"}) id = species.lookup({"name": "Palm", "type": "Tree"}, {"first_seen": "2020-01-01"})
assert species.get(id) == { assert species.get(id) == {
"id": 1, "id": 1,
@ -90,9 +90,9 @@ def test_lookup_with_extra_values(fresh_db):
def test_lookup_with_extra_insert_parameters(fresh_db): def test_lookup_with_extra_insert_parameters(fresh_db):
other_table = fresh_db["other_table"] other_table = fresh_db.table("other_table")
other_table.insert({"id": 1, "name": "Name"}, pk="id") other_table.insert({"id": 1, "name": "Name"}, pk="id")
species = fresh_db["species"] species = fresh_db.table("species")
id = species.lookup( id = species.lookup(
{"name": "Palm", "type": "Tree"}, {"name": "Palm", "type": "Tree"},
{ {
@ -156,15 +156,15 @@ def test_lookup_with_extra_insert_parameters(fresh_db):
@pytest.mark.parametrize("strict", (False, True)) @pytest.mark.parametrize("strict", (False, True))
def test_lookup_new_table_strict(fresh_db, strict): def test_lookup_new_table_strict(fresh_db, strict):
fresh_db["species"].lookup({"name": "Palm"}, strict=strict) fresh_db.table("species").lookup({"name": "Palm"}, strict=strict)
assert fresh_db["species"].strict == strict or not fresh_db.supports_strict assert fresh_db.table("species").strict == strict or not fresh_db.supports_strict
def test_lookup_null_value_idempotent(fresh_db): def test_lookup_null_value_idempotent(fresh_db):
# https://github.com/simonw/sqlite-utils/issues/186 # https://github.com/simonw/sqlite-utils/issues/186
# Repeated lookups of a null value should return the same row, # Repeated lookups of a null value should return the same row,
# not insert a duplicate row each time # not insert a duplicate row each time
species = fresh_db["species"] species = fresh_db.table("species")
first_id = species.lookup({"name": None}) first_id = species.lookup({"name": None})
second_id = species.lookup({"name": None}) second_id = species.lookup({"name": None})
assert first_id == second_id assert first_id == second_id
@ -172,7 +172,7 @@ def test_lookup_null_value_idempotent(fresh_db):
def test_lookup_compound_key_with_null_idempotent(fresh_db): def test_lookup_compound_key_with_null_idempotent(fresh_db):
species = fresh_db["species"] species = fresh_db.table("species")
palm_id = species.lookup({"name": "Palm", "type": None}) palm_id = species.lookup({"name": "Palm", "type": None})
oak_id = species.lookup({"name": "Oak", "type": "Tree"}) oak_id = species.lookup({"name": "Oak", "type": "Tree"})
assert palm_id == species.lookup({"name": "Palm", "type": None}) assert palm_id == species.lookup({"name": "Palm", "type": None})

View file

@ -4,45 +4,45 @@ from sqlite_utils.db import ForeignKey, NoObviousTable
def test_insert_m2m_single(fresh_db): def test_insert_m2m_single(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo"}, pk="id").m2m( dogs.insert({"id": 1, "name": "Cleo"}, pk="id").m2m(
"humans", {"id": 1, "name": "Natalie D"}, pk="id" "humans", {"id": 1, "name": "Natalie D"}, pk="id"
) )
assert {"dogs_humans", "humans", "dogs"} == set(fresh_db.table_names()) assert {"dogs_humans", "humans", "dogs"} == set(fresh_db.table_names())
humans = fresh_db["humans"] humans = fresh_db.table("humans")
dogs_humans = fresh_db["dogs_humans"] dogs_humans = fresh_db.table("dogs_humans")
assert [{"id": 1, "name": "Natalie D"}] == list(humans.rows) assert [{"id": 1, "name": "Natalie D"}] == list(humans.rows)
assert [{"humans_id": 1, "dogs_id": 1}] == list(dogs_humans.rows) assert [{"humans_id": 1, "dogs_id": 1}] == list(dogs_humans.rows)
def test_insert_m2m_alter(fresh_db): def test_insert_m2m_alter(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo"}, pk="id").m2m( dogs.insert({"id": 1, "name": "Cleo"}, pk="id").m2m(
"humans", {"id": 1, "name": "Natalie D"}, pk="id" "humans", {"id": 1, "name": "Natalie D"}, pk="id"
) )
dogs.update(1).m2m( dogs.update(1).m2m(
"humans", {"id": 2, "name": "Simon W", "nerd": True}, pk="id", alter=True "humans", {"id": 2, "name": "Simon W", "nerd": True}, pk="id", alter=True
) )
assert list(fresh_db["humans"].rows) == [ assert list(fresh_db.table("humans").rows) == [
{"id": 1, "name": "Natalie D", "nerd": None}, {"id": 1, "name": "Natalie D", "nerd": None},
{"id": 2, "name": "Simon W", "nerd": 1}, {"id": 2, "name": "Simon W", "nerd": 1},
] ]
assert list(fresh_db["dogs_humans"].rows) == [ assert list(fresh_db.table("dogs_humans").rows) == [
{"humans_id": 1, "dogs_id": 1}, {"humans_id": 1, "dogs_id": 1},
{"humans_id": 2, "dogs_id": 1}, {"humans_id": 2, "dogs_id": 1},
] ]
def test_insert_m2m_list(fresh_db): def test_insert_m2m_list(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo"}, pk="id").m2m( dogs.insert({"id": 1, "name": "Cleo"}, pk="id").m2m(
"humans", "humans",
[{"id": 1, "name": "Natalie D"}, {"id": 2, "name": "Simon W"}], [{"id": 1, "name": "Natalie D"}, {"id": 2, "name": "Simon W"}],
pk="id", pk="id",
) )
assert {"dogs", "humans", "dogs_humans"} == set(fresh_db.table_names()) assert {"dogs", "humans", "dogs_humans"} == set(fresh_db.table_names())
humans = fresh_db["humans"] humans = fresh_db.table("humans")
dogs_humans = fresh_db["dogs_humans"] dogs_humans = fresh_db.table("dogs_humans")
assert [{"humans_id": 1, "dogs_id": 1}, {"humans_id": 2, "dogs_id": 1}] == list( assert [{"humans_id": 1, "dogs_id": 1}, {"humans_id": 2, "dogs_id": 1}] == list(
dogs_humans.rows dogs_humans.rows
) )
@ -68,7 +68,7 @@ def test_insert_m2m_iterable(fresh_db):
def iterable(): def iterable():
yield from iterable_records yield from iterable_records
platypuses = fresh_db["platypuses"] platypuses = fresh_db.table("platypuses")
platypuses.insert({"id": 1, "name": "Perry"}, pk="id").m2m( platypuses.insert({"id": 1, "name": "Perry"}, pk="id").m2m(
"humans", "humans",
iterable(), iterable(),
@ -76,8 +76,8 @@ def test_insert_m2m_iterable(fresh_db):
) )
assert {"platypuses", "humans", "humans_platypuses"} == set(fresh_db.table_names()) assert {"platypuses", "humans", "humans_platypuses"} == set(fresh_db.table_names())
humans = fresh_db["humans"] humans = fresh_db.table("humans")
humans_platypuses = fresh_db["humans_platypuses"] humans_platypuses = fresh_db.table("humans_platypuses")
assert [ assert [
{"humans_id": 1, "platypuses_id": 1}, {"humans_id": 1, "platypuses_id": 1},
{"humans_id": 2, "platypuses_id": 1}, {"humans_id": 2, "platypuses_id": 1},
@ -111,14 +111,14 @@ def test_m2m_with_table_objects(fresh_db):
assert expected_tables == set(fresh_db.table_names()) assert expected_tables == set(fresh_db.table_names())
assert dogs.count == 1 assert dogs.count == 1
assert humans.count == 2 assert humans.count == 2
assert fresh_db["dogs_humans"].count == 2 assert fresh_db.table("dogs_humans").count == 2
def test_m2m_lookup(fresh_db): def test_m2m_lookup(fresh_db):
people = fresh_db.table("people", pk="id") people = fresh_db.table("people", pk="id")
people.insert({"name": "Wahyu"}).m2m("tags", lookup={"tag": "Coworker"}) people.insert({"name": "Wahyu"}).m2m("tags", lookup={"tag": "Coworker"})
people_tags = fresh_db["people_tags"] people_tags = fresh_db.table("people_tags")
tags = fresh_db["tags"] tags = fresh_db.table("tags")
assert people_tags.exists() assert people_tags.exists()
assert tags.exists() assert tags.exists()
assert [ assert [
@ -150,9 +150,9 @@ def test_m2m_explicit_table_name_argument(fresh_db):
people.insert({"name": "Wahyu"}).m2m( people.insert({"name": "Wahyu"}).m2m(
"tags", lookup={"tag": "Coworker"}, m2m_table="tagged" "tags", lookup={"tag": "Coworker"}, m2m_table="tagged"
) )
assert fresh_db["tags"].exists assert fresh_db.table("tags").exists
assert fresh_db["tagged"].exists assert fresh_db.table("tagged").exists
assert not fresh_db["people_tags"].exists() assert not fresh_db.table("people_tags").exists()
def test_m2m_table_candidates(fresh_db): def test_m2m_table_candidates(fresh_db):
@ -181,25 +181,25 @@ def test_uses_existing_m2m_table_if_exists(fresh_db):
# Code should look for an existing table with fks to both tables # Code should look for an existing table with fks to both tables
# and use that if it exists. # and use that if it exists.
people = fresh_db.create_table("people", {"id": int, "name": str}, pk="id") people = fresh_db.create_table("people", {"id": int, "name": str}, pk="id")
fresh_db["tags"].lookup({"tag": "Coworker"}) fresh_db.table("tags").lookup({"tag": "Coworker"})
fresh_db.create_table( fresh_db.create_table(
"tagged", "tagged",
{"people_id": int, "tags_id": int}, {"people_id": int, "tags_id": int},
foreign_keys=["people_id", "tags_id"], foreign_keys=["people_id", "tags_id"],
) )
people.insert({"name": "Wahyu"}).m2m("tags", lookup={"tag": "Coworker"}) people.insert({"name": "Wahyu"}).m2m("tags", lookup={"tag": "Coworker"})
assert fresh_db["tags"].exists() assert fresh_db.table("tags").exists()
assert fresh_db["tagged"].exists() assert fresh_db.table("tagged").exists()
assert not fresh_db["people_tags"].exists() assert not fresh_db.table("people_tags").exists()
assert not fresh_db["tags_people"].exists() assert not fresh_db.table("tags_people").exists()
assert [{"people_id": 1, "tags_id": 1}] == list(fresh_db["tagged"].rows) assert [{"people_id": 1, "tags_id": 1}] == list(fresh_db.table("tagged").rows)
def test_requires_explicit_m2m_table_if_multiple_options(fresh_db): def test_requires_explicit_m2m_table_if_multiple_options(fresh_db):
# If the code scans for m2m tables and finds more than one candidate # If the code scans for m2m tables and finds more than one candidate
# it should require that the m2m_table=x argument is used # it should require that the m2m_table=x argument is used
people = fresh_db.create_table("people", {"id": int, "name": str}, pk="id") people = fresh_db.create_table("people", {"id": int, "name": str}, pk="id")
fresh_db["tags"].lookup({"tag": "Coworker"}) fresh_db.table("tags").lookup({"tag": "Coworker"})
fresh_db.create_table( fresh_db.create_table(
"tagged", "tagged",
{"people_id": int, "tags_id": int}, {"people_id": int, "tags_id": int},

View file

@ -10,11 +10,11 @@ def migrations():
@migrations() @migrations()
def m001(db): def m001(db):
db["dogs"].insert({"name": "Cleo"}) db.table("dogs").insert({"name": "Cleo"})
@migrations() @migrations()
def m002(db): def m002(db):
db["cats"].create({"name": str}) db.table("cats").create({"name": str})
db.execute("insert into dogs (name) values ('Pancakes')") db.execute("insert into dogs (name) values ('Pancakes')")
return migrations return migrations
@ -28,11 +28,11 @@ def migrations_not_ordered_alphabetically():
@migrations() @migrations()
def m002(db): def m002(db):
db["dogs"].insert({"name": "Cleo"}) db.table("dogs").insert({"name": "Cleo"})
@migrations() @migrations()
def m001(db): def m001(db):
db["cats"].create({"name": str}) db.table("cats").create({"name": str})
db.execute("insert into dogs (name) values ('Pancakes')") db.execute("insert into dogs (name) values ('Pancakes')")
return migrations return migrations
@ -44,7 +44,7 @@ def migrations2():
@migrations() @migrations()
def m001(db): def m001(db):
db["dogs2"].insert({"name": "Cleo"}) db.table("dogs2").insert({"name": "Cleo"})
return migrations return migrations
@ -96,7 +96,7 @@ def test_applied_at_is_a_string(migrations):
def test_failing_migration_rolls_back(migrations): def test_failing_migration_rolls_back(migrations):
@migrations() @migrations()
def m003(db): def m003(db):
db["birds"].create({"name": str}) db.table("birds").create({"name": str})
db.execute("insert into dogs (name) values ('Dozer')") db.execute("insert into dogs (name) values ('Dozer')")
raise ValueError("boom") raise ValueError("boom")
@ -105,7 +105,7 @@ def test_failing_migration_rolls_back(migrations):
migrations.apply(db) migrations.apply(db)
# m001 and m002 committed before the failure and stay applied # m001 and m002 committed before the failure and stay applied
assert set(db.table_names()) == {"_sqlite_migrations", "dogs", "cats"} assert set(db.table_names()) == {"_sqlite_migrations", "dogs", "cats"}
assert [r["name"] for r in db["dogs"].rows] == ["Cleo", "Pancakes"] assert [r["name"] for r in db.table("dogs").rows] == ["Cleo", "Pancakes"]
assert [m.name for m in migrations.applied(db)] == ["m001", "m002"] assert [m.name for m in migrations.applied(db)] == ["m001", "m002"]
# Everything m003 did was rolled back and it is still pending # Everything m003 did was rolled back and it is still pending
assert [m.name for m in migrations.pending(db)] == ["m003"] assert [m.name for m in migrations.pending(db)] == ["m003"]
@ -117,11 +117,11 @@ def test_rerun_after_failure_applies_each_migration_once():
@migrations() @migrations()
def m001(db): def m001(db):
db["dogs"].insert({"name": "Cleo"}) db.table("dogs").insert({"name": "Cleo"})
@migrations() @migrations()
def m002(db): def m002(db):
db["dogs"].insert({"name": "Pancakes"}) db.table("dogs").insert({"name": "Pancakes"})
if state["fail"]: if state["fail"]:
raise ValueError("boom") raise ValueError("boom")
@ -131,7 +131,7 @@ def test_rerun_after_failure_applies_each_migration_once():
state["fail"] = False state["fail"] = False
migrations.apply(db) migrations.apply(db)
# m001 must not have been re-applied, m002 applied exactly once # m001 must not have been re-applied, m002 applied exactly once
assert [r["name"] for r in db["dogs"].rows] == ["Cleo", "Pancakes"] assert [r["name"] for r in db.table("dogs").rows] == ["Cleo", "Pancakes"]
assert [m.name for m in migrations.applied(db)] == ["m001", "m002"] assert [m.name for m in migrations.applied(db)] == ["m001", "m002"]
@ -142,7 +142,7 @@ def test_non_transactional_migration_allows_vacuum(tmpdir):
@migrations() @migrations()
def m001(db): def m001(db):
db["dogs"].insert({"name": "Cleo"}) db.table("dogs").insert({"name": "Cleo"})
@migrations(transactional=False) @migrations(transactional=False)
def m002(db): def m002(db):
@ -185,11 +185,13 @@ def test_apply_composes_inside_outer_transaction(migrations):
) )
def test_upgrades_sqlite_migrations(migrations, create_table, pk): def test_upgrades_sqlite_migrations(migrations, create_table, pk):
db = sqlite_utils.Database(memory=True) db = sqlite_utils.Database(memory=True)
db["_sqlite_migrations"].create(create_table, pk=pk) db.table("_sqlite_migrations").create(create_table, pk=pk)
assert db.table_names() == ["_sqlite_migrations"] assert db.table_names() == ["_sqlite_migrations"]
assert db["_sqlite_migrations"].pks == ([pk] if isinstance(pk, str) else list(pk)) assert db.table("_sqlite_migrations").pks == (
[pk] if isinstance(pk, str) else list(pk)
)
migrations.apply(db) migrations.apply(db)
assert db["_sqlite_migrations"].pks == ["id"] assert db.table("_sqlite_migrations").pks == ["id"]
def test_pending_and_applied_are_read_only(migrations): def test_pending_and_applied_are_read_only(migrations):
@ -227,7 +229,7 @@ def test_stop_before_applied_migration_errors(migrations):
assert "m001" in str(ex.value) assert "m001" in str(ex.value)
assert "already been applied" in str(ex.value) assert "already been applied" in str(ex.value)
# Nothing else was applied # Nothing else was applied
assert not db["cats"].exists() assert not db.table("cats").exists()
def test_stop_before_applied_migration_errors_before_any_apply(migrations): def test_stop_before_applied_migration_errors_before_any_apply(migrations):
@ -238,9 +240,9 @@ def test_stop_before_applied_migration_errors_before_any_apply(migrations):
@only_second() @only_second()
def m002(db): def m002(db):
db["cats"].create({"name": str}) db.table("cats").create({"name": str})
only_second.apply(db) # m002 applied, m001 still pending only_second.apply(db) # m002 applied, m001 still pending
with pytest.raises(ValueError): with pytest.raises(ValueError):
migrations.apply(db, stop_before="m002") migrations.apply(db, stop_before="m002")
assert not db["dogs"].exists() assert not db.table("dogs").exists()

View file

@ -112,7 +112,7 @@ def test_mutator_commits_by_default(tmp_path, mutate, expected_rows):
db = seed_database(path) db = seed_database(path)
assert not db.conn.in_transaction assert not db.conn.in_transaction
mutate(db["items"]) mutate(db.table("items"))
assert current_rows(db) == expected_rows assert current_rows(db) == expected_rows
assert not db.conn.in_transaction assert not db.conn.in_transaction
@ -127,7 +127,7 @@ def test_mutator_commits_with_outer_atomic(tmp_path, mutate, expected_rows):
with db.atomic(): with db.atomic():
assert db.conn.in_transaction assert db.conn.in_transaction
mutate(db["items"]) mutate(db.table("items"))
assert current_rows(db) == expected_rows assert current_rows(db) == expected_rows
assert db.conn.in_transaction assert db.conn.in_transaction
@ -143,7 +143,7 @@ def test_mutator_rolls_back_outer_atomic(tmp_path, mutate, expected_rows):
db = seed_database(path) db = seed_database(path)
with pytest.raises(RollbackTest), db.atomic(): with pytest.raises(RollbackTest), db.atomic():
mutate(db["items"]) mutate(db.table("items"))
assert current_rows(db) == expected_rows assert current_rows(db) == expected_rows
assert db.conn.in_transaction assert db.conn.in_transaction
raise RollbackTest raise RollbackTest

View file

@ -6,7 +6,7 @@ from sqlite_utils.utils import sqlite3
def test_query(fresh_db): def test_query(fresh_db):
fresh_db["dogs"].insert_all([{"name": "Cleo"}, {"name": "Pancakes"}]) fresh_db.table("dogs").insert_all([{"name": "Cleo"}, {"name": "Pancakes"}])
results = fresh_db.query("select * from dogs order by name desc") results = fresh_db.query("select * from dogs order by name desc")
assert isinstance(results, types.GeneratorType) assert isinstance(results, types.GeneratorType)
assert list(results) == [{"name": "Pancakes"}, {"name": "Cleo"}] assert list(results) == [{"name": "Pancakes"}, {"name": "Cleo"}]
@ -20,13 +20,13 @@ def test_query_executes_eagerly(fresh_db):
def test_query_rejects_statements_that_return_no_rows(fresh_db): def test_query_rejects_statements_that_return_no_rows(fresh_db):
fresh_db["dogs"].insert({"name": "Cleo"}) fresh_db.table("dogs").insert({"name": "Cleo"})
with pytest.raises(ValueError) as ex: with pytest.raises(ValueError) as ex:
fresh_db.query("update dogs set name = 'Cleopaws'") fresh_db.query("update dogs set name = 'Cleopaws'")
assert "execute()" in str(ex.value) assert "execute()" in str(ex.value)
# The rejected update was rolled back, and no transaction is left open # The rejected update was rolled back, and no transaction is left open
assert not fresh_db.conn.in_transaction assert not fresh_db.conn.in_transaction
assert [row["name"] for row in fresh_db["dogs"].rows] == ["Cleo"] assert [row["name"] for row in fresh_db.table("dogs").rows] == ["Cleo"]
def test_query_rejected_ddl_is_rolled_back(fresh_db): def test_query_rejected_ddl_is_rolled_back(fresh_db):
@ -37,7 +37,7 @@ def test_query_rejected_ddl_is_rolled_back(fresh_db):
def test_query_rejected_write_inside_transaction_is_rolled_back(fresh_db): def test_query_rejected_write_inside_transaction_is_rolled_back(fresh_db):
fresh_db["dogs"].insert({"name": "Cleo"}) fresh_db.table("dogs").insert({"name": "Cleo"})
fresh_db.begin() fresh_db.begin()
fresh_db.execute("insert into dogs (name) values ('Pancakes')") fresh_db.execute("insert into dogs (name) values ('Pancakes')")
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -45,7 +45,7 @@ def test_query_rejected_write_inside_transaction_is_rolled_back(fresh_db):
# The transaction is still open and the earlier insert is intact # The transaction is still open and the earlier insert is intact
assert fresh_db.conn.in_transaction assert fresh_db.conn.in_transaction
fresh_db.commit() fresh_db.commit()
assert [row["name"] for row in fresh_db["dogs"].rows] == ["Cleo", "Pancakes"] assert [row["name"] for row in fresh_db.table("dogs").rows] == ["Cleo", "Pancakes"]
@pytest.mark.parametrize( @pytest.mark.parametrize(
@ -77,7 +77,7 @@ def test_query_comment_prefixed_commit_does_not_commit_transaction(fresh_db):
# A COMMIT hidden behind a leading comment must not slip past the # A COMMIT hidden behind a leading comment must not slip past the
# keyword check - previously it committed the caller's open # keyword check - previously it committed the caller's open
# transaction before the ValueError was raised # transaction before the ValueError was raised
fresh_db["dogs"].insert({"name": "Cleo"}) fresh_db.table("dogs").insert({"name": "Cleo"})
fresh_db.begin() fresh_db.begin()
fresh_db.execute("insert into dogs (name) values ('Pancakes')") fresh_db.execute("insert into dogs (name) values ('Pancakes')")
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -85,7 +85,7 @@ def test_query_comment_prefixed_commit_does_not_commit_transaction(fresh_db):
# The explicit transaction is still open and can still be rolled back # The explicit transaction is still open and can still be rolled back
assert fresh_db.conn.in_transaction assert fresh_db.conn.in_transaction
fresh_db.rollback() fresh_db.rollback()
assert [row["name"] for row in fresh_db["dogs"].rows] == ["Cleo"] assert [row["name"] for row in fresh_db.table("dogs").rows] == ["Cleo"]
@pytest.mark.parametrize("sql", ["; COMMIT", "\ufeffCOMMIT"]) @pytest.mark.parametrize("sql", ["; COMMIT", "\ufeffCOMMIT"])
@ -94,7 +94,7 @@ def test_query_prefixed_commit_does_not_commit_transaction(fresh_db, sql):
# real token, so the keyword scanner must skip them too - previously # real token, so the keyword scanner must skip them too - previously
# '; COMMIT' slipped past the check and committed the caller's open # '; COMMIT' slipped past the check and committed the caller's open
# transaction before raising OperationalError # transaction before raising OperationalError
fresh_db["dogs"].insert({"name": "Cleo"}) fresh_db.table("dogs").insert({"name": "Cleo"})
fresh_db.begin() fresh_db.begin()
fresh_db.execute("insert into dogs (name) values ('Pancakes')") fresh_db.execute("insert into dogs (name) values ('Pancakes')")
with pytest.raises(ValueError): with pytest.raises(ValueError):
@ -102,7 +102,7 @@ def test_query_prefixed_commit_does_not_commit_transaction(fresh_db, sql):
# The explicit transaction is still open and can still be rolled back # The explicit transaction is still open and can still be rolled back
assert fresh_db.conn.in_transaction assert fresh_db.conn.in_transaction
fresh_db.rollback() fresh_db.rollback()
assert [row["name"] for row in fresh_db["dogs"].rows] == ["Cleo"] assert [row["name"] for row in fresh_db.table("dogs").rows] == ["Cleo"]
def test_query_error_leaves_no_transaction_open(fresh_db): def test_query_error_leaves_no_transaction_open(fresh_db):
@ -190,12 +190,12 @@ def test_first_keyword(sql, expected):
reason="RETURNING requires SQLite 3.35.0 or higher", reason="RETURNING requires SQLite 3.35.0 or higher",
) )
def test_query_insert_returning(fresh_db): def test_query_insert_returning(fresh_db):
fresh_db["dogs"].insert({"name": "Cleo"}) fresh_db.table("dogs").insert({"name": "Cleo"})
rows = list( rows = list(
fresh_db.query("insert into dogs (name) values ('Pancakes') returning name") fresh_db.query("insert into dogs (name) values ('Pancakes') returning name")
) )
assert rows == [{"name": "Pancakes"}] assert rows == [{"name": "Pancakes"}]
assert fresh_db["dogs"].count == 2 assert fresh_db.table("dogs").count == 2
@pytest.mark.skipif( @pytest.mark.skipif(
@ -207,7 +207,7 @@ def test_query_insert_returning_commits_without_iteration(tmpdir):
path = str(tmpdir / "test.db") path = str(tmpdir / "test.db")
db = Database(path) db = Database(path)
db["dogs"].insert({"name": "Cleo"}) db.table("dogs").insert({"name": "Cleo"})
# Never iterate over the results # Never iterate over the results
db.query("insert into dogs (name) values ('Pancakes') returning name") db.query("insert into dogs (name) values ('Pancakes') returning name")
assert not db.conn.in_transaction assert not db.conn.in_transaction
@ -227,7 +227,7 @@ def test_query_insert_returning_partial_iteration_still_commits(tmpdir):
path = str(tmpdir / "test.db") path = str(tmpdir / "test.db")
db = Database(path) db = Database(path)
db["dogs"].insert({"name": "Cleo"}) db.table("dogs").insert({"name": "Cleo"})
row = next( row = next(
db.query( db.query(
"insert into dogs (name) values ('Pancakes'), ('Marnie') returning name" "insert into dogs (name) values ('Pancakes'), ('Marnie') returning name"
@ -246,7 +246,7 @@ def test_query_insert_returning_partial_iteration_still_commits(tmpdir):
reason="RETURNING requires SQLite 3.35.0 or higher", reason="RETURNING requires SQLite 3.35.0 or higher",
) )
def test_query_insert_returning_respects_explicit_transaction(fresh_db): def test_query_insert_returning_respects_explicit_transaction(fresh_db):
fresh_db["dogs"].insert({"name": "Cleo"}) fresh_db.table("dogs").insert({"name": "Cleo"})
fresh_db.begin() fresh_db.begin()
rows = list( rows = list(
fresh_db.query("insert into dogs (name) values ('Pancakes') returning name") fresh_db.query("insert into dogs (name) values ('Pancakes') returning name")
@ -255,13 +255,13 @@ def test_query_insert_returning_respects_explicit_transaction(fresh_db):
# Still inside the explicit transaction - not committed # Still inside the explicit transaction - not committed
assert fresh_db.conn.in_transaction assert fresh_db.conn.in_transaction
fresh_db.rollback() fresh_db.rollback()
assert [row["name"] for row in fresh_db["dogs"].rows] == ["Cleo"] assert [row["name"] for row in fresh_db.table("dogs").rows] == ["Cleo"]
def test_query_duplicate_column_names_are_deduped(fresh_db): def test_query_duplicate_column_names_are_deduped(fresh_db):
# https://github.com/simonw/sqlite-utils/issues/624 # https://github.com/simonw/sqlite-utils/issues/624
fresh_db["one"].insert({"id": 1, "value": "left"}) fresh_db.table("one").insert({"id": 1, "value": "left"})
fresh_db["two"].insert({"id": 2, "value": "right"}) fresh_db.table("two").insert({"id": 2, "value": "right"})
rows = list( rows = list(
fresh_db.query("select one.id, two.id, one.value, two.value from one, two") fresh_db.query("select one.id, two.id, one.value, two.value from one, two")
) )
@ -277,7 +277,7 @@ def test_query_deduped_column_avoids_existing_names(fresh_db):
def test_execute_returning_dicts(fresh_db): def test_execute_returning_dicts(fresh_db):
# Like db.query() but returns a list, included for backwards compatibility # Like db.query() but returns a list, included for backwards compatibility
# see https://github.com/simonw/sqlite-utils/issues/290 # see https://github.com/simonw/sqlite-utils/issues/290
fresh_db["test"].insert({"id": 1, "bar": 2}, pk="id") fresh_db.table("test").insert({"id": 1, "bar": 2}, pk="id")
assert fresh_db.execute_returning_dicts("select * from test") == [ assert fresh_db.execute_returning_dicts("select * from test") == [
{"id": 1, "bar": 2} {"id": 1, "bar": 2}
] ]

View file

@ -8,7 +8,7 @@ from sqlite_utils.utils import sqlite3
@pytest.fixture @pytest.fixture
def dates_db(fresh_db): def dates_db(fresh_db):
fresh_db["example"].insert_all( fresh_db.table("example").insert_all(
[ [
{"id": 1, "dt": "5th October 2019 12:04"}, {"id": 1, "dt": "5th October 2019 12:04"},
{"id": 2, "dt": "6th October 2019 00:05:06"}, {"id": 2, "dt": "6th October 2019 00:05:06"},
@ -21,8 +21,8 @@ def dates_db(fresh_db):
def test_parsedate(dates_db): def test_parsedate(dates_db):
dates_db["example"].convert("dt", recipes.parsedate) dates_db.table("example").convert("dt", recipes.parsedate)
assert list(dates_db["example"].rows) == [ assert list(dates_db.table("example").rows) == [
{"id": 1, "dt": "2019-10-05"}, {"id": 1, "dt": "2019-10-05"},
{"id": 2, "dt": "2019-10-06"}, {"id": 2, "dt": "2019-10-06"},
{"id": 3, "dt": ""}, {"id": 3, "dt": ""},
@ -31,8 +31,8 @@ def test_parsedate(dates_db):
def test_parsedatetime(dates_db): def test_parsedatetime(dates_db):
dates_db["example"].convert("dt", recipes.parsedatetime) dates_db.table("example").convert("dt", recipes.parsedatetime)
assert list(dates_db["example"].rows) == [ assert list(dates_db.table("example").rows) == [
{"id": 1, "dt": "2019-10-05T12:04:00"}, {"id": 1, "dt": "2019-10-05T12:04:00"},
{"id": 2, "dt": "2019-10-06T00:05:06"}, {"id": 2, "dt": "2019-10-06T00:05:06"},
{"id": 3, "dt": ""}, {"id": 3, "dt": ""},
@ -50,16 +50,16 @@ def test_parsedatetime(dates_db):
), ),
) )
def test_dayfirst_yearfirst(fresh_db, recipe, kwargs, expected): def test_dayfirst_yearfirst(fresh_db, recipe, kwargs, expected):
fresh_db["example"].insert_all( fresh_db.table("example").insert_all(
[ [
{"id": 1, "dt": "03/04/05"}, {"id": 1, "dt": "03/04/05"},
], ],
pk="id", pk="id",
) )
fresh_db["example"].convert( fresh_db.table("example").convert(
"dt", lambda value: getattr(recipes, recipe)(value, **kwargs) "dt", lambda value: getattr(recipes, recipe)(value, **kwargs)
) )
assert list(fresh_db["example"].rows) == [ assert list(fresh_db.table("example").rows) == [
{"id": 1, "dt": expected}, {"id": 1, "dt": expected},
] ]
@ -68,7 +68,7 @@ def test_dayfirst_yearfirst(fresh_db, recipe, kwargs, expected):
@pytest.mark.parametrize("fn", ("parsedate", "parsedatetime")) @pytest.mark.parametrize("fn", ("parsedate", "parsedatetime"))
def test_dateparse_errors_raises(fresh_db, fn): def test_dateparse_errors_raises(fresh_db, fn):
"""Test that invalid dates raise errors when errors=None""" """Test that invalid dates raise errors when errors=None"""
fresh_db["example"].insert_all( fresh_db.table("example").insert_all(
[ [
{"id": 1, "dt": "invalid"}, {"id": 1, "dt": "invalid"},
], ],
@ -76,30 +76,32 @@ def test_dateparse_errors_raises(fresh_db, fn):
) )
# Exception in SQLite callback surfaces as OperationalError # Exception in SQLite callback surfaces as OperationalError
with pytest.raises(sqlite3.OperationalError): with pytest.raises(sqlite3.OperationalError):
fresh_db["example"].convert("dt", lambda value: getattr(recipes, fn)(value)) fresh_db.table("example").convert(
"dt", lambda value: getattr(recipes, fn)(value)
)
@pytest.mark.parametrize("fn", ("parsedate", "parsedatetime")) @pytest.mark.parametrize("fn", ("parsedate", "parsedatetime"))
@pytest.mark.parametrize("errors", (recipes.SET_NULL, recipes.IGNORE)) @pytest.mark.parametrize("errors", (recipes.SET_NULL, recipes.IGNORE))
def test_dateparse_errors_handled(fresh_db, fn, errors): def test_dateparse_errors_handled(fresh_db, fn, errors):
"""Test error handling modes for invalid dates""" """Test error handling modes for invalid dates"""
fresh_db["example"].insert_all( fresh_db.table("example").insert_all(
[ [
{"id": 1, "dt": "invalid"}, {"id": 1, "dt": "invalid"},
], ],
pk="id", pk="id",
) )
fresh_db["example"].convert( fresh_db.table("example").convert(
"dt", lambda value: getattr(recipes, fn)(value, errors=errors) "dt", lambda value: getattr(recipes, fn)(value, errors=errors)
) )
rows = list(fresh_db["example"].rows) rows = list(fresh_db.table("example").rows)
expected = [{"id": 1, "dt": None if errors is recipes.SET_NULL else "invalid"}] expected = [{"id": 1, "dt": None if errors is recipes.SET_NULL else "invalid"}]
assert rows == expected assert rows == expected
@pytest.mark.parametrize("delimiter", [None, ";", "-"]) @pytest.mark.parametrize("delimiter", [None, ";", "-"])
def test_jsonsplit(fresh_db, delimiter): def test_jsonsplit(fresh_db, delimiter):
fresh_db["example"].insert_all( fresh_db.table("example").insert_all(
[ [
{"id": 1, "tags": (delimiter or ",").join(["foo", "bar"])}, {"id": 1, "tags": (delimiter or ",").join(["foo", "bar"])},
{"id": 2, "tags": (delimiter or ",").join(["bar", "baz"])}, {"id": 2, "tags": (delimiter or ",").join(["bar", "baz"])},
@ -114,8 +116,8 @@ def test_jsonsplit(fresh_db, delimiter):
else: else:
fn = recipes.jsonsplit fn = recipes.jsonsplit
fresh_db["example"].convert("tags", fn) fresh_db.table("example").convert("tags", fn)
assert list(fresh_db["example"].rows) == [ assert list(fresh_db.table("example").rows) == [
{"id": 1, "tags": '["foo", "bar"]'}, {"id": 1, "tags": '["foo", "bar"]'},
{"id": 2, "tags": '["bar", "baz"]'}, {"id": 2, "tags": '["bar", "baz"]'},
] ]
@ -130,7 +132,7 @@ def test_jsonsplit(fresh_db, delimiter):
), ),
) )
def test_jsonsplit_type(fresh_db, type, expected): def test_jsonsplit_type(fresh_db, type, expected):
fresh_db["example"].insert_all( fresh_db.table("example").insert_all(
[ [
{"id": 1, "records": "1,2,3"}, {"id": 1, "records": "1,2,3"},
], ],
@ -144,5 +146,5 @@ def test_jsonsplit_type(fresh_db, type, expected):
else: else:
fn = recipes.jsonsplit fn = recipes.jsonsplit
fresh_db["example"].convert("records", fn) fresh_db.table("example").convert("records", fn)
assert json.loads(fresh_db["example"].get(1)["records"]) == expected assert json.loads(fresh_db.table("example").get(1)["records"]) == expected

View file

@ -33,8 +33,8 @@ def test_recreate(tmp_path, use_path, create_file_first):
filepath = pathlib.Path(filepath) filepath = pathlib.Path(filepath)
if create_file_first: if create_file_first:
db = Database(filepath) db = Database(filepath)
db["t1"].insert({"foo": "bar"}) db.table("t1").insert({"foo": "bar"})
assert ["t1"] == db.table_names() assert ["t1"] == db.table_names()
db.close() db.close()
Database(filepath, recreate=True)["t2"].insert({"foo": "bar"}) Database(filepath, recreate=True).table("t2").insert({"foo": "bar"})
assert ["t2"] == Database(filepath).table_names() assert ["t2"] == Database(filepath).table_names()

View file

@ -3,7 +3,7 @@ import pytest
def test_rows(existing_db): def test_rows(existing_db):
assert [{"text": "one"}, {"text": "two"}, {"text": "three"}] == list( assert [{"text": "one"}, {"text": "two"}, {"text": "three"}] == list(
existing_db["foo"].rows existing_db.table("foo").rows
) )
@ -18,7 +18,7 @@ def test_rows(existing_db):
], ],
) )
def test_rows_where(where, where_args, expected_ids, fresh_db): def test_rows_where(where, where_args, expected_ids, fresh_db):
table = fresh_db["dogs"] table = fresh_db.table("dogs")
table.insert_all( table.insert_all(
[ [
{"id": 1, "name": "Cleo", "age": 4, "is_good": True}, {"id": 1, "name": "Cleo", "age": 4, "is_good": True},
@ -41,7 +41,7 @@ def test_rows_where(where, where_args, expected_ids, fresh_db):
], ],
) )
def test_rows_where_order_by(where, order_by, expected_ids, fresh_db): def test_rows_where_order_by(where, order_by, expected_ids, fresh_db):
table = fresh_db["dogs"] table = fresh_db.table("dogs")
table.insert_all( table.insert_all(
[ [
{"id": 1, "name": "Cleo", "age": 4}, {"id": 1, "name": "Cleo", "age": 4},
@ -65,7 +65,7 @@ def test_rows_where_order_by(where, order_by, expected_ids, fresh_db):
], ],
) )
def test_rows_where_offset_limit(fresh_db, offset, limit, expected): def test_rows_where_offset_limit(fresh_db, offset, limit, expected):
table = fresh_db["rows"] table = fresh_db.table("rows")
table.insert_all([{"id": id} for id in range(1, 101)], pk="id") table.insert_all([{"id": id} for id in range(1, 101)], pk="id")
assert table.count == 100 assert table.count == 100
assert expected == [ assert expected == [
@ -74,13 +74,13 @@ def test_rows_where_offset_limit(fresh_db, offset, limit, expected):
def test_pks_and_rows_where_offset_without_limit(fresh_db): def test_pks_and_rows_where_offset_without_limit(fresh_db):
table = fresh_db["rows"] table = fresh_db.table("rows")
table.insert_all([{"id": id} for id in range(1, 6)], pk="id") table.insert_all([{"id": id} for id in range(1, 6)], pk="id")
assert [pk for pk, _ in table.pks_and_rows_where(offset=3, order_by="id")] == [4, 5] assert [pk for pk, _ in table.pks_and_rows_where(offset=3, order_by="id")] == [4, 5]
def test_pks_and_rows_where_rowid(fresh_db): def test_pks_and_rows_where_rowid(fresh_db):
table = fresh_db["rowid_table"] table = fresh_db.table("rowid_table")
table.insert_all({"number": i + 10} for i in range(3)) table.insert_all({"number": i + 10} for i in range(3))
pks_and_rows = list(table.pks_and_rows_where()) pks_and_rows = list(table.pks_and_rows_where())
assert pks_and_rows == [ assert pks_and_rows == [
@ -91,7 +91,7 @@ def test_pks_and_rows_where_rowid(fresh_db):
def test_pks_and_rows_where_simple_pk(fresh_db): def test_pks_and_rows_where_simple_pk(fresh_db):
table = fresh_db["simple_pk_table"] table = fresh_db.table("simple_pk_table")
table.insert_all(({"id": i + 10} for i in range(3)), pk="id") table.insert_all(({"id": i + 10} for i in range(3)), pk="id")
pks_and_rows = list(table.pks_and_rows_where()) pks_and_rows = list(table.pks_and_rows_where())
assert pks_and_rows == [ assert pks_and_rows == [
@ -102,7 +102,7 @@ def test_pks_and_rows_where_simple_pk(fresh_db):
def test_pks_and_rows_where_compound_pk(fresh_db): def test_pks_and_rows_where_compound_pk(fresh_db):
table = fresh_db["compound_pk_table"] table = fresh_db.table("compound_pk_table")
table.insert_all( table.insert_all(
({"type": "number", "number": i, "plusone": i + 1} for i in range(3)), ({"type": "number", "number": i, "plusone": i + 1} for i in range(3)),
pk=("type", "number"), pk=("type", "number"),
@ -117,8 +117,8 @@ def test_pks_and_rows_where_compound_pk(fresh_db):
def test_rows_where_duplicate_select_columns_are_deduped(fresh_db): def test_rows_where_duplicate_select_columns_are_deduped(fresh_db):
# https://github.com/simonw/sqlite-utils/issues/624 # https://github.com/simonw/sqlite-utils/issues/624
fresh_db["t"].insert({"id": 1, "name": "Cleo"}) fresh_db.table("t").insert({"id": 1, "name": "Cleo"})
rows = list(fresh_db["t"].rows_where(select="id, id, name")) rows = list(fresh_db.table("t").rows_where(select="id, id, name"))
assert rows == [{"id": 1, "id_2": 1, "name": "Cleo"}] assert rows == [{"id": 1, "id_2": 1, "name": "Cleo"}]
@ -130,10 +130,10 @@ def test_pks_and_rows_where_view(fresh_db):
# an AttributeError from View lacking Table-only properties # an AttributeError from View lacking Table-only properties
from sqlite_utils.utils import sqlite3 from sqlite_utils.utils import sqlite3
fresh_db["dogs"].insert({"id": 1, "name": "Cleo"}, pk="id") fresh_db.table("dogs").insert({"id": 1, "name": "Cleo"}, pk="id")
fresh_db.create_view("dog_names", "select name from dogs") fresh_db.create_view("dog_names", "select name from dogs")
try: try:
result = list(fresh_db["dog_names"].pks_and_rows_where()) result = list(fresh_db.view("dog_names").pks_and_rows_where())
except sqlite3.OperationalError: except sqlite3.OperationalError:
pass # SQLite 3.36+: no such column: rowid pass # SQLite 3.36+: no such column: rowid
else: else:
@ -144,6 +144,6 @@ def test_pks_and_rows_where_view(fresh_db):
def test_pks_and_rows_where_compound_pk_declaration_order(fresh_db): def test_pks_and_rows_where_compound_pk_declaration_order(fresh_db):
# Compound pks are returned in PRIMARY KEY declaration order # Compound pks are returned in PRIMARY KEY declaration order
fresh_db.execute("create table t (b text, a text, primary key (a, b))") fresh_db.execute("create table t (b text, a text, primary key (a, b))")
fresh_db["t"].insert({"a": "A", "b": "B"}) fresh_db.table("t").insert({"a": "A", "b": "B"})
pks_and_rows = list(fresh_db["t"].pks_and_rows_where()) pks_and_rows = list(fresh_db.table("t").pks_and_rows_where())
assert pks_and_rows == [(("A", "B"), {"b": "B", "a": "A"})] assert pks_and_rows == [(("A", "B"), {"b": "B", "a": "A"})]

View file

@ -19,7 +19,7 @@ def test_sniff(tmpdir, filepath):
) )
assert result.exit_code == 0, result.stdout assert result.exit_code == 0, result.stdout
db = Database(db_path) db = Database(db_path)
assert list(db["creatures"].rows) == [ assert list(db.table("creatures").rows) == [
{"id": "1", "species": "dog", "name": "Cleo", "age": "5"}, {"id": "1", "species": "dog", "name": "Cleo", "age": "5"},
{"id": "2", "species": "dog", "name": "Pancakes", "age": "4"}, {"id": "2", "species": "dog", "name": "Pancakes", "age": "4"},
{"id": "3", "species": "cat", "name": "Mozie", "age": "8"}, {"id": "3", "species": "cat", "name": "Mozie", "age": "8"},

View file

@ -128,7 +128,7 @@ def test_transform_sql_table_with_primary_key(
def tracer(sql, params): def tracer(sql, params):
return captured.append((sql, params)) return captured.append((sql, params))
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
if use_pragma_foreign_keys: if use_pragma_foreign_keys:
fresh_db.conn.execute("PRAGMA foreign_keys=ON") fresh_db.conn.execute("PRAGMA foreign_keys=ON")
dogs.insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id") dogs.insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id")
@ -209,7 +209,7 @@ def test_transform_sql_table_with_no_primary_key(
def tracer(sql, params): def tracer(sql, params):
return captured.append((sql, params)) return captured.append((sql, params))
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
if use_pragma_foreign_keys: if use_pragma_foreign_keys:
fresh_db.conn.execute("PRAGMA foreign_keys=ON") fresh_db.conn.execute("PRAGMA foreign_keys=ON")
dogs.insert({"id": 1, "name": "Cleo", "age": "5"}) dogs.insert({"id": 1, "name": "Cleo", "age": "5"})
@ -229,7 +229,7 @@ def test_transform_sql_table_with_no_primary_key(
def test_transform_sql_with_no_primary_key_to_primary_key_of_id(fresh_db): def test_transform_sql_with_no_primary_key_to_primary_key_of_id(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo", "age": "5"}) dogs.insert({"id": 1, "name": "Cleo", "age": "5"})
assert ( assert (
dogs.schema dogs.schema
@ -244,7 +244,7 @@ def test_transform_sql_with_no_primary_key_to_primary_key_of_id(fresh_db):
def test_transform_rename_pk(fresh_db): def test_transform_rename_pk(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id") dogs.insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id")
dogs.transform(rename={"id": "pk"}) dogs.transform(rename={"id": "pk"})
assert ( assert (
@ -265,7 +265,7 @@ def test_transform_preserves_keyword_literal_defaults(fresh_db):
" note TEXT DEFAULT NULL" " note TEXT DEFAULT NULL"
")" ")"
) )
table = fresh_db["t"] table = fresh_db.table("t")
table.insert({"id": 1}) table.insert({"id": 1})
before = fresh_db.execute("SELECT is_active, flag, note FROM t").fetchone() before = fresh_db.execute("SELECT is_active, flag, note FROM t").fetchone()
assert before == (1, 0, None) assert before == (1, 0, None)
@ -288,7 +288,7 @@ def test_transform_preserves_keyword_literal_defaults(fresh_db):
def test_transform_not_null(fresh_db): def test_transform_not_null(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id") dogs.insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id")
dogs.transform(not_null={"name"}) dogs.transform(not_null={"name"})
assert ( assert (
@ -298,7 +298,7 @@ def test_transform_not_null(fresh_db):
def test_transform_remove_a_not_null(fresh_db): def test_transform_remove_a_not_null(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo", "age": "5"}, not_null={"age"}, pk="id") dogs.insert({"id": 1, "name": "Cleo", "age": "5"}, not_null={"age"}, pk="id")
dogs.transform(not_null={"name": True, "age": False}) dogs.transform(not_null={"name": True, "age": False})
assert ( assert (
@ -309,7 +309,7 @@ def test_transform_remove_a_not_null(fresh_db):
@pytest.mark.parametrize("not_null", [{"age"}, {"age": True}]) @pytest.mark.parametrize("not_null", [{"age"}, {"age": True}])
def test_transform_add_not_null_with_rename(fresh_db, not_null): def test_transform_add_not_null_with_rename(fresh_db, not_null):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id") dogs.insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id")
dogs.transform(not_null=not_null, rename={"age": "dog_age"}) dogs.transform(not_null=not_null, rename={"age": "dog_age"})
assert ( assert (
@ -319,7 +319,7 @@ def test_transform_add_not_null_with_rename(fresh_db, not_null):
def test_transform_defaults(fresh_db): def test_transform_defaults(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo", "age": 5}, pk="id") dogs.insert({"id": 1, "name": "Cleo", "age": 5}, pk="id")
dogs.transform(defaults={"age": 1}) dogs.transform(defaults={"age": 1})
assert ( assert (
@ -329,7 +329,7 @@ def test_transform_defaults(fresh_db):
def test_transform_defaults_and_rename_column(fresh_db): def test_transform_defaults_and_rename_column(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo", "age": 5}, pk="id") dogs.insert({"id": 1, "name": "Cleo", "age": 5}, pk="id")
dogs.transform(rename={"age": "dog_age"}, defaults={"age": 1}) dogs.transform(rename={"age": "dog_age"}, defaults={"age": 1})
assert ( assert (
@ -339,7 +339,7 @@ def test_transform_defaults_and_rename_column(fresh_db):
def test_remove_defaults(fresh_db): def test_remove_defaults(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo", "age": 5}, defaults={"age": 1}, pk="id") dogs.insert({"id": 1, "name": "Cleo", "age": 5}, defaults={"age": 1}, pk="id")
dogs.transform(defaults={"age": None}) dogs.transform(defaults={"age": None})
assert ( assert (
@ -350,8 +350,8 @@ def test_remove_defaults(fresh_db):
@pytest.fixture @pytest.fixture
def authors_db(fresh_db): def authors_db(fresh_db):
books = fresh_db["books"] books = fresh_db.table("books")
authors = fresh_db["authors"] authors = fresh_db.table("authors")
authors.insert({"id": 5, "name": "Jane McGonical"}, pk="id") authors.insert({"id": 5, "name": "Jane McGonical"}, pk="id")
books.insert( books.insert(
{"id": 2, "title": "Reality is Broken", "author_id": 5}, {"id": 2, "title": "Reality is Broken", "author_id": 5},
@ -362,13 +362,13 @@ def authors_db(fresh_db):
def test_transform_foreign_keys_persist(authors_db): def test_transform_foreign_keys_persist(authors_db):
assert authors_db["books"].foreign_keys == [ assert authors_db.table("books").foreign_keys == [
ForeignKey( ForeignKey(
table="books", column="author_id", other_table="authors", other_column="id" table="books", column="author_id", other_table="authors", other_column="id"
) )
] ]
authors_db["books"].transform(rename={"title": "book_title"}) authors_db.table("books").transform(rename={"title": "book_title"})
assert authors_db["books"].foreign_keys == [ assert authors_db.table("books").foreign_keys == [
ForeignKey( ForeignKey(
table="books", column="author_id", other_table="authors", other_column="id" table="books", column="author_id", other_table="authors", other_column="id"
) )
@ -381,8 +381,8 @@ def test_transform_foreign_keys_survive_renamed_column(
): ):
if use_pragma_foreign_keys: if use_pragma_foreign_keys:
authors_db.conn.execute("PRAGMA foreign_keys=ON") authors_db.conn.execute("PRAGMA foreign_keys=ON")
authors_db["books"].transform(rename={"author_id": "author_id_2"}) authors_db.table("books").transform(rename={"author_id": "author_id_2"})
assert authors_db["books"].foreign_keys == [ assert authors_db.table("books").foreign_keys == [
ForeignKey( ForeignKey(
table="books", table="books",
column="author_id_2", column="author_id_2",
@ -393,9 +393,9 @@ def test_transform_foreign_keys_survive_renamed_column(
def _add_country_city_continent(db): def _add_country_city_continent(db):
db["country"].insert({"id": 1, "name": "France"}, pk="id") db.table("country").insert({"id": 1, "name": "France"}, pk="id")
db["continent"].insert({"id": 2, "name": "Europe"}, pk="id") db.table("continent").insert({"id": 2, "name": "Europe"}, pk="id")
db["city"].insert({"id": 24, "name": "Paris"}, pk="id") db.table("city").insert({"id": 24, "name": "Paris"}, pk="id")
_CAVEAU = { _CAVEAU = {
@ -413,11 +413,11 @@ def test_transform_drop_foreign_keys(fresh_db, use_pragma_foreign_keys):
fresh_db.conn.execute("PRAGMA foreign_keys=ON") fresh_db.conn.execute("PRAGMA foreign_keys=ON")
# Create table with three foreign keys so we can drop two of them # Create table with three foreign keys so we can drop two of them
_add_country_city_continent(fresh_db) _add_country_city_continent(fresh_db)
fresh_db["places"].insert( fresh_db.table("places").insert(
_CAVEAU, _CAVEAU,
foreign_keys=("country", "continent", "city"), foreign_keys=("country", "continent", "city"),
) )
assert fresh_db["places"].foreign_keys == [ assert fresh_db.table("places").foreign_keys == [
ForeignKey( ForeignKey(
table="places", column="city", other_table="city", other_column="id" table="places", column="city", other_table="city", other_column="id"
), ),
@ -432,9 +432,9 @@ def test_transform_drop_foreign_keys(fresh_db, use_pragma_foreign_keys):
), ),
] ]
# Drop two of those foreign keys # Drop two of those foreign keys
fresh_db["places"].transform(drop_foreign_keys=("country", "continent")) fresh_db.table("places").transform(drop_foreign_keys=("country", "continent"))
# Should be only one foreign key now # Should be only one foreign key now
assert fresh_db["places"].foreign_keys == [ assert fresh_db.table("places").foreign_keys == [
ForeignKey(table="places", column="city", other_table="city", other_column="id") ForeignKey(table="places", column="city", other_table="city", other_column="id")
] ]
if use_pragma_foreign_keys: if use_pragma_foreign_keys:
@ -443,17 +443,17 @@ def test_transform_drop_foreign_keys(fresh_db, use_pragma_foreign_keys):
def test_transform_verify_foreign_keys(fresh_db): def test_transform_verify_foreign_keys(fresh_db):
fresh_db.conn.execute("PRAGMA foreign_keys=ON") fresh_db.conn.execute("PRAGMA foreign_keys=ON")
fresh_db["authors"].insert({"id": 3, "name": "Tina"}, pk="id") fresh_db.table("authors").insert({"id": 3, "name": "Tina"}, pk="id")
fresh_db["books"].insert( fresh_db.table("books").insert(
{"id": 1, "title": "Book", "author_id": 3}, pk="id", foreign_keys={"author_id"} {"id": 1, "title": "Book", "author_id": 3}, pk="id", foreign_keys={"author_id"}
) )
# Renaming the id column on authors should break everything # Renaming the id column on authors should break everything
with pytest.raises(OperationalError) as e: with pytest.raises(OperationalError) as e:
fresh_db["authors"].transform(rename={"id": "id2"}) fresh_db.table("authors").transform(rename={"id": "id2"})
assert e.value.args[0] == 'foreign key mismatch - "books" referencing "authors"' assert e.value.args[0] == 'foreign key mismatch - "books" referencing "authors"'
# This should have rolled us back # This should have rolled us back
assert ( assert (
fresh_db["authors"].schema fresh_db.table("authors").schema
== 'CREATE TABLE "authors" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT\n)' == 'CREATE TABLE "authors" (\n "id" INTEGER PRIMARY KEY,\n "name" TEXT\n)'
) )
assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0] assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0]
@ -476,20 +476,22 @@ def test_transform_on_delete_cascade_does_not_delete_records(
author_id INTEGER REFERENCES authors(id) ON DELETE CASCADE author_id INTEGER REFERENCES authors(id) ON DELETE CASCADE
); );
""") """)
fresh_db["authors"].insert({"id": 1, "name": "Ursula K. Le Guin"}) fresh_db.table("authors").insert({"id": 1, "name": "Ursula K. Le Guin"})
fresh_db["books"].insert({"id": 1, "title": "The Dispossessed", "author_id": 1}) fresh_db.table("books").insert(
{"id": 1, "title": "The Dispossessed", "author_id": 1}
)
# Transform the table on the other end of the cascading foreign key # Transform the table on the other end of the cascading foreign key
fresh_db["authors"].transform(rename={"name": "author_name"}) fresh_db.table("authors").transform(rename={"name": "author_name"})
assert list(fresh_db["authors"].rows) == [ assert list(fresh_db.table("authors").rows) == [
{"id": 1, "author_name": "Ursula K. Le Guin"} {"id": 1, "author_name": "Ursula K. Le Guin"}
] ]
assert list(fresh_db["books"].rows) == [ assert list(fresh_db.table("books").rows) == [
{"id": 1, "title": "The Dispossessed", "author_id": 1} {"id": 1, "title": "The Dispossessed", "author_id": 1}
] ]
# Transforming the table with the cascading foreign key should not # Transforming the table with the cascading foreign key should not
# delete its records either # delete its records either
fresh_db["books"].transform(rename={"title": "book_title"}) fresh_db.table("books").transform(rename={"title": "book_title"})
assert list(fresh_db["books"].rows) == [ assert list(fresh_db.table("books").rows) == [
{"id": 1, "book_title": "The Dispossessed", "author_id": 1} {"id": 1, "book_title": "The Dispossessed", "author_id": 1}
] ]
if use_pragma_foreign_keys: if use_pragma_foreign_keys:
@ -511,17 +513,19 @@ def test_transform_in_transaction_refuses_destructive_on_delete(fresh_db, on_del
author_id INTEGER REFERENCES authors(id) ON DELETE {on_delete} author_id INTEGER REFERENCES authors(id) ON DELETE {on_delete}
); );
""") """)
fresh_db["authors"].insert({"id": 1, "name": "Ursula K. Le Guin"}) fresh_db.table("authors").insert({"id": 1, "name": "Ursula K. Le Guin"})
fresh_db["books"].insert({"id": 1, "title": "The Dispossessed", "author_id": 1}) fresh_db.table("books").insert(
previous_schema = fresh_db["authors"].schema {"id": 1, "title": "The Dispossessed", "author_id": 1}
)
previous_schema = fresh_db.table("authors").schema
with fresh_db.atomic(), pytest.raises(TransactionError) as excinfo: with fresh_db.atomic(), pytest.raises(TransactionError) as excinfo:
fresh_db["authors"].transform(rename={"name": "author_name"}) fresh_db.table("authors").transform(rename={"name": "author_name"})
message = str(excinfo.value) message = str(excinfo.value)
assert "books" in message assert "books" in message
assert f"ON DELETE {on_delete.upper()}" in message assert f"ON DELETE {on_delete.upper()}" in message
# Nothing should have changed # Nothing should have changed
assert fresh_db["authors"].schema == previous_schema assert fresh_db.table("authors").schema == previous_schema
assert list(fresh_db["books"].rows) == [ assert list(fresh_db.table("books").rows) == [
{"id": 1, "title": "The Dispossessed", "author_id": 1} {"id": 1, "title": "The Dispossessed", "author_id": 1}
] ]
assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0] assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0]
@ -538,16 +542,16 @@ def test_transform_in_transaction_refuses_self_referential_cascade(fresh_db):
parent_id INTEGER REFERENCES categories(id) ON DELETE CASCADE parent_id INTEGER REFERENCES categories(id) ON DELETE CASCADE
); );
""") """)
fresh_db["categories"].insert_all( fresh_db.table("categories").insert_all(
[ [
{"id": 1, "name": "Fiction", "parent_id": None}, {"id": 1, "name": "Fiction", "parent_id": None},
{"id": 2, "name": "Science Fiction", "parent_id": 1}, {"id": 2, "name": "Science Fiction", "parent_id": 1},
] ]
) )
with fresh_db.atomic(), pytest.raises(TransactionError) as excinfo: with fresh_db.atomic(), pytest.raises(TransactionError) as excinfo:
fresh_db["categories"].transform(rename={"name": "title"}) fresh_db.table("categories").transform(rename={"name": "title"})
assert "categories" in str(excinfo.value) assert "categories" in str(excinfo.value)
assert fresh_db["categories"].count == 2 assert fresh_db.table("categories").count == 2
def test_transform_in_transaction_allowed_with_no_action_foreign_key(fresh_db): def test_transform_in_transaction_allowed_with_no_action_foreign_key(fresh_db):
@ -562,14 +566,16 @@ def test_transform_in_transaction_allowed_with_no_action_foreign_key(fresh_db):
author_id INTEGER REFERENCES authors(id) author_id INTEGER REFERENCES authors(id)
); );
""") """)
fresh_db["authors"].insert({"id": 1, "name": "Ursula K. Le Guin"}) fresh_db.table("authors").insert({"id": 1, "name": "Ursula K. Le Guin"})
fresh_db["books"].insert({"id": 1, "title": "The Dispossessed", "author_id": 1}) fresh_db.table("books").insert(
{"id": 1, "title": "The Dispossessed", "author_id": 1}
)
with fresh_db.atomic(): with fresh_db.atomic():
fresh_db["authors"].transform(rename={"name": "author_name"}) fresh_db.table("authors").transform(rename={"name": "author_name"})
assert list(fresh_db["authors"].rows) == [ assert list(fresh_db.table("authors").rows) == [
{"id": 1, "author_name": "Ursula K. Le Guin"} {"id": 1, "author_name": "Ursula K. Le Guin"}
] ]
assert list(fresh_db["books"].rows) == [ assert list(fresh_db.table("books").rows) == [
{"id": 1, "title": "The Dispossessed", "author_id": 1} {"id": 1, "title": "The Dispossessed", "author_id": 1}
] ]
assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0] assert fresh_db.conn.execute("PRAGMA foreign_keys").fetchone()[0]
@ -587,11 +593,13 @@ def test_transform_in_transaction_allowed_for_child_table(fresh_db):
author_id INTEGER REFERENCES authors(id) ON DELETE CASCADE author_id INTEGER REFERENCES authors(id) ON DELETE CASCADE
); );
""") """)
fresh_db["authors"].insert({"id": 1, "name": "Ursula K. Le Guin"}) fresh_db.table("authors").insert({"id": 1, "name": "Ursula K. Le Guin"})
fresh_db["books"].insert({"id": 1, "title": "The Dispossessed", "author_id": 1}) fresh_db.table("books").insert(
{"id": 1, "title": "The Dispossessed", "author_id": 1}
)
with fresh_db.atomic(): with fresh_db.atomic():
fresh_db["books"].transform(rename={"title": "book_title"}) fresh_db.table("books").transform(rename={"title": "book_title"})
assert list(fresh_db["books"].rows) == [ assert list(fresh_db.table("books").rows) == [
{"id": 1, "book_title": "The Dispossessed", "author_id": 1} {"id": 1, "book_title": "The Dispossessed", "author_id": 1}
] ]
@ -607,24 +615,28 @@ def test_transform_in_transaction_allowed_with_foreign_keys_off(fresh_db):
author_id INTEGER REFERENCES authors(id) ON DELETE CASCADE author_id INTEGER REFERENCES authors(id) ON DELETE CASCADE
); );
""") """)
fresh_db["authors"].insert({"id": 1, "name": "Ursula K. Le Guin"}) fresh_db.table("authors").insert({"id": 1, "name": "Ursula K. Le Guin"})
fresh_db["books"].insert({"id": 1, "title": "The Dispossessed", "author_id": 1}) fresh_db.table("books").insert(
{"id": 1, "title": "The Dispossessed", "author_id": 1}
)
with fresh_db.atomic(): with fresh_db.atomic():
fresh_db["authors"].transform(rename={"name": "author_name"}) fresh_db.table("authors").transform(rename={"name": "author_name"})
assert list(fresh_db["books"].rows) == [ assert list(fresh_db.table("books").rows) == [
{"id": 1, "title": "The Dispossessed", "author_id": 1} {"id": 1, "title": "The Dispossessed", "author_id": 1}
] ]
def test_transform_add_foreign_keys_from_scratch(fresh_db): def test_transform_add_foreign_keys_from_scratch(fresh_db):
_add_country_city_continent(fresh_db) _add_country_city_continent(fresh_db)
fresh_db["places"].insert(_CAVEAU) fresh_db.table("places").insert(_CAVEAU)
# Should have no foreign keys # Should have no foreign keys
assert fresh_db["places"].foreign_keys == [] assert fresh_db.table("places").foreign_keys == []
# Now add them using .transform() # Now add them using .transform()
fresh_db["places"].transform(add_foreign_keys=("country", "continent", "city")) fresh_db.table("places").transform(
add_foreign_keys=("country", "continent", "city")
)
# Should now have all three: # Should now have all three:
assert fresh_db["places"].foreign_keys == [ assert fresh_db.table("places").foreign_keys == [
ForeignKey( ForeignKey(
table="places", column="city", other_table="city", other_column="id" table="places", column="city", other_table="city", other_column="id"
), ),
@ -638,7 +650,7 @@ def test_transform_add_foreign_keys_from_scratch(fresh_db):
table="places", column="country", other_table="country", other_column="id" table="places", column="country", other_table="country", other_column="id"
), ),
] ]
assert fresh_db["places"].schema == ( assert fresh_db.table("places").schema == (
'CREATE TABLE "places" (\n' 'CREATE TABLE "places" (\n'
' "id" INTEGER,\n' ' "id" INTEGER,\n'
' "name" TEXT,\n' ' "name" TEXT,\n'
@ -662,18 +674,18 @@ def test_transform_add_foreign_keys_from_scratch(fresh_db):
) )
def test_transform_add_foreign_keys_from_partial(fresh_db, add_foreign_keys): def test_transform_add_foreign_keys_from_partial(fresh_db, add_foreign_keys):
_add_country_city_continent(fresh_db) _add_country_city_continent(fresh_db)
fresh_db["places"].insert( fresh_db.table("places").insert(
_CAVEAU, _CAVEAU,
foreign_keys=("city",), foreign_keys=("city",),
) )
# Should have one foreign keys # Should have one foreign keys
assert fresh_db["places"].foreign_keys == [ assert fresh_db.table("places").foreign_keys == [
ForeignKey(table="places", column="city", other_table="city", other_column="id") ForeignKey(table="places", column="city", other_table="city", other_column="id")
] ]
# Now add three more using .transform() # Now add three more using .transform()
fresh_db["places"].transform(add_foreign_keys=add_foreign_keys) fresh_db.table("places").transform(add_foreign_keys=add_foreign_keys)
# Should now have all three: # Should now have all three:
assert fresh_db["places"].foreign_keys == [ assert fresh_db.table("places").foreign_keys == [
ForeignKey( ForeignKey(
table="places", column="city", other_table="city", other_column="id" table="places", column="city", other_table="city", other_column="id"
), ),
@ -702,14 +714,14 @@ def test_transform_add_foreign_keys_from_partial(fresh_db, add_foreign_keys):
) )
def test_transform_replace_foreign_keys(fresh_db, foreign_keys): def test_transform_replace_foreign_keys(fresh_db, foreign_keys):
_add_country_city_continent(fresh_db) _add_country_city_continent(fresh_db)
fresh_db["places"].insert( fresh_db.table("places").insert(
_CAVEAU, _CAVEAU,
foreign_keys=("city",), foreign_keys=("city",),
) )
assert len(fresh_db["places"].foreign_keys) == 1 assert len(fresh_db.table("places").foreign_keys) == 1
# Replace with two different ones # Replace with two different ones
fresh_db["places"].transform(foreign_keys=foreign_keys) fresh_db.table("places").transform(foreign_keys=foreign_keys)
assert fresh_db["places"].schema == ( assert fresh_db.table("places").schema == (
'CREATE TABLE "places" (\n' 'CREATE TABLE "places" (\n'
' "id" INTEGER,\n' ' "id" INTEGER,\n'
' "name" TEXT,\n' ' "name" TEXT,\n'
@ -729,7 +741,7 @@ def test_transform_preserves_rowids(fresh_db, table_type):
pk = ("id", "name") pk = ("id", "name")
elif table_type == "rowid": elif table_type == "rowid":
pk = None pk = None
fresh_db["places"].insert_all( fresh_db.table("places").insert_all(
[ [
{"id": "1", "name": "Paris", "country": "France"}, {"id": "1", "name": "Paris", "country": "France"},
{"id": "2", "name": "London", "country": "UK"}, {"id": "2", "name": "London", "country": "UK"},
@ -738,13 +750,13 @@ def test_transform_preserves_rowids(fresh_db, table_type):
pk=pk, pk=pk,
) )
# Now delete and insert a row to mix up the `rowid` sequence # Now delete and insert a row to mix up the `rowid` sequence
fresh_db["places"].delete_where("id = ?", ["2"]) fresh_db.table("places").delete_where("id = ?", ["2"])
fresh_db["places"].insert({"id": "4", "name": "London", "country": "UK"}) fresh_db.table("places").insert({"id": "4", "name": "London", "country": "UK"})
previous_rows = [ previous_rows = [
tuple(row) for row in fresh_db.execute("select rowid, id, name from places") tuple(row) for row in fresh_db.execute("select rowid, id, name from places")
] ]
# Transform it # Transform it
fresh_db["places"].transform(column_order=("country", "name")) fresh_db.table("places").transform(column_order=("country", "name"))
# Should be the same # Should be the same
next_rows = [ next_rows = [
tuple(row) for row in fresh_db.execute("select rowid, id, name from places") tuple(row) for row in fresh_db.execute("select rowid, id, name from places")
@ -774,7 +786,7 @@ def test_transform_strict(fresh_db, initial_strict, transform_strict, expected_s
def test_transform_to_strict_with_invalid_data(fresh_db): def test_transform_to_strict_with_invalid_data(fresh_db):
if not fresh_db.supports_strict: if not fresh_db.supports_strict:
pytest.skip("SQLite version does not support strict tables") pytest.skip("SQLite version does not support strict tables")
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.create({"id": int}) dogs.create({"id": int})
dogs.insert({"id": "not-an-integer"}) dogs.insert({"id": "not-an-integer"})
@ -801,7 +813,7 @@ def test_transform_strict_updates_default(fresh_db):
@pytest.mark.parametrize("method_name", ("transform", "transform_sql")) @pytest.mark.parametrize("method_name", ("transform", "transform_sql"))
def test_transform_to_strict_not_supported(fresh_db, method_name): def test_transform_to_strict_not_supported(fresh_db, method_name):
table = fresh_db["items"] table = fresh_db.table("items")
table.create({"id": int}) table.create({"id": int})
fresh_db._supports_strict = False fresh_db._supports_strict = False
@ -823,7 +835,7 @@ def test_transform_to_strict_not_supported(fresh_db, method_name):
def test_transform_indexes(fresh_db, indexes, transform_params): def test_transform_indexes(fresh_db, indexes, transform_params):
# https://github.com/simonw/sqlite-utils/issues/633 # https://github.com/simonw/sqlite-utils/issues/633
# New table should have same indexes as old table after transformation # New table should have same indexes as old table after transformation
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo", "age": 5, "breed": "Labrador"}, pk="id") dogs.insert({"id": 1, "name": "Cleo", "age": 5, "breed": "Labrador"}, pk="id")
for index in indexes: for index in indexes:
@ -849,13 +861,13 @@ def test_transform_indexes(fresh_db, indexes, transform_params):
if "keep_table" in transform_params: if "keep_table" in transform_params:
assert all( assert all(
index.origin == "pk" index.origin == "pk"
for index in fresh_db[transform_params["keep_table"]].indexes for index in fresh_db.table(transform_params["keep_table"]).indexes
) )
def test_transform_retains_indexes_with_foreign_keys(fresh_db): def test_transform_retains_indexes_with_foreign_keys(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
owners = fresh_db["owners"] owners = fresh_db.table("owners")
dogs.insert({"id": 1, "name": "Cleo", "owner_id": 1}, pk="id") dogs.insert({"id": 1, "name": "Cleo", "owner_id": 1}, pk="id")
owners.insert({"id": 1, "name": "Alice"}, pk="id") owners.insert({"id": 1, "name": "Alice"}, pk="id")
@ -890,7 +902,7 @@ def test_transform_retains_indexes_with_foreign_keys(fresh_db):
) )
def test_transform_with_indexes_errors(fresh_db, transform_params): def test_transform_with_indexes_errors(fresh_db, transform_params):
# Should error with a compound (name, age) index if age is renamed or dropped # Should error with a compound (name, age) index if age is renamed or dropped
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo", "age": 5}, pk="id") dogs.insert({"id": 1, "name": "Cleo", "age": 5}, pk="id")
dogs.create_index(["name", "age"]) dogs.create_index(["name", "age"])
@ -906,7 +918,7 @@ def test_transform_with_indexes_errors(fresh_db, transform_params):
def test_transform_with_unique_constraint_implicit_index(fresh_db): def test_transform_with_unique_constraint_implicit_index(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
# Create a table with a UNIQUE constraint on 'name', which creates an implicit index # Create a table with a UNIQUE constraint on 'name', which creates an implicit index
fresh_db.execute(""" fresh_db.execute("""
CREATE TABLE dogs ( CREATE TABLE dogs (
@ -933,7 +945,7 @@ def test_transform_with_unique_constraint_implicit_index(fresh_db):
def test_transform_preserves_view(fresh_db): def test_transform_preserves_view(fresh_db):
# https://github.com/simonw/sqlite-utils/issues/831 # https://github.com/simonw/sqlite-utils/issues/831
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo"}, pk="id") dogs.insert({"id": 1, "name": "Cleo"}, pk="id")
fresh_db.execute("create view dogs_view as select id, name from dogs") fresh_db.execute("create view dogs_view as select id, name from dogs")
view_sql_before = fresh_db.execute( view_sql_before = fresh_db.execute(
@ -958,8 +970,8 @@ def test_transform_preserves_view(fresh_db):
def test_transform_variants_preserve_view(fresh_db, transform_params): def test_transform_variants_preserve_view(fresh_db, transform_params):
# Covers retyping, changing primary key and foreign key modifications, # Covers retyping, changing primary key and foreign key modifications,
# with a view whose columns are untouched by the transform # with a view whose columns are untouched by the transform
fresh_db["other"].insert({"id": 1}, pk="id") fresh_db.table("other").insert({"id": 1}, pk="id")
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo", "other_id": 1}, pk="id") dogs.insert({"id": 1, "name": "Cleo", "other_id": 1}, pk="id")
if "drop_foreign_keys" in transform_params: if "drop_foreign_keys" in transform_params:
dogs.transform(add_foreign_keys=[("other_id", "other", "id")]) dogs.transform(add_foreign_keys=[("other_id", "other", "id")])
@ -972,13 +984,13 @@ def test_transform_variants_preserve_view(fresh_db, transform_params):
"select sql from sqlite_master where name = 'dogs_view'" "select sql from sqlite_master where name = 'dogs_view'"
).fetchone()[0] ).fetchone()[0]
assert view_sql_before == view_sql_after assert view_sql_before == view_sql_after
assert list(fresh_db["dogs_view"].rows) == [{"id": 1, "name": "Cleo"}] assert list(fresh_db.view("dogs_view").rows) == [{"id": 1, "name": "Cleo"}]
def test_transform_view_referencing_renamed_column(fresh_db): def test_transform_view_referencing_renamed_column(fresh_db):
# The view survives but querying it raises "no such column" - inherent # The view survives but querying it raises "no such column" - inherent
# to SQLite views, whose SQL is stored as text # to SQLite views, whose SQL is stored as text
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo"}, pk="id") dogs.insert({"id": 1, "name": "Cleo"}, pk="id")
fresh_db.execute("create view dogs_view as select id, name from dogs") fresh_db.execute("create view dogs_view as select id, name from dogs")
dogs.transform(rename={"name": "title"}) dogs.transform(rename={"name": "title"})
@ -987,7 +999,7 @@ def test_transform_view_referencing_renamed_column(fresh_db):
def test_transform_view_on_view(fresh_db): def test_transform_view_on_view(fresh_db):
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo"}, pk="id") dogs.insert({"id": 1, "name": "Cleo"}, pk="id")
fresh_db.execute("create view v1 as select id, name from dogs") fresh_db.execute("create view v1 as select id, name from dogs")
fresh_db.execute("create view v2 as select name from v1") fresh_db.execute("create view v2 as select name from v1")
@ -999,13 +1011,13 @@ def test_transform_view_on_view(fresh_db):
"select sql from sqlite_master where type = 'view' order by name" "select sql from sqlite_master where type = 'view' order by name"
).fetchall() ).fetchall()
assert sqls_before == sqls_after assert sqls_before == sqls_after
assert list(fresh_db["v2"].rows) == [{"name": "Cleo"}] assert list(fresh_db.view("v2").rows) == [{"name": "Cleo"}]
def test_transform_keep_table_does_not_repoint_view(fresh_db): def test_transform_keep_table_does_not_repoint_view(fresh_db):
# Without legacy_alter_table the ALTER TABLE dogs RENAME TO dogs_backup # Without legacy_alter_table the ALTER TABLE dogs RENAME TO dogs_backup
# step would rewrite the view to select from "dogs_backup" # step would rewrite the view to select from "dogs_backup"
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo"}, pk="id") dogs.insert({"id": 1, "name": "Cleo"}, pk="id")
fresh_db.execute("create view dogs_view as select id, name from dogs") fresh_db.execute("create view dogs_view as select id, name from dogs")
dogs.transform(types={"name": str}, keep_table="dogs_backup") dogs.transform(types={"name": str}, keep_table="dogs_backup")
@ -1015,7 +1027,7 @@ def test_transform_keep_table_does_not_repoint_view(fresh_db):
assert "dogs_backup" not in view_sql assert "dogs_backup" not in view_sql
# View reads from the live table, not the frozen backup # View reads from the live table, not the frozen backup
dogs.insert({"id": 2, "name": "Pancakes"}) dogs.insert({"id": 2, "name": "Pancakes"})
assert list(fresh_db["dogs_view"].rows) == [ assert list(fresh_db.view("dogs_view").rows) == [
{"id": 1, "name": "Cleo"}, {"id": 1, "name": "Cleo"},
{"id": 2, "name": "Pancakes"}, {"id": 2, "name": "Pancakes"},
] ]
@ -1024,7 +1036,7 @@ def test_transform_keep_table_does_not_repoint_view(fresh_db):
def test_transform_sql_standalone_statements_work_with_view(fresh_db): def test_transform_sql_standalone_statements_work_with_view(fresh_db):
# The documented "run these statements yourself" workflow should be # The documented "run these statements yourself" workflow should be
# standalone-correct, so the pragmas must come from transform_sql() # standalone-correct, so the pragmas must come from transform_sql()
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo"}, pk="id") dogs.insert({"id": 1, "name": "Cleo"}, pk="id")
fresh_db.execute("create view dogs_view as select id, name from dogs") fresh_db.execute("create view dogs_view as select id, name from dogs")
sqls = dogs.transform_sql(types={"name": str}, tmp_suffix="suffix") sqls = dogs.transform_sql(types={"name": str}, tmp_suffix="suffix")
@ -1033,12 +1045,12 @@ def test_transform_sql_standalone_statements_work_with_view(fresh_db):
assert sqls[-1] == "PRAGMA legacy_alter_table=OFF;" assert sqls[-1] == "PRAGMA legacy_alter_table=OFF;"
for sql in sqls: for sql in sqls:
fresh_db.execute(sql) fresh_db.execute(sql)
assert list(fresh_db["dogs_view"].rows) == [{"id": 1, "name": "Cleo"}] assert list(fresh_db.view("dogs_view").rows) == [{"id": 1, "name": "Cleo"}]
def test_transform_with_view_in_open_transaction(fresh_db): def test_transform_with_view_in_open_transaction(fresh_db):
fresh_db.conn.execute("PRAGMA foreign_keys=ON") fresh_db.conn.execute("PRAGMA foreign_keys=ON")
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo"}, pk="id") dogs.insert({"id": 1, "name": "Cleo"}, pk="id")
fresh_db.execute("create view dogs_view as select id, name from dogs") fresh_db.execute("create view dogs_view as select id, name from dogs")
with fresh_db.conn: with fresh_db.conn:
@ -1054,7 +1066,7 @@ def test_transform_with_view_in_open_transaction(fresh_db):
def test_transform_restores_legacy_alter_table_setting(fresh_db): def test_transform_restores_legacy_alter_table_setting(fresh_db):
if sqlite3.sqlite_version_info < (3, 25, 0): if sqlite3.sqlite_version_info < (3, 25, 0):
pytest.skip("legacy_alter_table pragma requires SQLite 3.25 or higher") pytest.skip("legacy_alter_table pragma requires SQLite 3.25 or higher")
dogs = fresh_db["dogs"] dogs = fresh_db.table("dogs")
dogs.insert({"id": 1, "name": "Cleo"}, pk="id") dogs.insert({"id": 1, "name": "Cleo"}, pk="id")
# Default is OFF, reset to OFF afterwards # Default is OFF, reset to OFF afterwards
dogs.transform(types={"name": str}) dogs.transform(types={"name": str})
@ -1075,7 +1087,7 @@ def test_transform_preserves_check_constraints(fresh_db):
CONSTRAINT nonzero_id CHECK(id != 0) CONSTRAINT nonzero_id CHECK(id != 0)
) )
""") """)
scores = fresh_db["scores"] scores = fresh_db.table("scores")
scores.insert({"id": 1, "score": 50}) scores.insert({"id": 1, "score": 50})
scores.transform() scores.transform()
assert scores.checks == [ assert scores.checks == [
@ -1095,7 +1107,7 @@ def test_transform_preserves_check_ending_in_line_comment(fresh_db):
) )
) )
""") """)
inventory = fresh_db["inventory"] inventory = fresh_db.table("inventory")
inventory.transform(types={"quantity": float}) inventory.transform(types={"quantity": float})
assert inventory.checks == [Check("quantity >= 0 -- Quantity cannot be negative")] assert inventory.checks == [Check("quantity >= 0 -- Quantity cannot be negative")]
with pytest.raises(sqlite3.IntegrityError, match="CHECK constraint failed"): with pytest.raises(sqlite3.IntegrityError, match="CHECK constraint failed"):
@ -1113,7 +1125,7 @@ def test_transform_preserves_comments_owned_by_columns(fresh_db):
age INTEGER -- May be NULL age INTEGER -- May be NULL
) )
""") """)
people = fresh_db["people"] people = fresh_db.table("people")
people.insert({"id": 1, "name": "Cleo", "age": 5}) people.insert({"id": 1, "name": "Cleo", "age": 5})
people.transform( people.transform(
rename={"name": "display_name"}, rename={"name": "display_name"},
@ -1143,8 +1155,8 @@ def test_transform_drops_comments_owned_by_dropped_column(fresh_db):
obsolete TEXT /* Drop this too */ obsolete TEXT /* Drop this too */
) )
""") """)
fresh_db["t"].transform(drop={"obsolete"}) fresh_db.table("t").transform(drop={"obsolete"})
schema = fresh_db["t"].schema schema = fresh_db.table("t").schema
assert "Keep this explanation" in schema assert "Keep this explanation" in schema
assert "Drop this explanation" not in schema assert "Drop this explanation" not in schema
assert "Drop this too" not in schema assert "Drop this too" not in schema
@ -1159,7 +1171,7 @@ def test_transform_renames_columns_inside_check_constraints(fresh_db):
CONSTRAINT within_maximum CHECK(quantity <= maximum) CONSTRAINT within_maximum CHECK(quantity <= maximum)
) )
""") """)
inventory = fresh_db["inventory"] inventory = fresh_db.table("inventory")
inventory.insert({"quantity": 2, "maximum": 3}) inventory.insert({"quantity": 2, "maximum": 3})
inventory.transform(rename={"quantity": "amount"}) inventory.transform(rename={"quantity": "amount"})
assert inventory.checks == [ assert inventory.checks == [
@ -1182,7 +1194,7 @@ def test_transform_check_rewrite_preserves_functions_and_quotes(fresh_db):
CHECK(length("old name") > 0 AND length != '') CHECK(length("old name") > 0 AND length != '')
) )
""") """)
items = fresh_db["items"] items = fresh_db.table("items")
items.insert({"length": "label", "old name": "hello"}) items.insert({"length": "label", "old name": "hello"})
items.transform(rename={"length": "description", "old name": "new name"}) items.transform(rename={"length": "description", "old name": "new name"})
assert items.checks == [Check("length(\"new name\") > 0 AND description != ''")] assert items.checks == [Check("length(\"new name\") > 0 AND description != ''")]
@ -1190,9 +1202,9 @@ def test_transform_check_rewrite_preserves_functions_and_quotes(fresh_db):
def test_transform_check_rewrite_quotes_keyword_column(fresh_db): def test_transform_check_rewrite_quotes_keyword_column(fresh_db):
fresh_db.execute("CREATE TABLE t(old_name TEXT CHECK(old_name != ''))") fresh_db.execute("CREATE TABLE t(old_name TEXT CHECK(old_name != ''))")
fresh_db["t"].insert({"old_name": "value"}) fresh_db.table("t").insert({"old_name": "value"})
fresh_db["t"].transform(rename={"old_name": "select"}) fresh_db.table("t").transform(rename={"old_name": "select"})
assert fresh_db["t"].checks == [Check("\"select\" != ''", column="select")] assert fresh_db.table("t").checks == [Check("\"select\" != ''", column="select")]
def test_transform_check_rewrite_does_not_rename_collations_or_cast_types(fresh_db): def test_transform_check_rewrite_does_not_rename_collations_or_cast_types(fresh_db):
@ -1209,9 +1221,9 @@ def test_transform_check_rewrite_does_not_rename_collations_or_cast_types(fresh_
) )
) )
""") """)
fresh_db["t"].insert({"nocase": "n", "kind": "k", "other": "o"}) fresh_db.table("t").insert({"nocase": "n", "kind": "k", "other": "o"})
fresh_db["t"].transform(rename={"nocase": "label", "kind": "category"}) fresh_db.table("t").transform(rename={"nocase": "label", "kind": "category"})
check = fresh_db["t"].checks[0].check check = fresh_db.table("t").checks[0].check
assert "COLLATE nocase" in check assert "COLLATE nocase" in check
assert "AS kind" in check assert "AS kind" in check
assert "AND label != ''" in check assert "AND label != ''" in check
@ -1226,9 +1238,9 @@ def test_transform_drops_check_owned_by_dropped_column(fresh_db):
CHECK(id > 0) CHECK(id > 0)
) )
""") """)
fresh_db["t"].insert({"id": 1, "obsolete": 2}) fresh_db.table("t").insert({"id": 1, "obsolete": 2})
fresh_db["t"].transform(drop={"obsolete"}) fresh_db.table("t").transform(drop={"obsolete"})
assert fresh_db["t"].checks == [Check("id > 0")] assert fresh_db.table("t").checks == [Check("id > 0")]
def test_transform_refuses_to_drop_column_used_by_remaining_check(fresh_db): def test_transform_refuses_to_drop_column_used_by_remaining_check(fresh_db):
@ -1239,7 +1251,7 @@ def test_transform_refuses_to_drop_column_used_by_remaining_check(fresh_db):
CHECK(minimum <= maximum) CHECK(minimum <= maximum)
) )
""") """)
ranges = fresh_db["ranges"] ranges = fresh_db.table("ranges")
ranges.insert({"minimum": 1, "maximum": 2}) ranges.insert({"minimum": 1, "maximum": 2})
schema_before = ranges.schema schema_before = ranges.schema
with pytest.raises( with pytest.raises(

View file

@ -7,14 +7,14 @@ from sqlite_utils.db import NotFoundError
def test_update_rowid_table(fresh_db): def test_update_rowid_table(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
rowid = table.insert({"foo": "bar"}).last_pk rowid = table.insert({"foo": "bar"}).last_pk
table.update(rowid, {"foo": "baz"}) table.update(rowid, {"foo": "baz"})
assert [{"foo": "baz"}] == list(table.rows) assert [{"foo": "baz"}] == list(table.rows)
def test_update_pk_table(fresh_db): def test_update_pk_table(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
pk = table.insert({"foo": "bar", "id": 5}, pk="id").last_pk pk = table.insert({"foo": "bar", "id": 5}, pk="id").last_pk
assert 5 == pk assert 5 == pk
table.update(pk, {"foo": "baz"}) table.update(pk, {"foo": "baz"})
@ -22,7 +22,7 @@ def test_update_pk_table(fresh_db):
def test_update_compound_pk_table(fresh_db): def test_update_compound_pk_table(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
pk = table.insert({"id1": 5, "id2": 3, "v": 1}, pk=("id1", "id2")).last_pk pk = table.insert({"id1": 5, "id2": 3, "v": 1}, pk=("id1", "id2")).last_pk
assert (5, 3) == pk assert (5, 3) == pk
table.update(pk, {"v": 2}) table.update(pk, {"v": 2})
@ -42,14 +42,14 @@ def test_update_compound_pk_table(fresh_db):
), ),
) )
def test_update_invalid_pk(fresh_db, pk, update_pk): def test_update_invalid_pk(fresh_db, pk, update_pk):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert({"id1": 5, "id2": 3, "v": 1}, pk=pk) table.insert({"id1": 5, "id2": 3, "v": 1}, pk=pk)
with pytest.raises(NotFoundError): with pytest.raises(NotFoundError):
table.update(update_pk, {"v": 2}) table.update(update_pk, {"v": 2})
def test_update_alter(fresh_db): def test_update_alter(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
rowid = table.insert({"foo": "bar"}).last_pk rowid = table.insert({"foo": "bar"}).last_pk
table.update(rowid, {"new_col": 1.2}, alter=True) table.update(rowid, {"new_col": 1.2}, alter=True)
assert [{"foo": "bar", "new_col": 1.2}] == list(table.rows) assert [{"foo": "bar", "new_col": 1.2}] == list(table.rows)
@ -72,7 +72,7 @@ def test_update_alter(fresh_db):
def test_update_alter_with_special_column_characters(fresh_db): def test_update_alter_with_special_column_characters(fresh_db):
# With double-quote escaping, columns with special characters are now valid # With double-quote escaping, columns with special characters are now valid
table = fresh_db["table"] table = fresh_db.table("table")
rowid = table.insert({"foo": "bar"}).last_pk rowid = table.insert({"foo": "bar"}).last_pk
table.update(rowid, {"new_col[abc]": 1.2}, alter=True) table.update(rowid, {"new_col[abc]": 1.2}, alter=True)
assert list(table.rows) == [{"foo": "bar", "new_col[abc]": 1.2}] assert list(table.rows) == [{"foo": "bar", "new_col[abc]": 1.2}]
@ -106,8 +106,8 @@ def test_update_with_no_values_sets_last_pk(fresh_db):
), ),
) )
def test_update_dictionaries_and_lists_as_json(fresh_db, data_structure): def test_update_dictionaries_and_lists_as_json(fresh_db, data_structure):
fresh_db["test"].insert({"id": 1, "data": ""}, pk="id") fresh_db.table("test").insert({"id": 1, "data": ""}, pk="id")
fresh_db["test"].update(1, {"data": data_structure}) fresh_db.table("test").update(1, {"data": data_structure})
row = fresh_db.execute("select id, data from test").fetchone() row = fresh_db.execute("select id, data from test").fetchone()
assert row[0] == 1 assert row[0] == 1
assert data_structure == json.loads(row[1]) assert data_structure == json.loads(row[1])

View file

@ -7,7 +7,7 @@ from sqlite_utils.db import PrimaryKeyRequired
@pytest.mark.parametrize("use_old_upsert", (False, True)) @pytest.mark.parametrize("use_old_upsert", (False, True))
def test_upsert(use_old_upsert): def test_upsert(use_old_upsert):
db = Database(memory=True, use_old_upsert=use_old_upsert) db = Database(memory=True, use_old_upsert=use_old_upsert)
table = db["table"] table = db.table("table")
table.insert({"id": 1, "name": "Cleo"}, pk="id") table.insert({"id": 1, "name": "Cleo"}, pk="id")
table.upsert({"id": 1, "age": 5}, pk="id", alter=True) table.upsert({"id": 1, "age": 5}, pk="id", alter=True)
assert list(table.rows) == [{"id": 1, "name": "Cleo", "age": 5}] assert list(table.rows) == [{"id": 1, "name": "Cleo", "age": 5}]
@ -15,7 +15,7 @@ def test_upsert(use_old_upsert):
def test_upsert_all(fresh_db): def test_upsert_all(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.upsert_all([{"id": 1, "name": "Cleo"}, {"id": 2, "name": "Nixie"}], pk="id") table.upsert_all([{"id": 1, "name": "Cleo"}, {"id": 2, "name": "Nixie"}], pk="id")
table.upsert_all([{"id": 1, "age": 5}, {"id": 2, "age": 5}], pk="id", alter=True) table.upsert_all([{"id": 1, "age": 5}, {"id": 2, "age": 5}], pk="id", alter=True)
assert list(table.rows) == [ assert list(table.rows) == [
@ -26,7 +26,7 @@ def test_upsert_all(fresh_db):
def test_upsert_all_single_column(fresh_db): def test_upsert_all_single_column(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.upsert_all([{"name": "Cleo"}], pk="name") table.upsert_all([{"name": "Cleo"}], pk="name")
assert list(table.rows) == [{"name": "Cleo"}] assert list(table.rows) == [{"name": "Cleo"}]
assert table.pks == ["name"] assert table.pks == ["name"]
@ -34,16 +34,16 @@ def test_upsert_all_single_column(fresh_db):
def test_upsert_all_not_null(fresh_db): def test_upsert_all_not_null(fresh_db):
# https://github.com/simonw/sqlite-utils/issues/538 # https://github.com/simonw/sqlite-utils/issues/538
fresh_db["comments"].upsert_all( fresh_db.table("comments").upsert_all(
[{"id": 1, "name": "Cleo"}], [{"id": 1, "name": "Cleo"}],
pk="id", pk="id",
not_null=["name"], not_null=["name"],
) )
assert list(fresh_db["comments"].rows) == [{"id": 1, "name": "Cleo"}] assert list(fresh_db.table("comments").rows) == [{"id": 1, "name": "Cleo"}]
def test_upsert_error_if_no_pk(fresh_db): def test_upsert_error_if_no_pk(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
with pytest.raises(PrimaryKeyRequired): with pytest.raises(PrimaryKeyRequired):
table.upsert_all([{"id": 1, "name": "Cleo"}]) table.upsert_all([{"id": 1, "name": "Cleo"}])
with pytest.raises(PrimaryKeyRequired): with pytest.raises(PrimaryKeyRequired):
@ -53,7 +53,7 @@ def test_upsert_error_if_no_pk(fresh_db):
@pytest.mark.parametrize("use_old_upsert", (False, True)) @pytest.mark.parametrize("use_old_upsert", (False, True))
def test_upsert_empty_record_errors(use_old_upsert): def test_upsert_empty_record_errors(use_old_upsert):
db = Database(memory=True, use_old_upsert=use_old_upsert) db = Database(memory=True, use_old_upsert=use_old_upsert)
table = db["table"] table = db.table("table")
table.insert({"id": 1, "name": "Cleo"}, pk="id") table.insert({"id": 1, "name": "Cleo"}, pk="id")
with pytest.raises(PrimaryKeyRequired): with pytest.raises(PrimaryKeyRequired):
table.upsert({}, pk="id") table.upsert({}, pk="id")
@ -66,7 +66,7 @@ def test_upsert_empty_record_errors(use_old_upsert):
@pytest.mark.parametrize("use_old_upsert", (False, True)) @pytest.mark.parametrize("use_old_upsert", (False, True))
def test_upsert_missing_pk_value_errors(use_old_upsert): def test_upsert_missing_pk_value_errors(use_old_upsert):
db = Database(memory=True, use_old_upsert=use_old_upsert) db = Database(memory=True, use_old_upsert=use_old_upsert)
table = db["table"] table = db.table("table")
table.insert({"id": 1, "name": "Cleo"}, pk="id") table.insert({"id": 1, "name": "Cleo"}, pk="id")
# Records that omit the pk column entirely # Records that omit the pk column entirely
with pytest.raises(PrimaryKeyRequired): with pytest.raises(PrimaryKeyRequired):
@ -78,7 +78,7 @@ def test_upsert_missing_pk_value_errors(use_old_upsert):
def test_upsert_missing_compound_pk_value_errors(fresh_db): def test_upsert_missing_compound_pk_value_errors(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.insert({"a": "x", "b": "y", "v": 1}, pk=("a", "b")) table.insert({"a": "x", "b": "y", "v": 1}, pk=("a", "b"))
# Missing one component of the detected compound primary key # Missing one component of the detected compound primary key
with pytest.raises(PrimaryKeyRequired): with pytest.raises(PrimaryKeyRequired):
@ -105,7 +105,7 @@ def test_upsert_uses_compound_pk_from_existing_table(use_old_upsert):
primary key (Source, Object, Category) primary key (Source, Object, Category)
) )
""") """)
table = db["summary"] table = db.table("summary")
table.upsert( table.upsert(
{ {
"Source": "Client A", "Source": "Client A",
@ -134,7 +134,7 @@ def test_upsert_uses_compound_pk_from_existing_table(use_old_upsert):
def test_upsert_with_hash_id(fresh_db): def test_upsert_with_hash_id(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.upsert({"foo": "bar"}, hash_id="pk") table.upsert({"foo": "bar"}, hash_id="pk")
assert [{"pk": "a5e744d0164540d33b1d7ea616c28f2fa97e754a", "foo": "bar"}] == list( assert [{"pk": "a5e744d0164540d33b1d7ea616c28f2fa97e754a", "foo": "bar"}] == list(
table.rows table.rows
@ -144,7 +144,7 @@ def test_upsert_with_hash_id(fresh_db):
@pytest.mark.parametrize("hash_id", (None, "custom_id")) @pytest.mark.parametrize("hash_id", (None, "custom_id"))
def test_upsert_with_hash_id_columns(fresh_db, hash_id): def test_upsert_with_hash_id_columns(fresh_db, hash_id):
table = fresh_db["table"] table = fresh_db.table("table")
table.upsert({"a": 1, "b": 2, "c": 3}, hash_id=hash_id, hash_id_columns=("a", "b")) table.upsert({"a": 1, "b": 2, "c": 3}, hash_id=hash_id, hash_id_columns=("a", "b"))
assert list(table.rows) == [ assert list(table.rows) == [
{ {
@ -167,7 +167,7 @@ def test_upsert_with_hash_id_columns(fresh_db, hash_id):
def test_upsert_compound_primary_key(fresh_db): def test_upsert_compound_primary_key(fresh_db):
table = fresh_db["table"] table = fresh_db.table("table")
table.upsert_all( table.upsert_all(
[ [
{"species": "dog", "id": 1, "name": "Cleo", "age": 4}, {"species": "dog", "id": 1, "name": "Cleo", "age": 4},

View file

@ -18,7 +18,7 @@ def test_enable_disable_wal(db_path_tmpdir):
assert "test.db-wal" not in [f.basename for f in tmpdir.listdir()] assert "test.db-wal" not in [f.basename for f in tmpdir.listdir()]
db.enable_wal() db.enable_wal()
assert "wal" == db.journal_mode assert "wal" == db.journal_mode
db["test"].insert({"foo": "bar"}) db.table("test").insert({"foo": "bar"})
assert "test.db-wal" in [f.basename for f in tmpdir.listdir()] assert "test.db-wal" in [f.basename for f in tmpdir.listdir()]
db.disable_wal() db.disable_wal()
assert "delete" == db.journal_mode assert "delete" == db.journal_mode
@ -27,25 +27,25 @@ def test_enable_disable_wal(db_path_tmpdir):
def test_enable_wal_inside_transaction_raises(db_path_tmpdir): def test_enable_wal_inside_transaction_raises(db_path_tmpdir):
db, _path, _tmpdir = db_path_tmpdir db, _path, _tmpdir = db_path_tmpdir
db["test"].insert({"id": 1}, pk="id") db.table("test").insert({"id": 1}, pk="id")
with pytest.raises(TransactionError), db.atomic(): with pytest.raises(TransactionError), db.atomic():
db["test"].insert({"id": 2}, pk="id") db.table("test").insert({"id": 2}, pk="id")
db.enable_wal() db.enable_wal()
# The atomic() block must have rolled back cleanly and the # The atomic() block must have rolled back cleanly and the
# journal mode must be unchanged # journal mode must be unchanged
assert db.journal_mode == "delete" assert db.journal_mode == "delete"
assert [r["id"] for r in db["test"].rows] == [1] assert [r["id"] for r in db.table("test").rows] == [1]
def test_disable_wal_inside_transaction_raises(db_path_tmpdir): def test_disable_wal_inside_transaction_raises(db_path_tmpdir):
db, _path, _tmpdir = db_path_tmpdir db, _path, _tmpdir = db_path_tmpdir
db.enable_wal() db.enable_wal()
db["test"].insert({"id": 1}, pk="id") db.table("test").insert({"id": 1}, pk="id")
with pytest.raises(TransactionError), db.atomic(): with pytest.raises(TransactionError), db.atomic():
db["test"].insert({"id": 2}, pk="id") db.table("test").insert({"id": 2}, pk="id")
db.disable_wal() db.disable_wal()
assert db.journal_mode == "wal" assert db.journal_mode == "wal"
assert [r["id"] for r in db["test"].rows] == [1] assert [r["id"] for r in db.table("test").rows] == [1]
def test_ensure_autocommit_on(db_path_tmpdir): def test_ensure_autocommit_on(db_path_tmpdir):
@ -65,9 +65,9 @@ def test_enable_wal_noop_inside_transaction_is_allowed(db_path_tmpdir):
db, _path, _tmpdir = db_path_tmpdir db, _path, _tmpdir = db_path_tmpdir
db.enable_wal() db.enable_wal()
with db.atomic(): with db.atomic():
db["test"].insert({"id": 1}, pk="id") db.table("test").insert({"id": 1}, pk="id")
db.enable_wal() db.enable_wal()
assert [r["id"] for r in db["test"].rows] == [1] assert [r["id"] for r in db.table("test").rows] == [1]
def test_ensure_autocommit_on_inside_transaction_raises(db_path_tmpdir): def test_ensure_autocommit_on_inside_transaction_raises(db_path_tmpdir):
@ -75,7 +75,7 @@ def test_ensure_autocommit_on_inside_transaction_raises(db_path_tmpdir):
# effect, silently breaking the caller's rollback guarantee - so # effect, silently breaking the caller's rollback guarantee - so
# entering autocommit mode with a transaction open is an error # entering autocommit mode with a transaction open is an error
db, _path, _tmpdir = db_path_tmpdir db, _path, _tmpdir = db_path_tmpdir
db["test"].insert({"id": 1}, pk="id") db.table("test").insert({"id": 1}, pk="id")
db.begin() db.begin()
db.execute("insert into test (id) values (2)") db.execute("insert into test (id) values (2)")
with pytest.raises(TransactionError), db.ensure_autocommit_on(): with pytest.raises(TransactionError), db.ensure_autocommit_on():
@ -83,4 +83,4 @@ def test_ensure_autocommit_on_inside_transaction_raises(db_path_tmpdir):
# The transaction is still open and can still be rolled back # The transaction is still open and can still be rolled back
assert db.conn.in_transaction assert db.conn.in_transaction
db.rollback() db.rollback()
assert [r["id"] for r in db["test"].rows] == [1] assert [r["id"] for r in db.table("test").rows] == [1]