fix(text-cache): apply model version migration on existing dbs

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
MythEclipse
2026-06-02 13:05:05 +07:00
co-authored by Claude Opus 4.8
parent e5673c2bbd
commit e584a7941e
2 changed files with 62 additions and 26 deletions
@@ -8,6 +8,13 @@
"when": 1780329073891, "when": 1780329073891,
"tag": "0000_tricky_mysterio", "tag": "0000_tricky_mysterio",
"breakpoints": true "breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1780329074000,
"tag": "0001_add_model_version_to_cache",
"breakpoints": true
} }
] ]
} }
@@ -1,7 +1,7 @@
import "dotenv/config"; import "dotenv/config";
import type { PoolClient } from "pg";
import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres"; import { drizzle as drizzlePostgres } from "drizzle-orm/node-postgres";
import { migrate as migratePostgres } from "drizzle-orm/node-postgres/migrator"; import { migrate as migratePostgres } from "drizzle-orm/node-postgres/migrator";
import type { PoolClient } from "pg";
import { createChildLogger } from "../../shared/logger/logger.js"; import { createChildLogger } from "../../shared/logger/logger.js";
import { import {
closeDatabase, closeDatabase,
@@ -15,27 +15,59 @@ const MIGRATION_LOCK_KEY_1 = 2026;
const MIGRATION_LOCK_KEY_2 = 531; const MIGRATION_LOCK_KEY_2 = 531;
/** /**
* Check if all schema tables already exist in the database. * Seed Drizzle's __drizzle_migrations tracking table for pre-existing databases
* If they do, the database was likely created by a previous deployment * that were created manually or by an earlier migration system (e.g., the old
* and migration is not needed. * checkSchemaExists short-circuit). Without this, Drizzle attempts to re-create
* all tables from 0000 and fails with "relation already exists".
*/ */
async function checkSchemaExists(client: PoolClient): Promise<boolean> { async function seedDrizzleHistory(client: PoolClient): Promise<void> {
try { const exists = await client.query(`
const result = await client.query(` SELECT EXISTS (
SELECT COUNT(*) as count SELECT FROM information_schema.tables
FROM information_schema.tables WHERE table_name = '__drizzle_migrations'
WHERE table_schema = 'public' )
AND table_name IN ( `);
'ai_analysis_runs', 'attachments', 'message_reviews', const drizzleTableExists = exists.rows[0]?.exists === true;
'messages', 'moderation_actions', 'muxer_jobs', if (drizzleTableExists) {
'retention_policies', 'text_analysis_cache', 'ui_state', return; // already seeded, nothing to do
'voice_recordings'
)
`);
return result.rows[0]?.count === "10";
} catch {
return false;
} }
// Check whether the app tables pre-exist (old ./migrations/ SQL or manual creation).
const hasTextCache = await client.query(`
SELECT EXISTS (
SELECT FROM information_schema.columns
WHERE table_name = 'text_analysis_cache' AND column_name = 'text'
)
`);
if (hasTextCache.rows[0]?.exists !== true) {
return; // brand-new database, let Drizzle handle everything
}
logger.info(
"Seeding Drizzle migration history — marking 0000 as already applied on this pre-existing database",
);
// Create the Drizzle tracking table and insert a row for migration 0000.
await client.query(`
CREATE TABLE IF NOT EXISTS "__drizzle_migrations" (
id SERIAL PRIMARY KEY,
hash text NOT NULL,
created_at bigint
)
`);
// Drizzle's __drizzle_migrations table has no UNIQUE(hash)
// constraint, so check manually before inserting.
const alreadySeeded = await client.query(
`SELECT 1 FROM "__drizzle_migrations" WHERE hash = $1 LIMIT 1`,
["0000_tricky_mysterio"],
);
if (alreadySeeded.rows.length === 0) {
await client.query(
`INSERT INTO "__drizzle_migrations" (hash, created_at) VALUES ($1, $2)`,
["0000_tricky_mysterio", Date.now()],
);
}
logger.info("Drizzle history seeded — 0000 marked applied");
} }
export async function runMigrations(): Promise<void> { export async function runMigrations(): Promise<void> {
@@ -53,12 +85,9 @@ export async function runMigrations(): Promise<void> {
]); ]);
try { try {
// If all schema tables already exist, skip migration // Seed history for pre-existing databases so Drizzle only applies
const schemaExists = await checkSchemaExists(client); // new (pending) migrations — it is idempotent after that.
if (schemaExists) { await seedDrizzleHistory(client);
logger.info("Schema tables already exist; skipping migration");
return;
}
await migratePostgres(db, { await migratePostgres(db, {
migrationsFolder: "./drizzle/migrations", migrationsFolder: "./drizzle/migrations",