You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
chore(db): drop the legacy folder tables and adopt the deferred folder_id FKs (#6051)
* chore(db): drop the legacy folder tables and adopt the deferred folder_id FKs
Contract migration closing out the generic-folders cutover: drops workflow_folder
and workspace_file_folders, and adopts the two folder_id foreign keys 0272
deliberately deferred.
Runs a final INSERT-ONLY reconcile first so no stranded legacy folder is lost by
the drop. Insert-only because 0272 deliberately renamed 47 workflow folders to
dedupe them, and an upsert would revert all 47. In production this is a no-op
(verified 0 stranded); it exists for deployments that never ran the post-drain
reconcile as an operational step, self-hosted upgrades above all.
Hand-written rather than drizzle-generated: the generated form drops the tables
BEFORE adding the FKs with no step to re-root unresolvable folder_ids, so one
dangling id fails ADD CONSTRAINT after the legacy data is already gone; it also
validates the FK inline, holding ACCESS EXCLUSIVE across a full scan of the
1.7M-row workspace_files, and uses DROP TABLE CASCADE. The generated snapshot is
kept so drizzle-kit generate reports no schema changes.
Every re-root path dedupes, because all three destinations are partial unique
indexes keyed on a coalesced nullable column: folder, workflow, and
workspace_files. Parents must also be reachable — an active row re-roots off a
soft-deleted parent, while a soft-deleted row may keep one.
Also hardens the retention sweep, which the new ON DELETE SET NULL exposes: a
surviving active child re-rooted by the FK can collide at the workspace root,
and chunkedBatchDelete turns that 23505 into a permanent per-chunk stall. The
folder cleanup target now renames children first, covering workflows, files and
subfolders, re-asserts eligibility so a restore mid-batch is not stripped, and
treats the deduplicated name as a hint since both allocators can return a
colliding one.
* fix(db): stop the re-root dedupe renaming personal workflows
workflow.workspace_id is nullable, and NULL is treated as EQUAL by PARTITION BY
but UNKNOWN by the = in base_taken. So for personal workflows rn incremented
across the group while no collision was ever detected: the dedupe could only
fire spuriously, renaming a user-visible workflow that needed no rename, since
the unique index treats NULL workspace_id rows as distinct anyway. The file
block already carried the equivalent guard.
Also gives step 1's inserts ON CONFLICT (id) DO NOTHING. It restates the
stranded guard, closing the window between that read's snapshot and the index
check — an operational re-run of 0274, or a live pod, committing into folder
mid-statement would otherwise raise 23505, and migrate.ts retries only 55P03.
0272 and 0274 were already written this way; step 1 was the exception.
Corrects three comments that claimed more than the code delivers: the header's
'no legacy folder is lost' (an id already present under another resource_type
cannot be rescued), the reachability note (a cycle among stranded rows survives
a per-row parent check), and a note made stale by the ON CONFLICT above.
0 commit comments