@@ -0,0 +1,94 @@ |
| 1 | +-- SPDX-License-Identifier: AGPL-3.0-or-later |
| 2 | +-- |
| 3 | +-- S41a workflow runs — top-level row per triggered workflow. |
| 4 | +-- |
| 5 | +-- Each push / pull_request / schedule / workflow_dispatch event matched |
| 6 | +-- against a parsed `.shithub/workflows/*.yml` produces one row here. |
| 7 | +-- Child rows live in workflow_jobs (0043) and workflow_steps (0044). |
| 8 | +-- Status moves queued → running → completed | cancelled. conclusion is |
| 9 | +-- set on completion via the same enum values check_runs uses (S24); |
| 10 | +-- one workflow_jobs row maps to one check_runs row (S41b creates the |
| 11 | +-- mapping). |
| 12 | +-- |
| 13 | +-- Per-repo run_index gives stable URLs (/owner/repo/actions/runs/42) |
| 14 | +-- without leaking global IDs across repos. Pattern cribbed from |
| 15 | +-- Forgejo's actions_run.index — see .refs/forgejo/models/actions/run.go. |
| 16 | +-- |
| 17 | +-- Optimistic-lock `version` column lets the runner + the cancel path |
| 18 | +-- update status concurrently without overwriting each other (Forgejo |
| 19 | +-- pattern, same file). |
| 20 | +-- |
| 21 | +-- concurrency_group is parsed in S41a but only honored from S41g; we |
| 22 | +-- carry the column from day one so retroactive backfill isn't needed. |
| 23 | +-- Fork-PR approval flow (need_approval, approved_by) is parked for |
| 24 | +-- v2 but the columns exist so the schema doesn't churn later. |
| 25 | + |
| 26 | +-- +goose Up |
| 27 | + |
| 28 | +CREATE TYPE workflow_run_status AS ENUM ( |
| 29 | + 'queued', 'running', 'completed', 'cancelled' |
| 30 | +); |
| 31 | + |
| 32 | +CREATE TYPE workflow_run_event AS ENUM ( |
| 33 | + 'push', 'pull_request', 'schedule', 'workflow_dispatch' |
| 34 | +); |
| 35 | + |
| 36 | +CREATE TABLE workflow_runs ( |
| 37 | + id bigserial PRIMARY KEY, |
| 38 | + repo_id bigint NOT NULL REFERENCES repos(id) ON DELETE CASCADE, |
| 39 | + run_index bigint NOT NULL, |
| 40 | + workflow_file text NOT NULL, |
| 41 | + workflow_name text NOT NULL DEFAULT '', |
| 42 | + head_sha text NOT NULL, |
| 43 | + head_ref text NOT NULL DEFAULT '', |
| 44 | + event workflow_run_event NOT NULL, |
| 45 | + event_payload jsonb NOT NULL DEFAULT '{}'::jsonb, |
| 46 | + actor_user_id bigint REFERENCES users(id) ON DELETE SET NULL, |
| 47 | + parent_run_id bigint REFERENCES workflow_runs(id) ON DELETE SET NULL, |
| 48 | + concurrency_group text NOT NULL DEFAULT '', |
| 49 | + status workflow_run_status NOT NULL DEFAULT 'queued', |
| 50 | + conclusion check_conclusion, |
| 51 | + pinned boolean NOT NULL DEFAULT false, |
| 52 | + need_approval boolean NOT NULL DEFAULT false, |
| 53 | + approved_by_user_id bigint REFERENCES users(id) ON DELETE SET NULL, |
| 54 | + started_at timestamptz, |
| 55 | + completed_at timestamptz, |
| 56 | + version integer NOT NULL DEFAULT 0, |
| 57 | + created_at timestamptz NOT NULL DEFAULT now(), |
| 58 | + updated_at timestamptz NOT NULL DEFAULT now(), |
| 59 | + |
| 60 | + UNIQUE (repo_id, run_index), |
| 61 | + |
| 62 | + CONSTRAINT workflow_runs_workflow_file_length CHECK (char_length(workflow_file) BETWEEN 1 AND 256), |
| 63 | + CONSTRAINT workflow_runs_workflow_name_length CHECK (char_length(workflow_name) <= 256), |
| 64 | + CONSTRAINT workflow_runs_head_sha_format CHECK (char_length(head_sha) BETWEEN 7 AND 64), |
| 65 | + CONSTRAINT workflow_runs_head_ref_length CHECK (char_length(head_ref) <= 256), |
| 66 | + CONSTRAINT workflow_runs_concurrency_length CHECK (char_length(concurrency_group) <= 256), |
| 67 | + CONSTRAINT workflow_runs_completed_has_conclusion CHECK ( |
| 68 | + status <> 'completed' OR conclusion IS NOT NULL |
| 69 | + ), |
| 70 | + CONSTRAINT workflow_runs_started_when_running CHECK ( |
| 71 | + status NOT IN ('running', 'completed', 'cancelled') OR started_at IS NOT NULL |
| 72 | + ), |
| 73 | + CONSTRAINT workflow_runs_completed_when_done CHECK ( |
| 74 | + status NOT IN ('completed', 'cancelled') OR completed_at IS NOT NULL |
| 75 | + ) |
| 76 | +); |
| 77 | + |
| 78 | +CREATE INDEX workflow_runs_repo_head_idx ON workflow_runs (repo_id, head_sha); |
| 79 | +CREATE INDEX workflow_runs_repo_status_idx ON workflow_runs (repo_id, status, created_at DESC); |
| 80 | +CREATE INDEX workflow_runs_actor_idx ON workflow_runs (actor_user_id, created_at DESC); |
| 81 | +CREATE INDEX workflow_runs_concurrency_idx ON workflow_runs (repo_id, concurrency_group, status) |
| 82 | + WHERE concurrency_group <> ''; |
| 83 | +CREATE INDEX workflow_runs_event_idx ON workflow_runs (repo_id, event, created_at DESC); |
| 84 | +CREATE INDEX workflow_runs_parent_idx ON workflow_runs (parent_run_id) |
| 85 | + WHERE parent_run_id IS NOT NULL; |
| 86 | + |
| 87 | +CREATE TRIGGER set_updated_at BEFORE UPDATE ON workflow_runs |
| 88 | + FOR EACH ROW EXECUTE FUNCTION tg_set_updated_at(); |
| 89 | + |
| 90 | + |
| 91 | +-- +goose Down |
| 92 | +DROP TABLE IF EXISTS workflow_runs; |
| 93 | +DROP TYPE IF EXISTS workflow_run_event; |
| 94 | +DROP TYPE IF EXISTS workflow_run_status; |