From 12f7d2b484ccee64caf2855cb5f9ed79f2497c8d Mon Sep 17 00:00:00 2001 From: diegosouzapw Date: Sat, 28 Feb 2026 00:54:07 -0300 Subject: [PATCH] fix: preserve database data on upgrade when old schema_migrations exists (#146) Previously, the upgrade detection logic renamed the entire DB file when it found a schema_migrations table (from older versions), causing data loss. Now checks if the DB actually contains data (provider_connections) before deciding to rename. If data exists, drops only the old migration tracking table and lets the new CREATE TABLE IF NOT EXISTS schema take over. --- src/lib/db/core.ts | 52 ++++++++++++++++++++++++++++++++++++---------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/src/lib/db/core.ts b/src/lib/db/core.ts index 10c133db42..a9d2a5de64 100644 --- a/src/lib/db/core.ts +++ b/src/lib/db/core.ts @@ -322,28 +322,58 @@ export function getDbInstance() { return _db; } - // Detect and replace old incompatible schema + // Detect and handle old schema format — preserve data when possible (#146) if (fs.existsSync(SQLITE_FILE)) { try { const probe = new Database(SQLITE_FILE, { readonly: true }); const hasOldSchema = probe .prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='schema_migrations'") .get(); - probe.close(); if (hasOldSchema) { - const oldPath = SQLITE_FILE + ".old-schema"; - console.log( - `[DB] Old incompatible schema detected — renaming to ${path.basename(oldPath)}` - ); - fs.renameSync(SQLITE_FILE, oldPath); - for (const ext of ["-wal", "-shm"]) { + // Check if the DB has actual data we should preserve + let hasData = false; + try { + const count = probe.prepare("SELECT COUNT(*) as c FROM provider_connections").get(); + hasData = count && count.c > 0; + } catch { + // Table might not exist at all — truly incompatible + } + probe.close(); + + if (hasData) { + // Data exists — preserve it! Just drop the old migration tracking table + // and let our new migration system (CREATE TABLE IF NOT EXISTS) take over + console.log( + `[DB] Old schema_migrations table found but data exists — preserving data (#146)` + ); + const fixDb = new Database(SQLITE_FILE); try { - if (fs.existsSync(SQLITE_FILE + ext)) fs.unlinkSync(SQLITE_FILE + ext); - } catch { - /* ok */ + fixDb.exec("DROP TABLE IF EXISTS schema_migrations"); + // Clean up WAL/SHM files that might be stale + fixDb.pragma("wal_checkpoint(TRUNCATE)"); + } catch (e) { + console.warn("[DB] Could not clean up old schema table:", e.message); + } finally { + fixDb.close(); + } + } else { + // No data — safe to rename and start fresh + const oldPath = SQLITE_FILE + ".old-schema"; + console.log( + `[DB] Old incompatible schema detected (empty) — renaming to ${path.basename(oldPath)}` + ); + fs.renameSync(SQLITE_FILE, oldPath); + for (const ext of ["-wal", "-shm"]) { + try { + if (fs.existsSync(SQLITE_FILE + ext)) fs.unlinkSync(SQLITE_FILE + ext); + } catch { + /* ok */ + } } } + } else { + probe.close(); } } catch (e) { console.warn("[DB] Could not probe existing DB, will create fresh:", e.message);