52 lines
2.6 KiB
JavaScript
52 lines
2.6 KiB
JavaScript
'use strict';
|
|
|
|
// KB 版本查看/回滚:备份表命名 kb_docs_v<timestamp>,当前表为 kb_docs。
|
|
function quoteIdent(name) {
|
|
if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(name)) throw new Error(`invalid identifier: ${name}`);
|
|
return `"${name}"`;
|
|
}
|
|
|
|
async function listVersions(pool) {
|
|
const backups = await pool.query(
|
|
`SELECT tablename FROM pg_tables WHERE schemaname='public' AND tablename LIKE 'kb_docs_v%' ORDER BY tablename DESC`
|
|
);
|
|
const runs = await pool.query('SELECT * FROM kb_sync_runs ORDER BY id DESC LIMIT 20').catch(() => ({ rows: [] }));
|
|
return { backups: backups.rows.map((r) => r.tablename), runs: runs.rows };
|
|
}
|
|
|
|
async function rollback(pool, target) {
|
|
if (!/^kb_docs_v[a-zA-Z0-9_]+$/.test(String(target))) throw new Error(`invalid backup table: ${target}`);
|
|
const exists = await pool.query('SELECT to_regclass($1) IS NOT NULL AS ok', [`public.${target}`]);
|
|
if (!exists.rows[0].ok) throw new Error(`backup not found: ${target}`);
|
|
const hasCurrent = await pool.query("SELECT to_regclass('public.kb_docs') IS NOT NULL AS ok");
|
|
const stamp = new Date().toISOString().replace(/[-:TZ.]/g, '').slice(0, 14);
|
|
const currentBackup = `kb_docs_before_rollback_${stamp}`;
|
|
const client = await pool.connect();
|
|
try {
|
|
await client.query('BEGIN');
|
|
if (hasCurrent.rows[0].ok) {
|
|
const idx = await client.query("SELECT indexname FROM pg_indexes WHERE schemaname='public' AND tablename='kb_docs'");
|
|
await client.query(`ALTER TABLE kb_docs RENAME TO ${quoteIdent(currentBackup)}`);
|
|
for (const row of idx.rows) {
|
|
const newName = `${currentBackup}_${row.indexname.replace(/^kb_docs_/, '').replace(/_idx$/, '')}_idx`;
|
|
await client.query(`ALTER INDEX ${quoteIdent(row.indexname)} RENAME TO ${quoteIdent(newName)}`).catch(() => {});
|
|
}
|
|
}
|
|
await client.query(`ALTER TABLE ${quoteIdent(target)} RENAME TO kb_docs`);
|
|
const targetIdx = await client.query(`SELECT indexname FROM pg_indexes WHERE schemaname='public' AND tablename='kb_docs'`);
|
|
for (const row of targetIdx.rows) {
|
|
const suffix = row.indexname.includes('vec') ? 'vec_idx' : row.indexname.includes('tsv') ? 'tsv_idx' : row.indexname.includes('parent') ? 'parent_idx' : null;
|
|
if (suffix) await client.query(`ALTER INDEX ${quoteIdent(row.indexname)} RENAME TO ${quoteIdent('kb_docs_' + suffix)}`).catch(() => {});
|
|
}
|
|
await client.query('COMMIT');
|
|
return { rolled_back_to: target, previous: hasCurrent.rows[0].ok ? currentBackup : null };
|
|
} catch (err) {
|
|
await client.query('ROLLBACK');
|
|
throw err;
|
|
} finally {
|
|
client.release();
|
|
}
|
|
}
|
|
|
|
module.exports = { listVersions, rollback };
|