mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-13 18:32:12 +03:00
fix(db): heal legacy call_logs columns before the inline schema indexes
A database created before migration 007 still owns a `call_logs` table without `request_type`. `CREATE TABLE IF NOT EXISTS call_logs (...)` in SCHEMA_SQL is a no-op there, but the index statements right after it are not: `idx_cl_request_provider` (added in #12832) indexes `call_logs(request_type, provider)`, so `db.exec(SCHEMA_SQL)` aborted the whole boot with `SqliteError: no such column: request_type` — before `ensureCallLogsColumns()`, the very code that adds the column, ever ran. Upgrading with an old database broke at startup, not only in tests. Split the inline schema by statement kind and reorder the boot path to create tables -> heal legacy columns -> create indexes. SCHEMA_SQL stays the single source of truth (the split is derived from it), so the in-memory path and every fresh install keep the exact same schema. Guarded by the existing test "legacy call_logs schemas are upgraded before combo target indexes are created" (tests/unit/db-core-init.test.ts), red before this change and green after: 14/14.
This commit is contained in:
@@ -519,6 +519,29 @@ const SCHEMA_SQL = `
|
||||
CREATE INDEX IF NOT EXISTS idx_quota_snapshots_created_at ON quota_snapshots(created_at);
|
||||
`;
|
||||
|
||||
// `CREATE TABLE IF NOT EXISTS` is a no-op against a legacy database that already owns the
|
||||
// table with an older column set — but the `CREATE INDEX` statements that follow it are
|
||||
// not: they still reference columns the ensure*Columns() healers have yet to backfill, so
|
||||
// running the whole schema in one exec aborts startup with "no such column". That is how
|
||||
// the composite idx_cl_request_provider index (#12832) broke booting on a pre-007
|
||||
// `call_logs` lineage. Split the inline schema so the boot order can be: create tables →
|
||||
// heal legacy columns → create indexes.
|
||||
function splitSchemaStatements(schemaSql: string): { tables: string; indexes: string } {
|
||||
const tables: string[] = [];
|
||||
const indexes: string[] = [];
|
||||
for (const rawStatement of schemaSql.split(";")) {
|
||||
const statement = rawStatement.trim();
|
||||
if (!statement) continue;
|
||||
// Classify on the first SQL keyword, ignoring any leading `--` comment lines.
|
||||
const sql = statement.replace(/^(?:[ \t]*--[^\n]*\n)+/, "").trimStart();
|
||||
(/^CREATE\s+(?:UNIQUE\s+)?INDEX\b/i.test(sql) ? indexes : tables).push(`${statement};`);
|
||||
}
|
||||
return { tables: tables.join("\n"), indexes: indexes.join("\n") };
|
||||
}
|
||||
|
||||
const { tables: SCHEMA_TABLES_SQL, indexes: SCHEMA_INDEXES_SQL } =
|
||||
splitSchemaStatements(SCHEMA_SQL);
|
||||
|
||||
// ──────────────── Singleton DB Instance ────────────────
|
||||
// Use globalThis to survive Next.js dev HMR module re-evaluation.
|
||||
// Module-level `let` resets on every webpack recompile, causing connection leaks.
|
||||
@@ -1254,10 +1277,15 @@ export function getDbInstance(): SqliteDatabase {
|
||||
db.pragma("synchronous = NORMAL");
|
||||
db.pragma(`cache_size = -${DEFAULT_DATABASE_SETTINGS.optimization.cacheSize}`);
|
||||
db.pragma("temp_store = MEMORY");
|
||||
db.exec(SCHEMA_SQL);
|
||||
// Tables first, then the legacy-column healers, and only then the indexes: an upgraded
|
||||
// database can already own call_logs/usage_history/provider_connections with an older
|
||||
// column set, where the CREATE TABLE is a no-op but the indexes still reference columns
|
||||
// the healers below are the ones adding.
|
||||
db.exec(SCHEMA_TABLES_SQL);
|
||||
ensureProviderConnectionsColumns(db);
|
||||
ensureUsageHistoryColumns(db);
|
||||
ensureCallLogsColumns(db);
|
||||
db.exec(SCHEMA_INDEXES_SQL);
|
||||
|
||||
// ── Versioned Migrations ──
|
||||
// Auto-seed 001 as applied (the inline SCHEMA_SQL already created these tables)
|
||||
|
||||
Reference in New Issue
Block a user