mirror of
https://github.com/simonw/sqlite-utils.git
synced 2026-07-23 17:34:32 +02:00
add_foreign_key() on_delete= and on_update= parameters, closes #530
table.add_foreign_key() now accepts on_delete= and on_update= to
create foreign keys with ON DELETE/ON UPDATE actions:
table.add_foreign_key("author_id", "authors", "id", on_delete="CASCADE")
Works for compound foreign keys too.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
658185d297
commit
0ec0180405
4 changed files with 61 additions and 3 deletions
|
|
@ -23,6 +23,7 @@ Compound foreign key support:
|
|||
Other foreign key improvements:
|
||||
|
||||
- ``ForeignKey`` now exposes ``on_delete`` and ``on_update`` fields reflecting the foreign key's ``ON DELETE``/``ON UPDATE`` actions, and ``table.transform()`` preserves those actions. Previously a transform silently stripped clauses such as ``ON DELETE CASCADE`` from the table schema.
|
||||
- ``table.add_foreign_key()`` accepts new ``on_delete=`` and ``on_update=`` parameters for creating foreign keys with actions, e.g. ``table.add_foreign_key("author_id", "authors", "id", on_delete="CASCADE")``. (:issue:`530`)
|
||||
- Foreign keys declared as ``REFERENCES other_table`` with no explicit column are now resolved to the other table's primary key by ``table.foreign_keys``, instead of reporting ``other_column=None``.
|
||||
- Fixed a ``TypeError`` when sorting ``ForeignKey`` objects where some were compound.
|
||||
|
||||
|
|
|
|||
|
|
@ -1591,6 +1591,16 @@ To add a compound foreign key, pass tuples of columns:
|
|||
|
||||
As with single columns, omitting the other columns will use the compound primary key of the other table. ``other_table`` must always be specified for a compound foreign key.
|
||||
|
||||
Use ``on_delete=`` and ``on_update=`` to specify ``ON DELETE`` and ``ON UPDATE`` actions for the foreign key:
|
||||
|
||||
.. code-block:: python
|
||||
|
||||
db.table("books").add_foreign_key(
|
||||
"author_id", "authors", "id", on_delete="CASCADE"
|
||||
)
|
||||
|
||||
This creates a foreign key with an ``ON DELETE CASCADE`` clause, so deleting an author will also delete their books (provided foreign key enforcement is enabled with ``PRAGMA foreign_keys = ON``). Valid actions are ``"SET NULL"``, ``"SET DEFAULT"``, ``"CASCADE"``, ``"RESTRICT"`` and the default ``"NO ACTION"``.
|
||||
|
||||
.. _python_api_add_foreign_keys:
|
||||
|
||||
Adding multiple foreign key constraints at once
|
||||
|
|
|
|||
|
|
@ -2934,6 +2934,8 @@ class Table(Queryable):
|
|||
other_table: Optional[str] = None,
|
||||
other_column: Optional[ForeignKeyColumns] = None,
|
||||
ignore: bool = False,
|
||||
on_delete: str = "NO ACTION",
|
||||
on_update: str = "NO ACTION",
|
||||
):
|
||||
"""
|
||||
Alter the schema to mark the specified column as a foreign key to another table.
|
||||
|
|
@ -2944,6 +2946,9 @@ class Table(Queryable):
|
|||
:param other_column: The column on the other table it - if omitted, will be guessed.
|
||||
Use a tuple of columns for a compound foreign key.
|
||||
:param ignore: Set this to ``True`` to ignore an existing foreign key - otherwise a ``AlterError`` will be raised.
|
||||
:param on_delete: ``ON DELETE`` action for the foreign key, e.g. ``"CASCADE"``
|
||||
or ``"SET NULL"``.
|
||||
:param on_update: ``ON UPDATE`` action for the foreign key.
|
||||
"""
|
||||
columns = (column,) if isinstance(column, str) else tuple(column)
|
||||
# Ensure columns exist
|
||||
|
|
@ -2997,11 +3002,27 @@ class Table(Queryable):
|
|||
)
|
||||
)
|
||||
if len(columns) == 1:
|
||||
self.db.add_foreign_keys(
|
||||
[(self.name, columns[0], other_table, other_columns[0])]
|
||||
fk_object = ForeignKey(
|
||||
self.name,
|
||||
columns[0],
|
||||
other_table,
|
||||
other_columns[0],
|
||||
on_delete=on_delete,
|
||||
on_update=on_update,
|
||||
)
|
||||
else:
|
||||
self.db.add_foreign_keys([(self.name, columns, other_table, other_columns)])
|
||||
fk_object = ForeignKey(
|
||||
self.name,
|
||||
None,
|
||||
other_table,
|
||||
None,
|
||||
columns=columns,
|
||||
other_columns=other_columns,
|
||||
is_compound=True,
|
||||
on_delete=on_delete,
|
||||
on_update=on_update,
|
||||
)
|
||||
self.db.add_foreign_keys([fk_object])
|
||||
return self
|
||||
|
||||
def enable_counts(self) -> None:
|
||||
|
|
|
|||
|
|
@ -498,3 +498,29 @@ def test_add_foreign_keys_preserves_actions_compound(courses_db):
|
|||
assert fk.is_compound is True
|
||||
assert fk.on_delete == "CASCADE"
|
||||
assert "ON DELETE CASCADE" in courses_db["courses"].schema
|
||||
|
||||
|
||||
def test_add_foreign_key_on_delete_on_update(fresh_db):
|
||||
fresh_db["authors"].insert({"id": 1}, pk="id")
|
||||
fresh_db["books"].insert({"id": 1, "author_id": 1}, pk="id")
|
||||
fresh_db["books"].add_foreign_key(
|
||||
"author_id", "authors", "id", on_delete="CASCADE", on_update="RESTRICT"
|
||||
)
|
||||
fk = fresh_db["books"].foreign_keys[0]
|
||||
assert fk.on_delete == "CASCADE"
|
||||
assert fk.on_update == "RESTRICT"
|
||||
assert "ON UPDATE RESTRICT ON DELETE CASCADE" in fresh_db["books"].schema
|
||||
# The cascade should actually fire
|
||||
fresh_db.execute("PRAGMA foreign_keys = ON")
|
||||
fresh_db.execute("delete from authors where id = 1")
|
||||
assert fresh_db["books"].count == 0
|
||||
|
||||
|
||||
def test_add_compound_foreign_key_on_delete(courses_db):
|
||||
courses_db["courses"].add_foreign_key(
|
||||
("campus_name", "dept_code"), "departments", on_delete="SET NULL"
|
||||
)
|
||||
fk = courses_db["courses"].foreign_keys[0]
|
||||
assert fk.is_compound is True
|
||||
assert fk.on_delete == "SET NULL"
|
||||
assert "ON DELETE SET NULL" in courses_db["courses"].schema
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue