1from __future__ import annotations
2
3
4class OnDelete:
5 """Sentinel marking an on_delete action.
6
7 Each valid action is a single `OnDelete` instance exported from
8 ``plain.postgres`` (CASCADE, SET_NULL, RESTRICT, NO_ACTION). Cascading
9 is enforced entirely by Postgres via the corresponding ``ON DELETE``
10 clause — there is no application-level traversal.
11 """
12
13 __slots__ = ("name", "sql_clause", "confdeltype")
14
15 def __init__(self, name: str, sql_clause: str, confdeltype: str) -> None:
16 self.name = name
17 self.sql_clause = sql_clause
18 self.confdeltype = confdeltype
19
20 def __repr__(self) -> str:
21 return f"<plain.postgres.{self.name}>"
22
23
24#: Child rows are deleted by Postgres when the parent is deleted.
25CASCADE = OnDelete("CASCADE", " ON DELETE CASCADE", "c")
26
27#: Child FK columns are set to NULL when the parent is deleted.
28#: Requires ``allow_null=True`` on the field.
29SET_NULL = OnDelete("SET_NULL", " ON DELETE SET NULL", "n")
30
31#: Deleting the parent fails immediately with IntegrityError if children exist.
32#: The check is always immediate — it is not affected by DEFERRABLE.
33RESTRICT = OnDelete("RESTRICT", " ON DELETE RESTRICT", "r")
34
35#: Deleting the parent fails at transaction commit if children exist.
36#: Respects DEFERRABLE INITIALLY DEFERRED, so constraint violations inside a
37#: transaction can be resolved before commit.
38NO_ACTION = OnDelete("NO_ACTION", "", "a")
39
40
41def sql_on_delete(on_delete: OnDelete) -> tuple[str, str]:
42 """Return ``(sql_clause, pg_confdeltype_code)`` for an on_delete value."""
43 if not isinstance(on_delete, OnDelete):
44 raise TypeError(
45 "on_delete must be one of plain.postgres.CASCADE, SET_NULL, "
46 f"RESTRICT, or NO_ACTION; got {on_delete!r}"
47 )
48 return (on_delete.sql_clause, on_delete.confdeltype)