fix(#2279): idempotent column-guard migrations, drop invalid ADD COLUMN IF NOT EXISTS

This commit is contained in:
damocles 2026-07-10 16:51:50 +02:00 committed by mara
commit dfbf198ef3
5 changed files with 188 additions and 121 deletions

View file

@ -19,6 +19,8 @@ use hive_sh4re::wire_time::now_unix;
use rusqlite::{Connection, OptionalExtension, params};
use serde::Serialize;
use crate::db::Migration;
const SCHEMA: &str = r"
CREATE TABLE IF NOT EXISTS operator_questions (
id INTEGER PRIMARY KEY AUTOINCREMENT,
@ -37,16 +39,25 @@ CREATE INDEX IF NOT EXISTS idx_operator_questions_pending
/// `"operator_questions"`). Legacy databases are detected via the `target`
/// column — the last column added before versioning — and fast-forwarded
/// past all known migrations.
const MIGRATIONS: &[&str] = &[
const MIGRATIONS: &[Migration] = &[
// v1: `multi` — checkbox-style multi-option questions.
"ALTER TABLE operator_questions ADD COLUMN IF NOT EXISTS \
Migration {
sql: "ALTER TABLE operator_questions ADD COLUMN \
multi INTEGER NOT NULL DEFAULT 0",
adds_column: Some(("operator_questions", "multi")),
},
// v2: `deadline_at` — optional TTL after which the watchdog auto-resolves.
"ALTER TABLE operator_questions ADD COLUMN IF NOT EXISTS deadline_at INTEGER",
Migration {
sql: "ALTER TABLE operator_questions ADD COLUMN deadline_at INTEGER",
adds_column: Some(("operator_questions", "deadline_at")),
},
// v3: `target` — recipient of the question. NULL = operator (back-compat
// default); non-null = peer-to-peer question. Dashboard's `pending()`
// filters on `target IS NULL` so peer questions never leak to the operator.
"ALTER TABLE operator_questions ADD COLUMN IF NOT EXISTS target TEXT",
Migration {
sql: "ALTER TABLE operator_questions ADD COLUMN target TEXT",
adds_column: Some(("operator_questions", "target")),
},
];
#[derive(Debug, Clone, Serialize)]
@ -80,12 +91,7 @@ impl OperatorQuestions {
let conn = crate::db::open(path, "operator_questions")?;
conn.execute_batch(SCHEMA)
.context("apply operator_questions schema")?;
crate::db::apply_versioned_migrations(
&conn,
"operator_questions",
("operator_questions", "target"),
MIGRATIONS,
)?;
crate::db::apply_versioned_migrations(&conn, "operator_questions", MIGRATIONS)?;
Ok(Self {
conn: Mutex::new(conn),
})