diff --git a/sqlite_minutils/db.py b/sqlite_minutils/db.py index e262d07..1657880 100644 --- a/sqlite_minutils/db.py +++ b/sqlite_minutils/db.py @@ -276,7 +276,7 @@ def close(self): def get_last_rowid(self): res = next(self.execute('SELECT last_insert_rowid()'), None) if res is None: return None - return int(res[0]) + return int(res[0]) @contextlib.contextmanager def ensure_autocommit_off(self): @@ -845,14 +845,23 @@ def sort_key(p): ) column_defs = [] + # All minidata tables get a primary key: https://docs.fastht.ml/explains/minidataapi.html#creating-tables + column_names = [x[0] for x in column_items] + if pk is None and 'id' not in column_names: + column_items.insert(0, ('id', int)) + column_names.insert(0, 'id') + if pk is None: + pk = ['id'] + # ensure pk is a tuple single_pk = None if isinstance(pk, list) and len(pk) == 1 and isinstance(pk[0], str): pk = pk[0] if isinstance(pk, str): single_pk = pk - if pk not in [c[0] for c in column_items]: + if pk not in column_names: column_items.insert(0, (pk, int)) + for column_name, column_type in column_items: column_extras = [] if column_name == single_pk: @@ -945,6 +954,15 @@ def create_table( if transform and self[name].exists(): table = cast(Table, self[name]) should_transform = False + # Has the primary key changed? + current_pks = table.pks + desired_pk = None + if isinstance(pk, str): + desired_pk = [pk] + elif pk: + desired_pk = list(pk) + if desired_pk and current_pks != desired_pk: + should_transform = True # First add missing columns and figure out columns to drop existing_columns = table.columns_dict missing_columns = dict( @@ -955,6 +973,14 @@ def create_table( columns_to_drop = [ column for column in existing_columns if column not in columns ] + # If no primary key was specified and id was added automatically, we prevent + # it from being deleted here or Sqlite will complain that a primary key is + # being dropped. We delete the ID column once a new PK has been set. + delete_id_column = False + if table.pks == ['id'] and 'id' in columns_to_drop: + columns_to_drop.remove('id') + delete_id_column = True + if columns_to_drop: for col_name in columns_to_drop: table.drop_column(col_name) if missing_columns: @@ -968,15 +994,6 @@ def create_table( and list(existing_columns)[: len(column_order)] != column_order ): should_transform = True - # Has the primary key changed? - current_pks = table.pks - desired_pk = None - if isinstance(pk, str): - desired_pk = [pk] - elif pk: - desired_pk = list(pk) - if desired_pk and current_pks != desired_pk: - should_transform = True # Any not-null changes? current_not_null = {c.name for c in table.columns if c.notnull} desired_not_null = set(not_null) if not_null else set() @@ -994,6 +1011,9 @@ def create_table( defaults=defaults, pk=pk, ) + # There has been set a primary key that isn't ['id'].It is now safe to drop the ID column. + if delete_id_column and table.pks != ['id']: + table.drop_column('id') return table sql = self.create_table_sql( name=name, @@ -1463,7 +1483,7 @@ def pks(self) -> List[str]: "Primary key columns for this table." names = [column.name for column in self.columns if int(column.is_pk)] if not names: - names = ["rowid"] + names = ["id"] return names @property @@ -2183,7 +2203,7 @@ def add_column( fk_col = pks[0].name fk_col_type = pks[0].type else: - fk_col = "rowid" + fk_col = "id" fk_col_type = "INTEGER" if col_type is None: col_type = str @@ -2925,6 +2945,11 @@ def insert_chunk( ignore, ) + if isinstance(pk, Union[str, None]): + pks = [pk] + else: + pks = pk + records = [] for query, params in queries_and_params: try: @@ -2989,17 +3014,32 @@ def insert_chunk( # around for multiple queries/records are returned for what should # be single SQL call operations. self.last_pk = records[0][hash_id] - elif (rid := self.db.get_last_rowid()) is not None: - self.last_pk = self.last_rowid = rid - # self.last_rowid will be 0 if a "INSERT OR IGNORE" happened + elif len(records) > 0 and any(pks): + # Pks provided as an argument + last_record = records[-1] + self.last_pk = tuple(last_record[pk] for pk in pks) + if len(self.last_pk) == 1: + self.last_pk = self.last_pk[0] + if (hash_id or pk) and not upsert: - row = list(self.rows_where("rowid = ?", [rid]))[0] if hash_id: - self.last_pk = row[hash_id] + self.last_pk = last_record[hash_id] elif isinstance(pk, str): - self.last_pk = row[pk] + self.last_pk = last_record[pk] else: - self.last_pk = tuple(row[p] for p in pk) + self.last_pk = tuple(last_record[p] for p in pk) + elif len(records): + # No pks provided, so we use the table's defaults + last_record = records[-1] + self.last_pk = tuple(last_record[pk] for pk in self.pks) + if len(self.last_pk) == 1: + self.last_pk = self.last_pk[0] + + # Setting last_rowid to preserve API so we don't break backwards + # compatibility for users. + # TODO: Consider turning this into a property that warns upon usage + self.last_rowid = self.last_pk + return records def insert( diff --git a/tests/test_constructor.py b/tests/test_constructor.py index 5297500..f7348bf 100644 --- a/tests/test_constructor.py +++ b/tests/test_constructor.py @@ -17,7 +17,7 @@ def test_memory_name(): db1 = Database(memory_name="shared") db2 = Database(memory_name="shared") db1["dogs"].insert({"name": "Cleo"}) - assert list(db2["dogs"].rows) == [{"name": "Cleo"}] + assert list(db2["dogs"].rows) == [{'id': 1, "name": "Cleo"}] def test_sqlite_version(): diff --git a/tests/test_conversions.py b/tests/test_conversions.py index d70f5c8..6b844fb 100644 --- a/tests/test_conversions.py +++ b/tests/test_conversions.py @@ -1,13 +1,13 @@ def test_insert_conversion(fresh_db): table = fresh_db["table"] table.insert({"foo": "bar"}, conversions={"foo": "upper(?)"}) - assert [{"foo": "BAR"}] == list(table.rows) + assert [{'id':1, "foo": "BAR"}] == list(table.rows) def test_insert_all_conversion(fresh_db): table = fresh_db["table"] table.insert_all([{"foo": "bar"}], conversions={"foo": "upper(?)"}) - assert [{"foo": "BAR"}] == list(table.rows) + assert [{'id':1, "foo": "BAR"}] == list(table.rows) def test_upsert_conversion(fresh_db): @@ -38,4 +38,4 @@ def test_update_conversion(fresh_db): def test_table_constructor_conversion(fresh_db): table = fresh_db.table("table", conversions={"bar": "upper(?)"}) table.insert({"bar": "baz"}) - assert [{"bar": "BAZ"}] == list(table.rows) + assert [{'id':1,"bar": "BAZ"}] == list(table.rows) diff --git a/tests/test_create.py b/tests/test_create.py index 11b7d46..b24cc72 100644 --- a/tests/test_create.py +++ b/tests/test_create.py @@ -40,6 +40,7 @@ def test_create_table(fresh_db): ) assert ["test_table"] == fresh_db.table_names() assert [ + {"name": "id", "type": "INTEGER"}, {"name": "text_col", "type": "TEXT"}, {"name": "float_col", "type": "FLOAT"}, {"name": "int_col", "type": "INTEGER"}, @@ -49,6 +50,7 @@ def test_create_table(fresh_db): ] == [{"name": col.name, "type": col.type} for col in table.columns] assert ( "CREATE TABLE [test_table] (\n" + " [id] INTEGER PRIMARY KEY,\n" " [text_col] TEXT,\n" " [float_col] FLOAT,\n" " [int_col] INTEGER,\n" @@ -94,11 +96,11 @@ def test_create_table_with_defaults(fresh_db): defaults={"score": 1, "name": "bob''bob"}, ) assert ["players"] == fresh_db.table_names() - assert [{"name": "name", "type": "TEXT"}, {"name": "score", "type": "INTEGER"}] == [ + assert [{"name": "id", "type": "INTEGER"}, {"name": "name", "type": "TEXT"}, {"name": "score", "type": "INTEGER"}] == [ {"name": col.name, "type": col.type} for col in table.columns ] assert ( - "CREATE TABLE [players] (\n [name] TEXT DEFAULT 'bob''''bob',\n [score] INTEGER DEFAULT 1\n)" + "CREATE TABLE [players] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT DEFAULT 'bob''''bob',\n [score] INTEGER DEFAULT 1\n)" ) == table.schema @@ -117,11 +119,11 @@ def test_create_table_with_not_null(fresh_db): defaults={"score": 3}, ) assert ["players"] == fresh_db.table_names() - assert [{"name": "name", "type": "TEXT"}, {"name": "score", "type": "INTEGER"}] == [ + assert [{"name": "id", "type": "INTEGER"}, {"name": "name", "type": "TEXT"}, {"name": "score", "type": "INTEGER"}] == [ {"name": col.name, "type": col.type} for col in table.columns ] assert ( - "CREATE TABLE [players] (\n [name] TEXT NOT NULL,\n [score] INTEGER NOT NULL DEFAULT 3\n)" + "CREATE TABLE [players] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT NOT NULL,\n [score] INTEGER NOT NULL DEFAULT 3\n)" ) == table.schema @@ -130,23 +132,23 @@ def test_create_table_with_not_null(fresh_db): ( ( {"name": "Ravi", "age": 63}, - [{"name": "name", "type": "TEXT"}, {"name": "age", "type": "INTEGER"}], + [{'name': 'id', 'type':'INTEGER'}, {"name": "name", "type": "TEXT"}, {"name": "age", "type": "INTEGER"}], ), ( {"create": "Reserved word", "table": "Another"}, - [{"name": "create", "type": "TEXT"}, {"name": "table", "type": "TEXT"}], + [{'name': 'id', 'type':'INTEGER'}, {"name": "create", "type": "TEXT"}, {"name": "table", "type": "TEXT"}], ), - ({"day": datetime.time(11, 0)}, [{"name": "day", "type": "TEXT"}]), - ({"decimal": decimal.Decimal("1.2")}, [{"name": "decimal", "type": "FLOAT"}]), + ({"day": datetime.time(11, 0)}, [{'name': 'id', 'type':'INTEGER'}, {"name": "day", "type": "TEXT"}]), + ({"decimal": decimal.Decimal("1.2")}, [{'name': 'id', 'type':'INTEGER'}, {"name": "decimal", "type": "FLOAT"}]), ( {"memoryview": memoryview(b"hello")}, - [{"name": "memoryview", "type": "BLOB"}], + [{'name': 'id', 'type':'INTEGER'}, {"name": "memoryview", "type": "BLOB"}], ), - ({"uuid": uuid.uuid4()}, [{"name": "uuid", "type": "TEXT"}]), - ({"foo[bar]": 1}, [{"name": "foo_bar_", "type": "INTEGER"}]), + ({"uuid": uuid.uuid4()}, [{'name': 'id', 'type':'INTEGER'}, {"name": "uuid", "type": "TEXT"}]), + ({"foo[bar]": 1}, [{'name': 'id', 'type':'INTEGER'}, {"name": "foo_bar_", "type": "INTEGER"}]), ( {"timedelta": datetime.timedelta(hours=1)}, - [{"name": "timedelta", "type": "TEXT"}], + [{'name': 'id', 'type':'INTEGER'}, {"name": "timedelta", "type": "TEXT"}], ), ), ) @@ -210,6 +212,7 @@ def test_create_table_column_order(fresh_db, use_table_factory): else: fresh_db["table"].insert(row, column_order=column_order) assert [ + {'name': 'id', 'type': 'INTEGER'}, {"name": "abc", "type": "TEXT"}, {"name": "ccc", "type": "TEXT"}, {"name": "zzz", "type": "TEXT"}, @@ -267,6 +270,7 @@ def do_it(): else: do_it() assert [ + {'name': 'id', 'type': 'INTEGER'}, {"name": "one_id", "type": "INTEGER"}, {"name": "two_id", "type": "INTEGER"}, ] == [{"name": col.name, "type": col.type} for col in fresh_db["m2m"].columns] @@ -334,58 +338,58 @@ def test_create_error_if_invalid_self_referential_foreign_keys(fresh_db): "nickname", str, None, - "CREATE TABLE [dogs] (\n [name] TEXT\n, [nickname] TEXT)", + 'CREATE TABLE [dogs] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n, [nickname] TEXT)', ), ( "dob", datetime.date, None, - "CREATE TABLE [dogs] (\n [name] TEXT\n, [dob] TEXT)", + "CREATE TABLE [dogs] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n, [dob] TEXT)", ), - ("age", int, None, "CREATE TABLE [dogs] (\n [name] TEXT\n, [age] INTEGER)"), + ("age", int, None, "CREATE TABLE [dogs] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n, [age] INTEGER)"), ( "weight", float, None, - "CREATE TABLE [dogs] (\n [name] TEXT\n, [weight] FLOAT)", + "CREATE TABLE [dogs] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n, [weight] FLOAT)", ), - ("text", "TEXT", None, "CREATE TABLE [dogs] (\n [name] TEXT\n, [text] TEXT)"), + ("text", "TEXT", None, "CREATE TABLE [dogs] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n, [text] TEXT)"), ( "integer", "INTEGER", None, - "CREATE TABLE [dogs] (\n [name] TEXT\n, [integer] INTEGER)", + "CREATE TABLE [dogs] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n, [integer] INTEGER)", ), ( "float", "FLOAT", None, - "CREATE TABLE [dogs] (\n [name] TEXT\n, [float] FLOAT)", + "CREATE TABLE [dogs] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n, [float] FLOAT)", ), - ("blob", "blob", None, "CREATE TABLE [dogs] (\n [name] TEXT\n, [blob] BLOB)"), + ("blob", "blob", None, "CREATE TABLE [dogs] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n, [blob] BLOB)"), ( "default_str", None, None, - "CREATE TABLE [dogs] (\n [name] TEXT\n, [default_str] TEXT)", + "CREATE TABLE [dogs] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n, [default_str] TEXT)", ), ( "nickname", str, "", - "CREATE TABLE [dogs] (\n [name] TEXT\n, [nickname] TEXT NOT NULL DEFAULT '')", + "CREATE TABLE [dogs] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n, [nickname] TEXT NOT NULL DEFAULT '')", ), ( "nickname", str, "dawg's dawg", - "CREATE TABLE [dogs] (\n [name] TEXT\n, [nickname] TEXT NOT NULL DEFAULT 'dawg''s dawg')", + "CREATE TABLE [dogs] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n, [nickname] TEXT NOT NULL DEFAULT 'dawg''s dawg')", ), ), ) def test_add_column(fresh_db, col_name, col_type, not_null_default, expected_schema): fresh_db.create_table("dogs", {"name": str}) - assert fresh_db["dogs"].schema == "CREATE TABLE [dogs] (\n [name] TEXT\n)" + assert fresh_db["dogs"].schema == 'CREATE TABLE [dogs] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n)' fresh_db["dogs"].add_column(col_name, col_type, not_null_default=not_null_default) assert fresh_db["dogs"].schema == expected_schema @@ -490,8 +494,9 @@ def test_add_column_foreign_key(fresh_db): fresh_db["dogs"].add_column("breed_id", fk="breeds") assert fresh_db["dogs"].schema == ( 'CREATE TABLE "dogs" (\n' + " [id] INTEGER PRIMARY KEY,\n" " [name] TEXT,\n" - " [breed_id] INTEGER REFERENCES [breeds]([rowid])\n" + " [breed_id] INTEGER REFERENCES [breeds]([id])\n" ")" ) # And again with an explicit primary key column @@ -499,8 +504,9 @@ def test_add_column_foreign_key(fresh_db): fresh_db["dogs"].add_column("subbreed_id", fk="subbreeds") assert fresh_db["dogs"].schema == ( 'CREATE TABLE "dogs" (\n' + " [id] INTEGER PRIMARY KEY,\n" " [name] TEXT,\n" - " [breed_id] INTEGER REFERENCES [breeds]([rowid]),\n" + " [breed_id] INTEGER REFERENCES [breeds]([id]),\n" " [subbreed_id] TEXT REFERENCES [subbreeds]([primkey])\n" ")" ) @@ -513,6 +519,7 @@ def test_add_foreign_key_guess_table(fresh_db): fresh_db["dogs"].add_foreign_key("breed_id") assert fresh_db["dogs"].schema == ( 'CREATE TABLE "dogs" (\n' + " [id] INTEGER PRIMARY KEY,\n" " [name] TEXT,\n" " [breed_id] INTEGER REFERENCES [breeds]([id])\n" ")" @@ -562,6 +569,7 @@ def test_insert_row_alter_table( table = fresh_db["books"] table.insert({"title": "Hedgehogs of the world", "author_id": 1}) assert [ + {"name": "id", "type": "INTEGER"}, {"name": "title", "type": "TEXT"}, {"name": "author_id", "type": "INTEGER"}, ] == [{"name": col.name, "type": col.type} for col in table.columns] @@ -572,6 +580,7 @@ def test_insert_row_alter_table( else: fresh_db["books"].insert(record, alter=True) assert [ + {"name": "id", "type": "INTEGER"}, {"name": "title", "type": "TEXT"}, {"name": "author_id", "type": "INTEGER"}, ] + expected_new_columns == [ @@ -654,10 +663,10 @@ def test_insert_all_with_extra_columns_in_later_chunks(fresh_db): ] fresh_db["t"].insert_all(chunk, batch_size=2, alter=True) assert list(fresh_db["t"].rows) == [ - {"record": "Record 1", "extra": None}, - {"record": "Record 2", "extra": None}, - {"record": "Record 3", "extra": None}, - {"record": "Record 4", "extra": 1}, + {'id': 1, "record": "Record 1", "extra": None}, + {'id': 2, "record": "Record 2", "extra": None}, + {'id': 3, "record": "Record 3", "extra": None}, + {'id': 4, "record": "Record 4", "extra": 1}, ] @@ -873,7 +882,7 @@ def test_insert_uuid(fresh_db): uuid4 = uuid.uuid4() fresh_db["test"].insert({"uuid": uuid4}) row = list(fresh_db["test"].rows)[0] - assert {"uuid"} == row.keys() + assert {'id', "uuid"} == row.keys() assert isinstance(row["uuid"], str) assert row["uuid"] == str(uuid4) @@ -881,7 +890,7 @@ def test_insert_uuid(fresh_db): def test_insert_memoryview(fresh_db): fresh_db["test"].insert({"data": memoryview(b"hello")}) row = list(fresh_db["test"].rows)[0] - assert {"data"} == row.keys() + assert {'id', "data"} == row.keys() assert isinstance(row["data"], bytes) assert row["data"] == b"hello" @@ -890,7 +899,7 @@ def test_insert_thousands_using_generator(fresh_db): fresh_db["test"].insert_all( {"i": i, "word": "word_{}".format(i)} for i in range(10000) ) - assert [{"name": "i", "type": "INTEGER"}, {"name": "word", "type": "TEXT"}] == [ + assert [{'name': 'id', 'type': 'INTEGER'}, {"name": "i", "type": "INTEGER"}, {"name": "word", "type": "TEXT"}] == [ {"name": col.name, "type": col.type} for col in fresh_db["test"].columns ] assert fresh_db["test"].count == 10000 @@ -913,7 +922,7 @@ def test_insert_thousands_adds_extra_columns_after_first_100_with_alter(fresh_db alter=True, ) rows = list(fresh_db.query("select * from test where i = 101")) - assert rows == [{"i": 101, "word": None, "extra": "Should trigger ALTER"}] + assert rows == [{'id': 101, "i": 101, "word": None, "extra": "Should trigger ALTER"}] def test_insert_ignore(fresh_db): @@ -984,9 +993,9 @@ def test_create_table_numpy(fresh_db): df = pd.DataFrame({"col 1": range(3), "col 2": range(3)}) fresh_db["pandas"].insert_all(df.to_dict(orient="records")) assert [ - {"col 1": 0, "col 2": 0}, - {"col 1": 1, "col 2": 1}, - {"col 1": 2, "col 2": 2}, + {'id': 1, "col 1": 0, "col 2": 0}, + {'id': 2, "col 1": 1, "col 2": 1}, + {'id': 3, "col 1": 2, "col 2": 2}, ] == list(fresh_db["pandas"].rows) # Now try all the different types df = pd.DataFrame( @@ -1035,6 +1044,7 @@ def test_create_table_numpy(fresh_db): fresh_db["types"].insert_all(df.to_dict(orient="records")) assert [ { + 'id': 1, "np.float16": 16.5, "np.float32": 32.5, "np.float64": 64.5, @@ -1127,6 +1137,7 @@ def test_insert_all_analyze(fresh_db, method_name): def test_create_with_a_null_column(fresh_db): record = {"name": "Name", "description": None} fresh_db["t"].insert(record) + record['id'] = fresh_db["t"].last_pk assert [record] == list(fresh_db["t"].rows) @@ -1148,19 +1159,19 @@ def test_quote(fresh_db, input, expected): ( ( {"id": int}, - "[id] INTEGER", + "[id] INTEGER PRIMARY KEY", ), ( {"col": dict}, - "[col] TEXT", + "[id] INTEGER PRIMARY KEY,\n [col] TEXT", ), ( {"col": tuple}, - "[col] TEXT", + "[id] INTEGER PRIMARY KEY,\n [col] TEXT", ), ( {"col": list}, - "[col] TEXT", + "[id] INTEGER PRIMARY KEY,\n [col] TEXT", ), ), ) @@ -1226,7 +1237,7 @@ def test_create_replace(fresh_db): fresh_db["t"].create({"id": int}) # This should not fresh_db["t"].create({"name": str}, replace=True) - assert fresh_db["t"].schema == ("CREATE TABLE [t] (\n" " [name] TEXT\n" ")") + assert fresh_db["t"].schema == 'CREATE TABLE [t] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT\n)' @pytest.mark.parametrize( @@ -1240,7 +1251,7 @@ def test_create_replace(fresh_db): False, ), # Drop name column, remove primary key - ({"id": int}, {}, 'CREATE TABLE "demo" (\n [id] INTEGER\n)', True), + ({"id": int}, {}, 'CREATE TABLE "demo" (\n [id] INTEGER PRIMARY KEY\n)', True), # Add a new column ( {"id": int, "name": str, "age": int}, @@ -1310,7 +1321,7 @@ def test_rename_table(fresh_db): assert ["t"] == fresh_db.table_names() fresh_db.rename_table("t", "renamed") assert ["renamed"] == fresh_db.table_names() - assert [{"foo": "bar"}] == list(fresh_db["renamed"].rows) + assert [{'id': 1, "foo": "bar"}] == list(fresh_db["renamed"].rows) # Should error if table does not exist: with pytest.raises(sqlite3.OperationalError): fresh_db.rename_table("does_not_exist", "renamed") @@ -1357,3 +1368,4 @@ def test_create_strict(fresh_db, strict): table = fresh_db["t"] table.create({"id": int}, strict=strict) assert table.strict == strict or not fresh_db.supports_strict + \ No newline at end of file diff --git a/tests/test_extract.py b/tests/test_extract.py index e1016cc..71dc970 100644 --- a/tests/test_extract.py +++ b/tests/test_extract.py @@ -122,6 +122,7 @@ def test_extract_rowid_table(fresh_db): fresh_db["tree"].extract(["common_name", "latin_name"]) assert fresh_db["tree"].schema == ( 'CREATE TABLE "tree" (\n' + ' [id] INTEGER PRIMARY KEY,\n' " [name] TEXT,\n" " [common_name_latin_name_id] INTEGER REFERENCES [common_name_latin_name]([id])\n" ")" diff --git a/tests/test_extracts.py b/tests/test_extracts.py index d5c6368..58fefe9 100644 --- a/tests/test_extracts.py +++ b/tests/test_extracts.py @@ -36,7 +36,7 @@ def test_extracts(fresh_db, kwargs, expected_table, use_table_factory): == fresh_db[expected_table].schema ) assert ( - "CREATE TABLE [Trees] (\n [id] INTEGER,\n [species_id] INTEGER REFERENCES [{}]([id])\n)".format( + "CREATE TABLE [Trees] (\n [id] INTEGER PRIMARY KEY,\n [species_id] INTEGER REFERENCES [{}]([id])\n)".format( expected_table ) == fresh_db["Trees"].schema diff --git a/tests/test_get.py b/tests/test_get.py index 304e37d..c7f5a82 100644 --- a/tests/test_get.py +++ b/tests/test_get.py @@ -6,7 +6,9 @@ def test_get_rowid(fresh_db): dogs = fresh_db["dogs"] cleo = {"name": "Cleo", "age": 4} row_id = dogs.insert(cleo).last_rowid - assert cleo == dogs.get(row_id) + res = dogs.get(row_id) + res.pop('id') + assert cleo == res def test_get_primary_key(fresh_db): diff --git a/tests/test_hypothesis.py b/tests/test_hypothesis.py index 771e992..faedaa4 100644 --- a/tests/test_hypothesis.py +++ b/tests/test_hypothesis.py @@ -7,9 +7,7 @@ @given(st.integers(-9223372036854775808, 9223372036854775807)) def test_roundtrip_integers(integer): db = sqlite_minutils.Database(memory=True) - row = { - "integer": integer, - } + row = {'id':1, "integer": integer} db["test"].insert(row) assert list(db["test"].rows) == [row] @@ -17,9 +15,7 @@ def test_roundtrip_integers(integer): @given(st.text()) def test_roundtrip_text(text): db = sqlite_minutils.Database(memory=True) - row = { - "text": text, - } + row = {'id':1, "text": text} db["test"].insert(row) assert list(db["test"].rows) == [row] @@ -27,9 +23,7 @@ def test_roundtrip_text(text): @given(st.binary(max_size=1024 * 1024)) def test_roundtrip_binary(binary): db = sqlite_minutils.Database(memory=True) - row = { - "binary": binary, - } + row = {'id':1, "binary": binary} db["test"].insert(row) assert list(db["test"].rows) == [row] @@ -37,8 +31,6 @@ def test_roundtrip_binary(binary): @given(st.floats(allow_nan=False)) def test_roundtrip_floats(floats): db = sqlite_minutils.Database(memory=True) - row = { - "floats": floats, - } + row = {'id':1, "floats": floats} db["test"].insert(row) assert list(db["test"].rows) == [row] diff --git a/tests/test_m2m.py b/tests/test_m2m.py index 646a395..26367c2 100644 --- a/tests/test_m2m.py +++ b/tests/test_m2m.py @@ -192,7 +192,7 @@ def test_uses_existing_m2m_table_if_exists(fresh_db): assert fresh_db["tagged"].exists() assert not fresh_db["people_tags"].exists() assert not fresh_db["tags_people"].exists() - assert [{"people_id": 1, "tags_id": 1}] == list(fresh_db["tagged"].rows) + assert [{'id': 1,"people_id": 1, "tags_id": 1}] == list(fresh_db["tagged"].rows) def test_requires_explicit_m2m_table_if_multiple_options(fresh_db): diff --git a/tests/test_query.py b/tests/test_query.py index 9d87460..643c283 100644 --- a/tests/test_query.py +++ b/tests/test_query.py @@ -7,7 +7,7 @@ def test_query(fresh_db): fresh_db["dogs"].insert_all([{"name": "Cleo"}, {"name": "Pancakes"}]) results = fresh_db.query("select * from dogs order by name desc") assert isinstance(results, types.GeneratorType) - assert list(results) == [{"name": "Pancakes"}, {"name": "Cleo"}] + assert list(results) == [{'id':2, "name": "Pancakes"}, {'id':1, "name": "Cleo"}] def test_execute_returning_dicts(fresh_db): @@ -24,4 +24,4 @@ def test_query_no_update(fresh_db): fresh_db["message"].insert({"msg_type": "greeting", "content": "hello"}) results = fresh_db.query("update message set msg_type='note' where msg_type='md'") assert list(results) == [] - assert list(fresh_db["message"].rows) == [{"msg_type": "greeting", "content": "hello"}] + assert list(fresh_db["message"].rows) == [{'id':1,"msg_type": "greeting", "content": "hello"}] diff --git a/tests/test_rows.py b/tests/test_rows.py index a8a4ca0..a9bfd14 100644 --- a/tests/test_rows.py +++ b/tests/test_rows.py @@ -75,9 +75,9 @@ def test_pks_and_rows_where_rowid(fresh_db): table.insert_all({"number": i + 10} for i in range(3)) pks_and_rows = list(table.pks_and_rows_where()) assert pks_and_rows == [ - (1, {"rowid": 1, "number": 10}), - (2, {"rowid": 2, "number": 11}), - (3, {"rowid": 3, "number": 12}), + (1, {"id": 1, "number": 10}), + (2, {"id": 2, "number": 11}), + (3, {"id": 3, "number": 12}), ] diff --git a/tests/test_transform.py b/tests/test_transform.py index 93bed9c..2cf67b7 100644 --- a/tests/test_transform.py +++ b/tests/test_transform.py @@ -1,4 +1,4 @@ -from sqlite_minutils.db import ForeignKey +from sqlite_minutils.db import ForeignKey, Database from sqlite_minutils.utils import OperationalError from sqlite3 import IntegrityError import pytest @@ -77,16 +77,6 @@ "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", ], ), - # Remove primary key, creating a rowid table - ( - {"pk": None}, - [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [age] TEXT\n);", - "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", - "DROP TABLE [dogs];", - "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", - ], - ), # Keeping the table ( {"drop": ["age"], "keep_table": "kept_table"}, @@ -134,7 +124,7 @@ def tracer(sql, params): ( {}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [age] TEXT\n);", + "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [age] TEXT\n);", "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", "DROP TABLE [dogs];", "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", @@ -144,7 +134,7 @@ def tracer(sql, params): ( {"types": {"age": int}}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [age] INTEGER\n);", + "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [age] INTEGER\n);", "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", "DROP TABLE [dogs];", "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", @@ -154,7 +144,7 @@ def tracer(sql, params): ( {"rename": {"age": "dog_age"}}, [ - "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER,\n [name] TEXT,\n [dog_age] TEXT\n);", + "CREATE TABLE [dogs_new_suffix] (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [dog_age] TEXT\n);", "INSERT INTO [dogs_new_suffix] ([rowid], [id], [name], [dog_age])\n SELECT [rowid], [id], [name], [age] FROM [dogs];", "DROP TABLE [dogs];", "ALTER TABLE [dogs_new_suffix] RENAME TO [dogs];", @@ -200,21 +190,6 @@ def tracer(sql, params): assert ("PRAGMA foreign_keys=1;", None) not in captured -def test_transform_sql_with_no_primary_key_to_primary_key_of_id(fresh_db): - dogs = fresh_db["dogs"] - dogs.insert({"id": 1, "name": "Cleo", "age": "5"}) - assert ( - dogs.schema - == "CREATE TABLE [dogs] (\n [id] INTEGER,\n [name] TEXT,\n [age] TEXT\n)" - ) - dogs.transform(pk="id") - # Slight oddity: [dogs] becomes "dogs" during the rename: - assert ( - dogs.schema - == 'CREATE TABLE "dogs" (\n [id] INTEGER PRIMARY KEY,\n [name] TEXT,\n [age] TEXT\n)' - ) - - def test_transform_rename_pk(fresh_db): dogs = fresh_db["dogs"] dogs.insert({"id": 1, "name": "Cleo", "age": "5"}, pk="id") @@ -427,7 +402,7 @@ def test_transform_add_foreign_keys_from_scratch(fresh_db): ] assert fresh_db["places"].schema == ( 'CREATE TABLE "places" (\n' - " [id] INTEGER,\n" + " [id] INTEGER PRIMARY KEY,\n" " [name] TEXT,\n" " [country] INTEGER REFERENCES [country]([id]),\n" " [continent] INTEGER REFERENCES [continent]([id]),\n" @@ -498,7 +473,7 @@ def test_transform_replace_foreign_keys(fresh_db, foreign_keys): fresh_db["places"].transform(foreign_keys=foreign_keys) assert fresh_db["places"].schema == ( 'CREATE TABLE "places" (\n' - " [id] INTEGER,\n" + " [id] INTEGER PRIMARY KEY,\n" " [name] TEXT,\n" " [country] INTEGER REFERENCES [country]([id]),\n" " [continent] INTEGER REFERENCES [continent]([id]),\n" @@ -546,3 +521,22 @@ def test_transform_strict(fresh_db, strict): assert dogs.strict == strict or not fresh_db.supports_strict dogs.transform(not_null={"name"}) assert dogs.strict == strict or not fresh_db.supports_strict + + +def test_create_twice(fresh_db): + columns = {'name': str} + fresh_db['demo'].create(columns=columns) + fresh_db['demo'].create(columns=columns, transform=True) + +def test_create_twice_with_pk_change(fresh_db): + columns = {'name': str} + fresh_db['demo'].create(columns=columns) + new_columns = {'name': str, 'age': int} + fresh_db['demo'].create(columns=new_columns, transform=True, pk='age') + +def test_create_thrice_with_pk_change(fresh_db): + columns = {'name': str} + fresh_db['demo'].create(columns=columns) + new_columns = {'name': str, 'age': int} + fresh_db['demo'].create(columns=new_columns, transform=True, pk='age') + fresh_db['demo'].create(columns=new_columns, transform=True, pk='age') \ No newline at end of file diff --git a/tests/test_update.py b/tests/test_update.py index 08d3d62..fd2a26a 100644 --- a/tests/test_update.py +++ b/tests/test_update.py @@ -13,9 +13,9 @@ def test_update_no_change(fresh_db): table = fresh_db["table"] table.insert({"foo": "bar"}) table.update(1, {"foo": "bar"}) - assert [{"foo": "bar"}] == table.update(1, {"foo": "bar"}).result + assert [{'id': 1, "foo": "bar"}] == table.update(1, {"foo": "bar"}).result table.update(1, {}) - assert [{"foo": "bar"}] == list(table.rows) + assert [{'id': 1, "foo": "bar"}] == list(table.rows) ## Updates where something changes