import type Database from 'better-sqlite3'; /** Keep metadata ahead of overflow pages, preserving IDs and all references. */ export function migrateVersionLayout(handle: Database.Database): void { const columns = handle.pragma('table_info(script_versions)') as { name: string }[]; if (!columns.some((column) => column.name === 'content') || columns.at(-1)?.name === 'content') return; const { sql } = handle .prepare("SELECT sql FROM sqlite_master WHERE name='script_versions'") .get() as { sql: string }; const content = /\bcontent\s+BLOB\s+NOT\s+NULL\s*,/i; if (!content.test(sql)) throw new Error('Unrecognized script version content layout'); const replacement = sql .replace(content, '') .replace(/,\s*UNIQUE\s*\(/i, ', content BLOB NOT NULL, UNIQUE(') .replace('script_versions', 'script_versions_metadata'); const dependents = handle .prepare( "SELECT sql FROM sqlite_master WHERE tbl_name='script_versions' AND type IN ('index','trigger') AND sql IS NOT NULL" ) .all() as { sql: string }[]; const sequence = handle .prepare("SELECT seq FROM sqlite_sequence WHERE name='script_versions'") .get() as { seq: number } | undefined; handle.pragma('foreign_keys = OFF'); try { handle.transaction(() => { handle.exec(replacement); const names = columns.map((column) => `"${column.name}"`).join(','); handle.exec(`INSERT INTO script_versions_metadata (${names}) SELECT ${names} FROM script_versions; DROP TABLE script_versions; ALTER TABLE script_versions_metadata RENAME TO script_versions;`); if (sequence) handle .prepare( "UPDATE sqlite_sequence SET seq=MAX(seq,?) WHERE name='script_versions'" ) .run(sequence.seq); for (const dependent of dependents) handle.exec(dependent.sql); if ((handle.pragma('foreign_key_check') as unknown[]).length) throw new Error('Version layout migration found broken references'); })(); } finally { handle.pragma('foreign_keys = ON'); } }