mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-07-24 18:04:32 +02:00
.extract() and .lookup() no longer extract null values, closes #186
- table.extract() and the sqlite-utils extract command now skip rows where every extracted column is null: the new foreign key column is left null and no all-null record is added to the lookup table. Rows with at least one non-null extracted column are extracted as before. - The extracts= option to insert() and friends keeps None values as null instead of creating a lookup record for them - previously each insert batch added a duplicate null row to the lookup table. - table.lookup() compares values using IS rather than = so lookup values containing None match existing rows correctly, instead of inserting a duplicate row on every call. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b8aa136857
commit
2616dec795
8 changed files with 176 additions and 7 deletions
|
|
@ -189,9 +189,85 @@ def test_extract_works_with_null_values(fresh_db):
|
|||
)
|
||||
assert list(fresh_db["listens"].rows) == [
|
||||
{"id": 1, "track_title": "foo", "album_id": 1},
|
||||
{"id": 2, "track_title": "baz", "album_id": 2},
|
||||
{"id": 2, "track_title": "baz", "album_id": None},
|
||||
]
|
||||
assert list(fresh_db["albums"].rows) == [
|
||||
{"id": 1, "album_title": "bar"},
|
||||
{"id": 2, "album_title": None},
|
||||
]
|
||||
|
||||
|
||||
def test_extract_null_values_single_column(fresh_db):
|
||||
# https://github.com/simonw/sqlite-utils/issues/186
|
||||
fresh_db["species"].insert({"id": 1, "species": "Wolf"}, pk="id")
|
||||
fresh_db["individuals"].insert_all(
|
||||
[
|
||||
{"id": 10, "name": "Terriana", "species": "Fox"},
|
||||
{"id": 11, "name": "Spenidorm", "species": None},
|
||||
{"id": 12, "name": "Grantheim", "species": "Wolf"},
|
||||
{"id": 13, "name": "Turnutopia", "species": None},
|
||||
{"id": 14, "name": "Wargal", "species": "Wolf"},
|
||||
],
|
||||
pk="id",
|
||||
)
|
||||
fresh_db["individuals"].extract("species")
|
||||
# No null row should have been added to species
|
||||
assert list(fresh_db["species"].rows) == [
|
||||
{"id": 1, "species": "Wolf"},
|
||||
{"id": 2, "species": "Fox"},
|
||||
]
|
||||
assert list(fresh_db["individuals"].rows) == [
|
||||
{"id": 10, "name": "Terriana", "species_id": 2},
|
||||
{"id": 11, "name": "Spenidorm", "species_id": None},
|
||||
{"id": 12, "name": "Grantheim", "species_id": 1},
|
||||
{"id": 13, "name": "Turnutopia", "species_id": None},
|
||||
{"id": 14, "name": "Wargal", "species_id": 1},
|
||||
]
|
||||
|
||||
|
||||
def test_extract_null_values_multiple_columns(fresh_db):
|
||||
# A row should be extracted if at least one column is not null -
|
||||
# only rows where ALL extracted columns are null are left alone
|
||||
fresh_db["circulation"].insert_all(
|
||||
[
|
||||
{"id": 1, "title": "title one", "creator": "creator one", "year": 2018},
|
||||
{"id": 2, "title": "title two", "creator": None, "year": 2019},
|
||||
{"id": 3, "title": None, "creator": None, "year": 2020},
|
||||
{"id": 4, "title": None, "creator": None, "year": 2021},
|
||||
],
|
||||
pk="id",
|
||||
)
|
||||
fresh_db["circulation"].extract(
|
||||
["title", "creator"], table="books", fk_column="book_id"
|
||||
)
|
||||
assert list(fresh_db["books"].rows) == [
|
||||
{"id": 1, "title": "title one", "creator": "creator one"},
|
||||
{"id": 2, "title": "title two", "creator": None},
|
||||
]
|
||||
assert list(fresh_db["circulation"].rows) == [
|
||||
{"id": 1, "book_id": 1, "year": 2018},
|
||||
{"id": 2, "book_id": 2, "year": 2019},
|
||||
{"id": 3, "book_id": None, "year": 2020},
|
||||
{"id": 4, "book_id": None, "year": 2021},
|
||||
]
|
||||
|
||||
|
||||
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
|
||||
# every extracted column is null should keep a null foreign key
|
||||
fresh_db["species"].insert({"id": 1, "species": None}, pk="id")
|
||||
fresh_db["individuals"].insert_all(
|
||||
[
|
||||
{"id": 10, "name": "Terriana", "species": "Fox"},
|
||||
{"id": 11, "name": "Spenidorm", "species": None},
|
||||
],
|
||||
pk="id",
|
||||
)
|
||||
fresh_db["individuals"].extract("species")
|
||||
assert list(fresh_db["species"].rows) == [
|
||||
{"id": 1, "species": None},
|
||||
{"id": 2, "species": "Fox"},
|
||||
]
|
||||
assert list(fresh_db["individuals"].rows) == [
|
||||
{"id": 10, "name": "Terriana", "species_id": 2},
|
||||
{"id": 11, "name": "Spenidorm", "species_id": None},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -67,3 +67,51 @@ def test_extracts(fresh_db, kwargs, expected_table, use_table_factory):
|
|||
{"id": 2, "species_id": 1},
|
||||
{"id": 3, "species_id": 2},
|
||||
] == list(fresh_db["Trees"].rows)
|
||||
|
||||
|
||||
def test_extracts_null_values(fresh_db):
|
||||
# https://github.com/simonw/sqlite-utils/issues/186
|
||||
# Null values should stay null, not be extracted into the lookup table
|
||||
fresh_db["Trees"].insert_all(
|
||||
[
|
||||
{"id": 1, "species_id": "Oak"},
|
||||
{"id": 2, "species_id": None},
|
||||
{"id": 3, "species_id": "Palm"},
|
||||
{"id": 4, "species_id": None},
|
||||
],
|
||||
extracts={"species_id": "Species"},
|
||||
)
|
||||
assert list(fresh_db["Species"].rows) == [
|
||||
{"id": 1, "value": "Oak"},
|
||||
{"id": 2, "value": "Palm"},
|
||||
]
|
||||
assert list(fresh_db["Trees"].rows) == [
|
||||
{"id": 1, "species_id": 1},
|
||||
{"id": 2, "species_id": None},
|
||||
{"id": 3, "species_id": 2},
|
||||
{"id": 4, "species_id": None},
|
||||
]
|
||||
|
||||
|
||||
def test_extracts_null_values_list_mode(fresh_db):
|
||||
# Same as test_extracts_null_values but for list-based records
|
||||
fresh_db["Trees"].insert_all(
|
||||
[
|
||||
["id", "species_id"],
|
||||
[1, "Oak"],
|
||||
[2, None],
|
||||
[3, "Palm"],
|
||||
[4, None],
|
||||
],
|
||||
extracts={"species_id": "Species"},
|
||||
)
|
||||
assert list(fresh_db["Species"].rows) == [
|
||||
{"id": 1, "value": "Oak"},
|
||||
{"id": 2, "value": "Palm"},
|
||||
]
|
||||
assert list(fresh_db["Trees"].rows) == [
|
||||
{"id": 1, "species_id": 1},
|
||||
{"id": 2, "species_id": None},
|
||||
{"id": 3, "species_id": 2},
|
||||
{"id": 4, "species_id": None},
|
||||
]
|
||||
|
|
|
|||
|
|
@ -157,3 +157,27 @@ def test_lookup_with_extra_insert_parameters(fresh_db):
|
|||
def test_lookup_new_table_strict(fresh_db, strict):
|
||||
fresh_db["species"].lookup({"name": "Palm"}, strict=strict)
|
||||
assert fresh_db["species"].strict == strict or not fresh_db.supports_strict
|
||||
|
||||
|
||||
def test_lookup_null_value_idempotent(fresh_db):
|
||||
# https://github.com/simonw/sqlite-utils/issues/186
|
||||
# Repeated lookups of a null value should return the same row,
|
||||
# not insert a duplicate row each time
|
||||
species = fresh_db["species"]
|
||||
first_id = species.lookup({"name": None})
|
||||
second_id = species.lookup({"name": None})
|
||||
assert first_id == second_id
|
||||
assert list(species.rows) == [{"id": first_id, "name": None}]
|
||||
|
||||
|
||||
def test_lookup_compound_key_with_null_idempotent(fresh_db):
|
||||
species = fresh_db["species"]
|
||||
palm_id = species.lookup({"name": "Palm", "type": None})
|
||||
oak_id = species.lookup({"name": "Oak", "type": "Tree"})
|
||||
assert palm_id == species.lookup({"name": "Palm", "type": None})
|
||||
assert oak_id == species.lookup({"name": "Oak", "type": "Tree"})
|
||||
assert palm_id != oak_id
|
||||
assert list(species.rows) == [
|
||||
{"id": palm_id, "name": "Palm", "type": None},
|
||||
{"id": oak_id, "name": "Oak", "type": "Tree"},
|
||||
]
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue