import type { SQLiteDatabase } from 'expo-sqlite'; import { CREATE_MESSAGES_TABLE, CREATE_MESSAGES_INDEXES, MIGRATE_MESSAGES_TABLE, } from './MessageSchema'; import { CREATE_CONVERSATIONS_TABLE, CREATE_CONVERSATION_CACHE_TABLE, CREATE_CONVERSATION_LIST_CACHE_TABLE, MIGRATE_CONVERSATIONS_TABLE, } from './ConversationSchema'; import { CREATE_USERS_TABLE, CREATE_CURRENT_USER_CACHE_TABLE, } from './UserSchema'; import { CREATE_GROUPS_TABLE, CREATE_GROUP_MEMBERS_TABLE, CREATE_GROUP_INDEXES, } from './GroupSchema'; import { CREATE_POSTS_CACHE_TABLE, } from './PostSchema'; /** * 迁移版本号管理(对标 expo-sqlite 官方推荐用 PRAGMA user_version)。 * * 设计: * - version 0:基线(建表 + 建索引),所有老库都已执行过(靠 CREATE IF NOT EXISTS 幂等) * - version 1:列级迁移(补 seq/status/segments/lastSeq 列),即原 MIGRATE_* 函数 * * 向后兼容:老库 user_version 默认为 0,会重新跑 version 1 的列迁移, * 但 MIGRATE_* 靠 PRAGMA table_info 列检查幂等,重复执行无副作用。 * * 扩展方式(未来破坏性迁移):在 MIGRATIONS 注册表新增 version 2/3/..., * 每个 step 靠 transaction 包裹保证原子性(失败回滚,不污染 user_version)。 */ export const CURRENT_SCHEMA_VERSION = 1; /** 读取当前 user_version */ async function getUserVersion(db: SQLiteDatabase): Promise { const result = await db.getFirstAsync<{ user_version: number }>('PRAGMA user_version'); return result?.user_version ?? 0; } /** 设置 user_version */ async function setUserVersion(db: SQLiteDatabase, version: number): Promise { await execWithRetry(db, `PRAGMA user_version = ${version};`); } /** 版本化迁移注册表:key = 目标版本,value = 该版本要执行的迁移逻辑 */ const MIGRATIONS: Map Promise> = new Map([ // version 1:列级迁移(补 seq/status/segments/lastSeq 列) // 历史背景:最初表结构不含这些列,靠 MIGRATE_* 增量 ALTER。现在纳入版本管理。 [1, async (db: SQLiteDatabase) => { await MIGRATE_MESSAGES_TABLE(db); await MIGRATE_CONVERSATIONS_TABLE(db); }], ]); async function execWithRetry(db: SQLiteDatabase, sql: string, maxRetries = 5): Promise { for (let i = 0; i < maxRetries; i++) { try { await db.execAsync(sql); return; } catch (error) { const msg = String(error); if (msg.includes('database is locked') && i < maxRetries - 1) { const delay = 200 * (i + 1); console.warn(`[MigrationManager] 数据库锁定,${delay}ms 后重试 (${i + 1}/${maxRetries})`); await new Promise(r => setTimeout(r, delay)); continue; } throw error; } } } /** * 执行数据库迁移。 * * 流程: * 1. 设置 busy_timeout * 2. 基线建表 + 建索引(version 0,CREATE IF NOT EXISTS 幂等,每次都执行以保证新表存在) * 3. 按 user_version 顺序执行版本化迁移,每步成功后递增 user_version */ export async function runMigrations(db: SQLiteDatabase): Promise { await execWithRetry(db, 'PRAGMA busy_timeout = 10000;'); // ---- 基线:version 0(建表 + 建索引)---- // 保留每次执行,确保新库/损坏恢复时表结构就位;对老库靠 IF NOT EXISTS 幂等。 await execWithRetry(db, CREATE_MESSAGES_TABLE); await execWithRetry(db, CREATE_CONVERSATIONS_TABLE); await execWithRetry(db, CREATE_CONVERSATION_CACHE_TABLE); await execWithRetry(db, CREATE_CONVERSATION_LIST_CACHE_TABLE); await execWithRetry(db, CREATE_USERS_TABLE); await execWithRetry(db, CREATE_CURRENT_USER_CACHE_TABLE); await execWithRetry(db, CREATE_GROUPS_TABLE); await execWithRetry(db, CREATE_GROUP_MEMBERS_TABLE); await execWithRetry(db, CREATE_POSTS_CACHE_TABLE); for (const indexSql of CREATE_MESSAGES_INDEXES) { await execWithRetry(db, indexSql); } for (const indexSql of CREATE_GROUP_INDEXES) { await execWithRetry(db, indexSql); } // ---- 版本化迁移:version 1 ~ CURRENT_SCHEMA_VERSION ---- const currentVersion = await getUserVersion(db); // 当前版本已是最新(含未来更高版本),跳过 if (currentVersion >= CURRENT_SCHEMA_VERSION) { return; } // 从 currentVersion+1 开始,逐版本执行迁移 for (let target = currentVersion + 1; target <= CURRENT_SCHEMA_VERSION; target++) { const migration = MIGRATIONS.get(target); if (!migration) { console.warn(`[MigrationManager] 未找到 version ${target} 的迁移逻辑,跳过`); continue; } try { await migration(db); // 每个版本迁移成功后立即持久化 user_version, // 即使后续版本失败,已完成的版本不会重复执行 await setUserVersion(db, target); } catch (error) { console.error(`[MigrationManager] 迁移到 version ${target} 失败:`, error); // 不抛出致命错误以避免阻塞 App 启动;降级运行(表可能缺列,MIGRATE_* 内部会兜底) // 但记录版本未推进,下次启动会重试该版本 break; } } }