-- schema_pre_sync.sql -- Drops hand-managed unique indexes that sea-orm schema sync would reject. -- The sync only compares the indexed column list against the entity's -- #[sea_orm(indexed)] / unique_key declarations; a predicate, a NULLS NOT -- DISTINCT clause or a column list the entity cannot express all look like a -- stale unique key, and Postgres then issues DROP CONSTRAINT on a plain index, -- which fails. These indexes are recreated by schema_extras.sql after sync. -- -- Only indexes whose column list differs from an entity unique key belong -- here. Indexes that do match (idx_issue_watch_issue_user, ..._project_user, -- idx_issue_reaction_issue, idx_issue_reaction_comment) must NOT be dropped: -- sync keeps them as-is and the drop would only rebuild them needlessly. -- -- The migrate command only runs this file when the entity schema fingerprint -- changed, so the rebuild cost is paid on schema changes, not on every start. -- Legacy plain indexes left behind by older unique_key definitions. They no -- longer match the entity column list, so sync would try to drop them and -- abort because they are plain indexes. DROP INDEX IF EXISTS "idx-issue_watch-issue_user"; DROP INDEX IF EXISTS "idx-issue-reaction-issue_user_emoji"; -- Hand-managed per-target unique keys (predicates not expressible in entities). DROP INDEX IF EXISTS idx_issue_reaction_project; DROP INDEX IF EXISTS idx_branch_protection_whitelist_user; DROP INDEX IF EXISTS idx_branch_protection_whitelist_group; DROP INDEX IF EXISTS idx_chat_message_room_nonce; DROP INDEX IF EXISTS idx_chat_dm_pair; DROP INDEX IF EXISTS idx_chat_gdm_key; DROP INDEX IF EXISTS idx_chat_draft_unique; -- scheduled messages feature removed; drop the legacy table before sync DROP TABLE IF EXISTS user_scheduled_message; -- legacy REST admin retired; drop its tables before sync DROP TABLE IF EXISTS admin_audit_log; DROP TABLE IF EXISTS admin_user; -- CI, webhook delivery, import and search features removed DROP TABLE IF EXISTS ci_job_token CASCADE; DROP TABLE IF EXISTS ci_runner_token CASCADE; DROP TABLE IF EXISTS ci_runner CASCADE; DROP TABLE IF EXISTS ci_log_chunk CASCADE; DROP TABLE IF EXISTS workflow_step CASCADE; DROP TABLE IF EXISTS workflow_job CASCADE; DROP TABLE IF EXISTS workflow_artifact CASCADE; DROP TABLE IF EXISTS workflow_run CASCADE; DROP TABLE IF EXISTS ci_workflow_counter CASCADE; DROP TABLE IF EXISTS ci_actions_identity CASCADE; DROP TABLE IF EXISTS ci_actions_schedule CASCADE; DROP TABLE IF EXISTS ci_actions_cache CASCADE; DROP TABLE IF EXISTS ci_deployment CASCADE; DROP TABLE IF EXISTS ci_environment_review CASCADE; DROP TABLE IF EXISTS ci_secret CASCADE; DROP TABLE IF EXISTS ci_variable CASCADE; DROP TABLE IF EXISTS ci_environment CASCADE; DROP TABLE IF EXISTS repo_webhook_delivery CASCADE; DROP TABLE IF EXISTS repo_webhook CASCADE; DROP TABLE IF EXISTS webhook_delivery CASCADE; DROP TABLE IF EXISTS webhook CASCADE; DROP TABLE IF EXISTS import_job CASCADE; DROP TABLE IF EXISTS search_index_job CASCADE; DROP TABLE IF EXISTS search_repo_state CASCADE; -- chat message full-text search removed DROP FUNCTION IF EXISTS chat_message_search_vector_update() CASCADE; DO $$ BEGIN IF to_regclass('chat_message') IS NOT NULL THEN DROP TRIGGER IF EXISTS chat_message_search_vector_trigger ON chat_message; ALTER TABLE chat_message DROP COLUMN IF EXISTS search_vector; END IF; END $$; -- pipeline tab removed from repository settings DO $$ BEGIN IF to_regclass('repos') IS NOT NULL THEN ALTER TABLE repos DROP COLUMN IF EXISTS pipelines_public; END IF; END $$; -- rooms.parent_id used to be declared as a self-reference, so sync created -- `fk-rooms-parent_id -> rooms(id)`. The column actually points at a directory -- node (dir.id) — update_room validates the target is a same-space DirKind::Dir -- row before writing — so every attempt to file a channel into a folder was -- rejected by that constraint and the feature never once succeeded. Sync only -- adds missing foreign keys and treats a same-named constraint as present, so -- the wrong one has to go before the corrected entity can be built. -- Guarded on confrelid so this is a no-op once the constraint is correct. DO $$ BEGIN IF to_regclass('rooms') IS NOT NULL AND to_regclass('dir') IS NOT NULL AND EXISTS ( SELECT 1 FROM pg_constraint WHERE conname = 'fk-rooms-parent_id' AND conrelid = to_regclass('rooms') AND confrelid <> to_regclass('dir') ) THEN ALTER TABLE rooms DROP CONSTRAINT "fk-rooms-parent_id"; -- Defensive: anything the old constraint let through cannot be a dir -- node, so it is not a valid folder. Reset it instead of letting the -- corrected foreign key fail to build. UPDATE rooms SET parent_id = NULL WHERE parent_id IS NOT NULL AND NOT EXISTS (SELECT 1 FROM dir WHERE dir.id = rooms.parent_id); END IF; END $$; DROP TABLE IF EXISTS pipeline_run; -- idempotent column adds for phase-2 model changes (sync layer cannot -- express NOT NULL DEFAULT additions on populated tables). -- Guarded with to_regclass: on a fresh database the tables do not exist yet -- (they are created by the registry sync right after, with these columns -- already included), so the ALTERs must be skipped instead of failing. DO $$ BEGIN IF to_regclass('room_members') IS NOT NULL THEN ALTER TABLE room_members ADD COLUMN IF NOT EXISTS role SMALLINT NOT NULL DEFAULT 0; END IF; END $$; DO $$ BEGIN IF to_regclass('issue') IS NOT NULL THEN ALTER TABLE issue ADD COLUMN IF NOT EXISTS priority SMALLINT NOT NULL DEFAULT 0; ALTER TABLE issue ADD COLUMN IF NOT EXISTS due_at TIMESTAMP WITH TIME ZONE; END IF; END $$; DO $$ BEGIN IF to_regclass('user_access_key') IS NOT NULL THEN ALTER TABLE user_access_key ADD COLUMN IF NOT EXISTS expires_at TIMESTAMP WITH TIME ZONE; ALTER TABLE user_access_key ADD COLUMN IF NOT EXISTS last_used_at TIMESTAMP WITH TIME ZONE; END IF; END $$; DO $$ BEGIN IF to_regclass('user_preferences') IS NOT NULL THEN ALTER TABLE user_preferences ADD COLUMN IF NOT EXISTS board_view_mode VARCHAR NOT NULL DEFAULT 'board'; END IF; END $$; DO $$ BEGIN IF to_regclass('chat_message_reminder') IS NOT NULL THEN ALTER TABLE chat_message_reminder ADD COLUMN IF NOT EXISTS note TEXT; END IF; END $$; -- pull_request domain (PR API): the sync layer cannot relax NOT NULL nor add -- NOT NULL DEFAULT columns on populated tables, so both are applied here. DO $$ BEGIN IF to_regclass('pull_request') IS NOT NULL THEN ALTER TABLE pull_request ALTER COLUMN issue_id DROP NOT NULL; ALTER TABLE pull_request ADD COLUMN IF NOT EXISTS head_repo_id UUID; ALTER TABLE pull_request ADD COLUMN IF NOT EXISTS head_label VARCHAR; ALTER TABLE pull_request ADD COLUMN IF NOT EXISTS is_draft BOOLEAN NOT NULL DEFAULT false; ALTER TABLE pull_request ADD COLUMN IF NOT EXISTS merge_method SMALLINT NOT NULL DEFAULT 0; ALTER TABLE pull_request ADD COLUMN IF NOT EXISTS squash_commit_sha VARCHAR; ALTER TABLE pull_request ADD COLUMN IF NOT EXISTS auto_merge BOOLEAN NOT NULL DEFAULT false; END IF; END $$; DO $$ BEGIN IF to_regclass('pull_request_comment') IS NOT NULL THEN ALTER TABLE pull_request_comment ALTER COLUMN commit_sha DROP NOT NULL; ALTER TABLE pull_request_comment ALTER COLUMN path DROP NOT NULL; ALTER TABLE pull_request_comment ADD COLUMN IF NOT EXISTS parent_comment_id UUID; ALTER TABLE pull_request_comment ADD COLUMN IF NOT EXISTS original_commit_sha VARCHAR; ALTER TABLE pull_request_comment ADD COLUMN IF NOT EXISTS side VARCHAR; ALTER TABLE pull_request_comment ADD COLUMN IF NOT EXISTS original_line INTEGER; ALTER TABLE pull_request_comment ADD COLUMN IF NOT EXISTS resolved_at TIMESTAMP WITH TIME ZONE; ALTER TABLE pull_request_comment ADD COLUMN IF NOT EXISTS resolved_by UUID; END IF; END $$; DO $$ BEGIN IF to_regclass('repo_branch_protection_policy') IS NOT NULL THEN ALTER TABLE repo_branch_protection_policy ADD COLUMN IF NOT EXISTS required_status_contexts VARCHAR NOT NULL DEFAULT ''; END IF; END $$; -- 2026-10-31: local users table keeps only KenApi-owned settings. This column -- must exist before SeaORM sync adds it as NOT NULL on an existing table. ALTER TABLE IF EXISTS users ADD COLUMN IF NOT EXISTS local_avatar_url text NOT NULL DEFAULT '';