import fs from 'node:fs'; import path from 'node:path'; import Database from 'better-sqlite3'; import { newDb } from 'pg-mem'; const tables = [ 'operation_groups', 'users', 'projects', 'collections', 'notes', 'images', 'annotations', 'work_comments', 'work_versions', 'review_events', 'sessions', 'customer_sessions', 'audit_logs', 'api_keys', 'storage_configs', ] as const; const booleanColumns: Record> = { users: new Set(['must_change_password']), projects: new Set(['customer_access_enabled']), }; const memory = newDb({ autoCreateForeignKeyIndices: true }); const adapter = memory.adapters.createPg(); const client = new adapter.Client(); const sqlite = new Database(path.resolve('data/app.db'), { readonly: true }); try { await client.connect(); const schema = fs.readFileSync(path.resolve('db/postgres/schema.sql'), 'utf8') .replace(/^BEGIN;|COMMIT;$/gm, '') .replace(/CREATE UNIQUE INDEX IF NOT EXISTS one_active_storage_config[^;]+;/, ''); await client.query(schema); for (const table of tables) { const exists = sqlite.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name=?").get(table); if (!exists) continue; const rows = sqlite.prepare(`SELECT * FROM "${table}" ORDER BY id`).all() as Record[]; for (const row of rows) { const columns = Object.keys(row); const values = columns.map((column) => booleanColumns[table]?.has(column) ? Boolean(row[column]) : row[column]); await client.query(`INSERT INTO "${table}" (${columns.map((column) => `"${column}"`).join(',')}) VALUES (${columns.map((_, index) => `$${index + 1}`).join(',')})`, values); } const pgCount = Number((await client.query(`SELECT COUNT(*)::int AS count FROM "${table}"`)).rows[0].count); if (pgCount !== rows.length) throw new Error(`${table} 行数不一致:SQLite=${rows.length}, PostgreSQL=${pgCount}`); } const current = (await client.query(`SELECT n.version_number,n.review_status,i.storage_provider FROM notes n JOIN images i ON i.note_id=n.id AND i.version_number=n.version_number WHERE n.id=1 LIMIT 1`)).rows[0]; if (!current || Number(current.version_number) < 1) throw new Error('作品版本关系未正确迁移'); const activeStorage = Number((await client.query("SELECT COUNT(*)::int AS count FROM storage_configs WHERE status='active'")).rows[0].count); if (activeStorage > 1) throw new Error('活动对象存储配置超过一个'); process.stdout.write(`PostgreSQL schema 与迁移映射验证通过:${tables.length} 张表,当前作品 V${current.version_number},存储=${current.storage_provider}\n`); } finally { sqlite.close(); await client.end(); }