tenseleyflow/shithub / b4ac4b4

Browse files

S27: migration 0029 — fork_count + init_status + maintenance triggers

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
b4ac4b4e384f964db1b4ce3fe26b6288acfb83f7
Parents
be2115f
Tree
53fa61c

1 changed file

StatusFile+-
A internal/migrationsfs/migrations/0029_forks.sql 83 0
internal/migrationsfs/migrations/0029_forks.sqladded
@@ -0,0 +1,83 @@
1
+-- SPDX-License-Identifier: AGPL-3.0-or-later
2
+--
3
+-- S27 fork-support columns + the maintenance trigger.
4
+--
5
+-- The S11 / S27 specs assumed `is_fork` and `fork_count` were already
6
+-- in `repos`; in fact only `fork_of_repo_id` shipped. We add
7
+-- `fork_count` here. `is_fork` is intentionally skipped — it would
8
+-- duplicate the truth of `fork_of_repo_id IS NOT NULL`. Same gap
9
+-- pattern as S26 caught for `star_count`/`watcher_count`; noted in
10
+-- the S27 status block.
11
+--
12
+-- `init_status` tracks the async clone job's progression. Synchronous
13
+-- repo creates (the S11 path) write 'initialized' directly. Forks
14
+-- start at 'init_pending' and the worker flips to 'initialized' on
15
+-- success or 'init_failed' on permanent failure (poison error). The
16
+-- repo home view reads this column to decide between "your fork is
17
+-- being prepared" placeholder and the real tree view.
18
+
19
+-- +goose Up
20
+ALTER TABLE repos
21
+    ADD COLUMN fork_count bigint NOT NULL DEFAULT 0;
22
+
23
+CREATE TYPE repo_init_status AS ENUM ('initialized', 'init_pending', 'init_failed');
24
+
25
+-- Default 'initialized' so the back-fill on existing rows is correct
26
+-- (every pre-S27 repo was created synchronously).
27
+ALTER TABLE repos
28
+    ADD COLUMN init_status repo_init_status NOT NULL DEFAULT 'initialized';
29
+
30
+-- Maintenance trigger: when a repos row with fork_of_repo_id IS NOT NULL
31
+-- is inserted (or hard-deleted), bump the source repo's fork_count.
32
+-- Soft delete (deleted_at IS NOT NULL) does NOT decrement — the row
33
+-- is still present, hard-delete is what cascades. ON DELETE SET NULL
34
+-- on `fork_of_repo_id` would NULL the column on source delete; the
35
+-- delete trigger fires before that on the *fork* row.
36
+
37
+-- +goose StatementBegin
38
+CREATE OR REPLACE FUNCTION tg_forks_count_inc() RETURNS trigger
39
+    LANGUAGE plpgsql AS $$
40
+BEGIN
41
+    IF NEW.fork_of_repo_id IS NOT NULL THEN
42
+        UPDATE repos SET fork_count = fork_count + 1
43
+            WHERE id = NEW.fork_of_repo_id;
44
+    END IF;
45
+    RETURN NEW;
46
+END;
47
+$$;
48
+-- +goose StatementEnd
49
+
50
+-- +goose StatementBegin
51
+CREATE OR REPLACE FUNCTION tg_forks_count_dec() RETURNS trigger
52
+    LANGUAGE plpgsql AS $$
53
+BEGIN
54
+    IF OLD.fork_of_repo_id IS NOT NULL THEN
55
+        UPDATE repos SET fork_count = GREATEST(fork_count - 1, 0)
56
+            WHERE id = OLD.fork_of_repo_id;
57
+    END IF;
58
+    RETURN OLD;
59
+END;
60
+$$;
61
+-- +goose StatementEnd
62
+
63
+CREATE TRIGGER forks_count_inc AFTER INSERT ON repos
64
+    FOR EACH ROW EXECUTE FUNCTION tg_forks_count_inc();
65
+
66
+CREATE TRIGGER forks_count_dec AFTER DELETE ON repos
67
+    FOR EACH ROW EXECUTE FUNCTION tg_forks_count_dec();
68
+
69
+-- Listing forks of a repo: index on the FK + recency.
70
+CREATE INDEX repos_fork_of_repo_id_idx
71
+    ON repos (fork_of_repo_id, created_at DESC)
72
+    WHERE fork_of_repo_id IS NOT NULL;
73
+
74
+-- +goose Down
75
+DROP INDEX IF EXISTS repos_fork_of_repo_id_idx;
76
+DROP TRIGGER IF EXISTS forks_count_dec ON repos;
77
+DROP TRIGGER IF EXISTS forks_count_inc ON repos;
78
+DROP FUNCTION IF EXISTS tg_forks_count_dec();
79
+DROP FUNCTION IF EXISTS tg_forks_count_inc();
80
+ALTER TABLE repos
81
+    DROP COLUMN IF EXISTS init_status,
82
+    DROP COLUMN IF EXISTS fork_count;
83
+DROP TYPE IF EXISTS repo_init_status;