-- Run this once on an existing MySQL database before deploying the -- single-version SOP application. It is intentionally explicit: the Go -- service only performs the data-preserving backfill and does not drop data. SET @sql = ( SELECT IF(COUNT(*) = 0, 'ALTER TABLE sops ADD COLUMN start_node_key VARCHAR(64) NOT NULL DEFAULT ''start''', 'SELECT 1') FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'sops' AND column_name = 'start_node_key' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; UPDATE sops s JOIN ( SELECT sop_id, MAX(id) AS version_id FROM sop_versions WHERE status = 'published' GROUP BY sop_id ) current ON current.sop_id = s.id JOIN sop_versions sv ON sv.id = current.version_id SET s.start_node_key = COALESCE(NULLIF(sv.start_node_key, ''), NULLIF(s.start_node_key, ''), 'start'); SET @sql = ( SELECT IF(COUNT(*) = 0, 'ALTER TABLE sop_nodes ADD COLUMN sop_id BIGINT UNSIGNED NULL', 'SELECT 1') FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'sop_nodes' AND column_name = 'sop_id' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql = ( SELECT IF(COUNT(*) = 0, 'ALTER TABLE sop_edges ADD COLUMN sop_id BIGINT UNSIGNED NULL', 'SELECT 1') FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'sop_edges' AND column_name = 'sop_id' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql = ( SELECT IF(COUNT(*) = 0, 'ALTER TABLE sop_runs ADD COLUMN sop_id BIGINT UNSIGNED NULL', 'SELECT 1') FROM information_schema.columns WHERE table_schema = DATABASE() AND table_name = 'sop_runs' AND column_name = 'sop_id' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; UPDATE sop_nodes n JOIN sop_versions sv ON sv.id = n.sop_version_id SET n.sop_id = sv.sop_id WHERE n.sop_id IS NULL; UPDATE sop_edges e JOIN sop_versions sv ON sv.id = e.sop_version_id SET e.sop_id = sv.sop_id WHERE e.sop_id IS NULL; UPDATE sop_runs r JOIN sop_versions sv ON sv.id = r.sop_version_id SET r.sop_id = sv.sop_id WHERE r.sop_id IS NULL; -- A single-version SOP can only have one graph. Keep the latest published -- version's nodes and edges; remove historical graph rows after the backup. DELETE n FROM sop_nodes n JOIN sop_versions sv ON sv.id = n.sop_version_id JOIN ( SELECT sop_id, MAX(id) AS version_id FROM sop_versions WHERE status = 'published' GROUP BY sop_id ) current ON current.sop_id = sv.sop_id WHERE sv.id <> current.version_id; DELETE e FROM sop_edges e JOIN sop_versions sv ON sv.id = e.sop_version_id JOIN ( SELECT sop_id, MAX(id) AS version_id FROM sop_versions WHERE status = 'published' GROUP BY sop_id ) current ON current.sop_id = sv.sop_id WHERE sv.id <> current.version_id; ALTER TABLE sop_nodes MODIFY COLUMN sop_id BIGINT UNSIGNED NOT NULL; ALTER TABLE sop_edges MODIFY COLUMN sop_id BIGINT UNSIGNED NOT NULL; ALTER TABLE sop_runs MODIFY COLUMN sop_id BIGINT UNSIGNED NOT NULL; SET @sql = ( SELECT IF(COUNT(*) = 0, 'ALTER TABLE sop_nodes ADD INDEX idx_sop_nodes_sop_id (sop_id)', 'SELECT 1') FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'sop_nodes' AND index_name = 'idx_sop_nodes_sop_id' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql = ( SELECT IF(COUNT(*) = 0, 'ALTER TABLE sop_edges ADD INDEX idx_sop_edges_sop_id (sop_id)', 'SELECT 1') FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'sop_edges' AND index_name = 'idx_sop_edges_sop_id' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; SET @sql = ( SELECT IF(COUNT(*) = 0, 'ALTER TABLE sop_runs ADD INDEX idx_sop_runs_sop_id (sop_id)', 'SELECT 1') FROM information_schema.statistics WHERE table_schema = DATABASE() AND table_name = 'sop_runs' AND index_name = 'idx_sop_runs_sop_id' ); PREPARE stmt FROM @sql; EXECUTE stmt; DEALLOCATE PREPARE stmt; -- Verify the backfill before the destructive cleanup below. SELECT COUNT(*) AS nodes_without_sop FROM sop_nodes WHERE sop_id IS NULL; SELECT COUNT(*) AS edges_without_sop FROM sop_edges WHERE sop_id IS NULL; SELECT COUNT(*) AS runs_without_sop FROM sop_runs WHERE sop_id IS NULL; -- After the three counts above are all zero, remove the old version fields. -- The old columns/table are intentionally retained by this backfill step. -- Run 20260819_drop_sop_versions.sql only after all three counts above are 0.