mirror of
https://github.com/MODSetter/SurfSense.git
synced 2026-05-03 21:02:40 +02:00
139 lines
4.2 KiB
Python
139 lines
4.2 KiB
Python
"""134_relax_revision_fks
|
|
|
|
Revision ID: 134
|
|
Revises: 133
|
|
Create Date: 2026-04-29
|
|
|
|
Relax the parent FKs on ``document_revisions`` and ``folder_revisions`` so
|
|
revisions survive the deletes they describe.
|
|
|
|
Why: the snapshot/revert pipeline writes a ``DocumentRevision`` BEFORE
|
|
hard-deleting a document via the ``rm`` tool (and likewise a
|
|
``FolderRevision`` before ``rmdir``). If the FK is ``ON DELETE CASCADE``
|
|
the snapshot row is wiped at the exact moment we need it most — revert
|
|
then has nothing to read and the operation becomes irreversible.
|
|
|
|
Migration:
|
|
|
|
* ``document_revisions.document_id``: ``NOT NULL`` -> nullable; FK
|
|
``ON DELETE CASCADE`` -> ``ON DELETE SET NULL``.
|
|
* ``folder_revisions.folder_id``: same treatment.
|
|
|
|
The ``search_space_id`` FK on both tables is left unchanged (still
|
|
``ON DELETE CASCADE``). When a search space is deleted, all documents,
|
|
folders, AND their revisions go together — that's the correct teardown
|
|
story.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
from collections.abc import Sequence
|
|
|
|
import sqlalchemy as sa
|
|
from sqlalchemy import inspect
|
|
|
|
from alembic import op
|
|
|
|
revision: str = "134"
|
|
down_revision: str | None = "133"
|
|
branch_labels: str | Sequence[str] | None = None
|
|
depends_on: str | Sequence[str] | None = None
|
|
|
|
|
|
def _fk_name(bind, table: str, column: str) -> str | None:
|
|
"""Return the (single) FK constraint name on ``table.column``, if any."""
|
|
inspector = inspect(bind)
|
|
for fk in inspector.get_foreign_keys(table):
|
|
cols = fk.get("constrained_columns") or []
|
|
if cols == [column]:
|
|
return fk.get("name")
|
|
return None
|
|
|
|
|
|
def upgrade() -> None:
|
|
bind = op.get_bind()
|
|
|
|
# --- document_revisions.document_id -> nullable + SET NULL ---------------
|
|
fk_name = _fk_name(bind, "document_revisions", "document_id")
|
|
if fk_name:
|
|
op.drop_constraint(fk_name, "document_revisions", type_="foreignkey")
|
|
op.alter_column(
|
|
"document_revisions",
|
|
"document_id",
|
|
existing_type=sa.Integer(),
|
|
nullable=True,
|
|
)
|
|
op.create_foreign_key(
|
|
"document_revisions_document_id_fkey",
|
|
"document_revisions",
|
|
"documents",
|
|
["document_id"],
|
|
["id"],
|
|
ondelete="SET NULL",
|
|
)
|
|
|
|
# --- folder_revisions.folder_id -> nullable + SET NULL -------------------
|
|
fk_name = _fk_name(bind, "folder_revisions", "folder_id")
|
|
if fk_name:
|
|
op.drop_constraint(fk_name, "folder_revisions", type_="foreignkey")
|
|
op.alter_column(
|
|
"folder_revisions",
|
|
"folder_id",
|
|
existing_type=sa.Integer(),
|
|
nullable=True,
|
|
)
|
|
op.create_foreign_key(
|
|
"folder_revisions_folder_id_fkey",
|
|
"folder_revisions",
|
|
"folders",
|
|
["folder_id"],
|
|
["id"],
|
|
ondelete="SET NULL",
|
|
)
|
|
|
|
|
|
def downgrade() -> None:
|
|
bind = op.get_bind()
|
|
|
|
# Reinstating NOT NULL + CASCADE requires draining orphan rows first
|
|
# (any revision whose parent doc/folder has already been deleted).
|
|
op.execute("DELETE FROM document_revisions WHERE document_id IS NULL")
|
|
op.execute("DELETE FROM folder_revisions WHERE folder_id IS NULL")
|
|
|
|
# --- document_revisions.document_id -> NOT NULL + CASCADE ---------------
|
|
fk_name = _fk_name(bind, "document_revisions", "document_id")
|
|
if fk_name:
|
|
op.drop_constraint(fk_name, "document_revisions", type_="foreignkey")
|
|
op.alter_column(
|
|
"document_revisions",
|
|
"document_id",
|
|
existing_type=sa.Integer(),
|
|
nullable=False,
|
|
)
|
|
op.create_foreign_key(
|
|
"document_revisions_document_id_fkey",
|
|
"document_revisions",
|
|
"documents",
|
|
["document_id"],
|
|
["id"],
|
|
ondelete="CASCADE",
|
|
)
|
|
|
|
# --- folder_revisions.folder_id -> NOT NULL + CASCADE -------------------
|
|
fk_name = _fk_name(bind, "folder_revisions", "folder_id")
|
|
if fk_name:
|
|
op.drop_constraint(fk_name, "folder_revisions", type_="foreignkey")
|
|
op.alter_column(
|
|
"folder_revisions",
|
|
"folder_id",
|
|
existing_type=sa.Integer(),
|
|
nullable=False,
|
|
)
|
|
op.create_foreign_key(
|
|
"folder_revisions_folder_id_fkey",
|
|
"folder_revisions",
|
|
"folders",
|
|
["folder_id"],
|
|
["id"],
|
|
ondelete="CASCADE",
|
|
)
|