diff --git a/docs/DEBUG_DEATH_SUCCESSION_QUERIES.sql b/docs/DEBUG_DEATH_SUCCESSION_QUERIES.sql new file mode 100644 index 0000000..e55f2e7 --- /dev/null +++ b/docs/DEBUG_DEATH_SUCCESSION_QUERIES.sql @@ -0,0 +1,270 @@ +-- Debug-Queries: Tod -> Erbwechsel -> Cleanup +-- Nutzung: +-- psql ... -f docs/DEBUG_DEATH_SUCCESSION_QUERIES.sql +-- Ersetze zuerst: +-- :character_id (verstorbener Charakter) +-- :user_id (falukant_user.id des Spielers) +-- :heir_id (optional; geplanter Erbe) +-- +-- In psql z. B.: +-- \set character_id 123 +-- \set user_id 45 +-- \set heir_id 678 + +-- ============================================================ +-- 0) Vorab: Existenz / Ausgangszustand prüfen +-- ============================================================ + +SELECT c.id, c.user_id, c.health, c.updated_at +FROM falukant_data.character c +WHERE c.id = :character_id; + +SELECT fu.id, fu.user_id, fu.money, fu.certificate +FROM falukant_data.falukant_user fu +WHERE fu.id = :user_id; + +-- Mögliche Erben (wie QUERY_GET_HEIR priorisiert) +SELECT child_character_id, is_heir, updated_at +FROM falukant_data.child_relation +WHERE father_character_id = :character_id + OR mother_character_id = :character_id +ORDER BY (is_heir IS TRUE) DESC, updated_at DESC +LIMIT 20; + +-- Fallback-Erbe (entspricht QUERY_RANDOM_HEIR-Prinzip: nur Vorschau) +SELECT cr.child_character_id +FROM falukant_data.child_relation cr +JOIN falukant_data.character c ON c.id = cr.child_character_id +WHERE (cr.father_character_id = :character_id OR cr.mother_character_id = :character_id) + AND c.health > 0 +ORDER BY RANDOM() +LIMIT 1; + +-- ============================================================ +-- 1) Queries, die der Daemon im Todespfad nutzt (Vorschau) +-- Reihenfolge entspricht Worker-Logik. +-- ============================================================ + +-- 1.1 get_falukant_user_id +SELECT user_id +FROM falukant_data.character +WHERE id = :character_id; + +-- 1.2 get_heir (bevorzugt is_heir=true) +SELECT child_character_id +FROM falukant_data.child_relation +WHERE father_character_id = :character_id OR mother_character_id = :character_id +ORDER BY (is_heir IS TRUE) DESC, updated_at DESC +LIMIT 1; + +-- 1.3 random_heir (wenn 1.2 leer) +SELECT cr.child_character_id +FROM falukant_data.child_relation cr +JOIN falukant_data.character c ON c.id = cr.child_character_id +WHERE (cr.father_character_id = :character_id OR cr.mother_character_id = :character_id) + AND c.health > 0 +ORDER BY RANDOM() +LIMIT 1; + +-- 1.4 set_character_user (nur wenn Erbe vorhanden) +-- UPDATE falukant_data.character +-- SET user_id = :user_id, updated_at = NOW() +-- WHERE id = :heir_id; + +-- 1.5 Vermögensberechnung: Inputs +SELECT money FROM falukant_data.falukant_user WHERE id = :user_id; + +SELECT COALESCE(SUM(h.cost), 0) AS sum +FROM falukant_data.user_house uh +JOIN falukant_type.house h ON uh.house_type_id = h.id +WHERE uh.user_id = :user_id; + +SELECT COALESCE(SUM(b.base_cost), 0) AS sum +FROM falukant_data.branch br +JOIN falukant_type.branch b ON br.branch_type_id = b.id +WHERE br.falukant_user_id = :user_id; + +SELECT COALESCE(SUM(i.quantity * p.sell_cost), 0) AS sum +FROM falukant_data.inventory i +JOIN falukant_type.product p ON i.product_id = p.id +JOIN falukant_data.stock s ON i.stock_id = s.id +JOIN falukant_data.branch br ON s.branch_id = br.id +WHERE br.falukant_user_id = :user_id; + +SELECT COALESCE(SUM(remaining_amount), 0) AS sum +FROM falukant_data.credit +WHERE falukant_user_id = :user_id; + +SELECT COUNT(DISTINCT cr.child_character_id) AS cnt +FROM falukant_data.child_relation cr +JOIN falukant_data.character parent + ON (parent.id = cr.father_character_id OR parent.id = cr.mother_character_id) +WHERE parent.user_id = :user_id; + +-- 1.6 set_new_money (nur wenn du den errechneten Wert testen willst) +-- UPDATE falukant_data.falukant_user +-- SET money = '', updated_at = NOW() +-- WHERE id = :user_id; + +-- 1.7 delete_director +WITH deleted AS ( + DELETE FROM falukant_data.director + WHERE director_character_id = :character_id + RETURNING employer_user_id +) +SELECT * FROM deleted; + +-- 1.8 delete_relationship +WITH deleted AS ( + DELETE FROM falukant_data.relationship + WHERE character1_id = :character_id OR character2_id = :character_id + RETURNING CASE WHEN character1_id = :character_id THEN character2_id ELSE character1_id END AS related_character_id +) +SELECT c.user_id AS related_user_id +FROM deleted d +JOIN falukant_data.character c ON c.id = d.related_character_id; + +-- 1.9 delete_child_relation_by_parent +DELETE FROM falukant_data.child_relation +WHERE father_character_id = :character_id OR mother_character_id = :character_id; + +-- 1.10 delete_child_relation (als Kind) +WITH deleted AS ( + DELETE FROM falukant_data.child_relation + WHERE child_character_id = :character_id + RETURNING father_character_id, mother_character_id +) +SELECT cf.user_id AS father_user_id, cm.user_id AS mother_user_id +FROM deleted d +JOIN falukant_data.character cf ON cf.id = d.father_character_id +JOIN falukant_data.character cm ON cm.id = d.mother_character_id; + +-- 1.11 zusätzliche Cleanup-Queries +DELETE FROM falukant_data.knowledge +WHERE character_id = :character_id; + +DELETE FROM falukant_data.debtors_prism +WHERE character_id = :character_id; + +WITH removed AS ( + DELETE FROM falukant_data.political_office + WHERE character_id = :character_id + RETURNING character_id, office_type_id, region_id, created_at +), +archived_removed AS ( + INSERT INTO falukant_log.political_office_history + (character_id, office_type_id, region_id, start_date, end_date) + SELECT character_id, office_type_id, region_id, created_at, NOW() + FROM removed +), +affected AS ( + SELECT DISTINCT office_type_id, region_id + FROM removed +), +seats AS ( + SELECT pot.id AS office_type_id, rt.id AS region_id, pot.seats_per_region AS seats_total + FROM falukant_type.political_office_type pot + JOIN falukant_type.region rt ON pot.region_type = rt.label_tr + JOIN affected a ON a.office_type_id = pot.id AND a.region_id = rt.id +), +ranked AS ( + SELECT po.id, po.character_id, po.office_type_id, po.region_id, po.created_at, s.seats_total, + ROW_NUMBER() OVER ( + PARTITION BY po.office_type_id, po.region_id + ORDER BY po.created_at DESC + ) AS rn + FROM falukant_data.political_office po + JOIN seats s ON s.office_type_id = po.office_type_id AND s.region_id = po.region_id +), +to_delete AS ( + SELECT id, character_id, office_type_id, region_id, created_at + FROM ranked + WHERE rn > seats_total +), +archived_trim AS ( + INSERT INTO falukant_log.political_office_history + (character_id, office_type_id, region_id, start_date, end_date) + SELECT character_id, office_type_id, region_id, created_at, NOW() + FROM to_delete +) +DELETE FROM falukant_data.political_office +WHERE id IN (SELECT id FROM to_delete); + +-- election_candidate ist nicht in allen Bestands-DBs vorhanden: +SELECT EXISTS ( + SELECT 1 + FROM information_schema.tables + WHERE table_schema = 'falukant_data' + AND table_name = 'election_candidate' +) AS election_candidate_table_ready; + +-- Nur ausführen, wenn vorher TRUE: +-- DELETE FROM falukant_data.election_candidate +-- WHERE character_id = :character_id; + +-- 1.12 final delete_character +DELETE FROM falukant_data.character +WHERE id = :character_id; + +-- ============================================================ +-- 2) Nachkontrolle (muss leer / 0 sein) +-- ============================================================ + +SELECT 1 AS character_still_exists +FROM falukant_data.character +WHERE id = :character_id; + +SELECT COUNT(*) AS knowledge_left +FROM falukant_data.knowledge +WHERE character_id = :character_id; + +SELECT COUNT(*) AS debtors_prism_left +FROM falukant_data.debtors_prism +WHERE character_id = :character_id; + +SELECT COUNT(*) AS political_office_left +FROM falukant_data.political_office +WHERE character_id = :character_id; + +SELECT COUNT(*) AS election_candidate_left +FROM falukant_data.election_candidate +WHERE character_id = :character_id; + +SELECT * +FROM falukant_log.political_office_history +WHERE character_id = :character_id +ORDER BY end_date DESC +LIMIT 10; + +-- Neuer aktiver Charakter des Users: +SELECT c.id, c.user_id, c.health, c.updated_at +FROM falukant_data.character c +WHERE c.user_id = :user_id +ORDER BY c.updated_at DESC, c.id DESC +LIMIT 10; + +-- ============================================================ +-- 3) Unabhängig: completion_count-Fehler für Zertifikat +-- ============================================================ + +-- 3.1 Existiert Spalte? +SELECT column_name, data_type, is_nullable, column_default +FROM information_schema.columns +WHERE table_schema = 'falukant_log' + AND table_name = 'production' + AND column_name = 'completion_count'; + +-- 3.2 Sofort-Fix (idempotent), falls Spalte fehlt oder unvollständig: +ALTER TABLE falukant_log.production + ADD COLUMN IF NOT EXISTS completion_count integer; + +UPDATE falukant_log.production + SET completion_count = 1 + WHERE completion_count IS NULL; + +ALTER TABLE falukant_log.production + ALTER COLUMN completion_count SET DEFAULT 1, + ALTER COLUMN completion_count SET NOT NULL; + +-- 3.3 Optional: Migration historisch sauber markieren/prüfen +-- (abhängig davon, wie ihr Migrationsstände verwaltet) diff --git a/src/worker/events.rs b/src/worker/events.rs index 0344159..25240c8 100644 --- a/src/worker/events.rs +++ b/src/worker/events.rs @@ -48,6 +48,7 @@ use crate::worker::sql::{ QUERY_DELETE_CHILD_RELATION_BY_PARENT, QUERY_DELETE_DEBTORS_PRISM, QUERY_DELETE_CHARACTER, + QUERY_ELECTION_CANDIDATE_TABLE_READY, QUERY_DELETE_ELECTION_CANDIDATE, QUERY_DELETE_KNOWLEDGE, QUERY_DELETE_POLITICAL_OFFICE, @@ -1936,11 +1937,20 @@ impl EventsWorker { conn.prepare("delete_knowledge", QUERY_DELETE_KNOWLEDGE)?; conn.prepare("delete_debtors_prism", QUERY_DELETE_DEBTORS_PRISM)?; conn.prepare("delete_political_office", QUERY_DELETE_POLITICAL_OFFICE)?; - conn.prepare("delete_election_candidate", QUERY_DELETE_ELECTION_CANDIDATE)?; conn.execute("delete_knowledge", &[&character_id])?; conn.execute("delete_debtors_prism", &[&character_id])?; conn.execute("delete_political_office", &[&character_id])?; - conn.execute("delete_election_candidate", &[&character_id])?; + conn.prepare("election_candidate_table_ready", QUERY_ELECTION_CANDIDATE_TABLE_READY)?; + let election_ready_rows = conn.execute("election_candidate_table_ready", &[])?; + let election_ready = election_ready_rows + .first() + .and_then(|r| r.get("ready")) + .map(|v| v == "t" || v == "true") + .unwrap_or(false); + if election_ready { + conn.prepare("delete_election_candidate", QUERY_DELETE_ELECTION_CANDIDATE)?; + conn.execute("delete_election_candidate", &[&character_id])?; + } // 6) Charakter löschen conn.prepare("delete_character", QUERY_DELETE_CHARACTER)?; diff --git a/src/worker/sql.rs b/src/worker/sql.rs index 4af548a..7f4a860 100644 --- a/src/worker/sql.rs +++ b/src/worker/sql.rs @@ -2577,6 +2577,16 @@ pub const QUERY_DELETE_ELECTION_CANDIDATE: &str = r#" WHERE character_id = $1; "#; +/// Legacy-/Migrationsschutz: Tabelle existiert nicht in allen Umgebungen. +pub const QUERY_ELECTION_CANDIDATE_TABLE_READY: &str = r#" + SELECT EXISTS ( + SELECT 1 + FROM information_schema.tables + WHERE table_schema = 'falukant_data' + AND table_name = 'election_candidate' + ) AS ready; +"#; + pub const QUERY_GET_STOCK_TYPE_ID: &str = r#" SELECT id FROM falukant_type.stock WHERE label_tr = $1 LIMIT 1; "#; diff --git a/src/worker/user_character.rs b/src/worker/user_character.rs index efb4565..9cd43a8 100644 --- a/src/worker/user_character.rs +++ b/src/worker/user_character.rs @@ -54,6 +54,7 @@ use crate::worker::sql::{ QUERY_DELETE_DEBTORS_PRISM, QUERY_DELETE_POLITICAL_OFFICE, QUERY_DELETE_ELECTION_CANDIDATE, + QUERY_ELECTION_CANDIDATE_TABLE_READY, }; /// Vereinfachtes Abbild eines Characters aus `QUERY_GET_USERS_TO_UPDATE`. @@ -853,7 +854,6 @@ impl UserCharacterWorker { conn.prepare("delete_knowledge", QUERY_DELETE_KNOWLEDGE)?; conn.prepare("delete_debtors_prism", QUERY_DELETE_DEBTORS_PRISM)?; conn.prepare("delete_political_office", QUERY_DELETE_POLITICAL_OFFICE)?; - conn.prepare("delete_election_candidate", QUERY_DELETE_ELECTION_CANDIDATE)?; conn.prepare("insert_notification", QUERY_INSERT_NOTIFICATION)?; let dir_result = conn.execute("delete_director", &[&character_id])?; @@ -923,7 +923,17 @@ impl UserCharacterWorker { conn.execute("delete_knowledge", &[&character_id])?; conn.execute("delete_debtors_prism", &[&character_id])?; conn.execute("delete_political_office", &[&character_id])?; - conn.execute("delete_election_candidate", &[&character_id])?; + conn.prepare("election_candidate_table_ready", QUERY_ELECTION_CANDIDATE_TABLE_READY)?; + let election_ready_rows = conn.execute("election_candidate_table_ready", &[])?; + let election_ready = election_ready_rows + .first() + .and_then(|r| r.get("ready")) + .map(|v| v == "t" || v == "true") + .unwrap_or(false); + if election_ready { + conn.prepare("delete_election_candidate", QUERY_DELETE_ELECTION_CANDIDATE)?; + conn.execute("delete_election_candidate", &[&character_id])?; + } // Character selbst löschen conn.prepare(