diff --git a/src/blackboard/db.py b/src/blackboard/db.py index 934866c..8a2ab8a 100644 --- a/src/blackboard/db.py +++ b/src/blackboard/db.py @@ -36,6 +36,78 @@ def _migrate_v261(conn: sqlite3.Connection) -> None: pass +def _migrate_v27(conn: sqlite3.Connection) -> None: + """v2.7 数据迁移:cards 表 + 所有表加 card_id/stage""" + # 1. cards 表(CREATE IF NOT EXISTS 已处理) + for stmt in _CARDS_SCHEMA: + try: + conn.execute(stmt) + except sqlite3.OperationalError: + pass + + # 2. 所有表加 card_id + for table in ("tasks", "outputs", "reviews", "comments", "events", + "task_attempts", "routing_decisions"): + _safe_add_column(conn, table, "card_id", "TEXT") + + # 3. tasks 加 stage + _safe_add_column(conn, "tasks", "stage", "TEXT") + + # 4. card_id 索引 + for table in ("tasks", "events"): + try: + conn.execute( + f"CREATE INDEX IF NOT EXISTS idx_{table}_card ON {table}(card_id)") + except sqlite3.OperationalError: + pass + + # 5. 回填 card_id = 'default'(分批,避免大表锁太久) + for table in ("tasks", "outputs", "reviews", "comments", "events", + "task_attempts", "routing_decisions"): + try: + row = conn.execute( + f"SELECT COUNT(*) as cnt FROM {table} WHERE card_id IS NULL" + ).fetchone() + if row and row["cnt"] > 0: + # 分批更新,每批 500 行 + _batch_update_card_id(conn, table, "default", batch_size=500) + except sqlite3.OperationalError: + pass # 表不存在则跳过 + + # 6. 创建 default Card(如果不存在) + try: + existing = conn.execute( + "SELECT id FROM cards WHERE id='default'" + ).fetchone() + if not existing: + conn.execute( + "INSERT INTO cards (id, name, status, card_type, stages_json, labels_json, created_at) " + "VALUES ('default', 'Default', 'active', 'default', '[]', '[]', datetime('now'))" + ) + except sqlite3.OperationalError: + pass # cards 表不存在则跳过(不应该发生) + + +def _batch_update_card_id(conn: sqlite3.Connection, table: str, + card_id: str, batch_size: int = 500) -> None: + """分批更新 card_id,避免大表长时间锁""" + while True: + conn.execute( + f"UPDATE {table} SET card_id=? WHERE rowid IN (" + f"SELECT rowid FROM {table} WHERE card_id IS NULL LIMIT {batch_size})", + (card_id,), + ) + if conn.total_changes == 0: + break + conn.commit() + # 检查是否还有剩余 + row = conn.execute( + f"SELECT COUNT(*) as cnt FROM {table} WHERE card_id IS NULL" + ).fetchone() + if not row or row["cnt"] == 0: + break + + def _safe_add_column(conn: sqlite3.Connection, table: str, column: str, col_type: str) -> None: """安全添加列(已存在则跳过)"""