|
|
#!/usr/bin/env node |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
const { Client } = require('pg'); |
|
|
const readline = require('readline'); |
|
|
const fs = require('fs'); |
|
|
const path = require('path'); |
|
|
|
|
|
|
|
|
const rl = readline.createInterface({ |
|
|
input: process.stdin, |
|
|
output: process.stdout |
|
|
}); |
|
|
|
|
|
|
|
|
const config = { |
|
|
host: 'rag-ai.e.aivencloud.com', |
|
|
port: 15005, |
|
|
ssl: { |
|
|
rejectUnauthorized: false |
|
|
} |
|
|
}; |
|
|
|
|
|
|
|
|
function question(query, hidden = false) { |
|
|
return new Promise((resolve) => { |
|
|
if (hidden) { |
|
|
const stdin = process.openStdin(); |
|
|
process.stdout.write(query); |
|
|
stdin.setEncoding('utf8'); |
|
|
stdin.on('data', (char) => { |
|
|
char = char.toString(); |
|
|
if (char === '\n' || char === '\r' || char === '\u0004') { |
|
|
stdin.pause(); |
|
|
resolve(); |
|
|
} else { |
|
|
process.stdout.write('*'); |
|
|
} |
|
|
}); |
|
|
} else { |
|
|
rl.question(query, resolve); |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
async function main() { |
|
|
console.log('\n=== PostgreSQL 迁移失败清理工具 ===\n'); |
|
|
|
|
|
try { |
|
|
|
|
|
const database = await question('请输入数据库名 [默认: anythingllm]: ') || 'anythingllm'; |
|
|
const username = await question('请输入用户名 [默认: postgres]: ') || 'postgres'; |
|
|
const password = await question('请输入密码: ', true); |
|
|
|
|
|
config.user = username; |
|
|
config.password = password; |
|
|
config.database = database; |
|
|
|
|
|
|
|
|
const client = new Client(config); |
|
|
|
|
|
console.log('\n正在连接到数据库...'); |
|
|
await client.connect(); |
|
|
console.log('✓ 连接成功\n'); |
|
|
|
|
|
|
|
|
console.log('检查 _prisma_migrations 表状态...'); |
|
|
const migrationsResult = await client.query(` |
|
|
SELECT |
|
|
migration_name, |
|
|
started_at, |
|
|
finished_at, |
|
|
logs |
|
|
FROM _prisma_migrations |
|
|
ORDER BY started_at DESC |
|
|
`); |
|
|
|
|
|
if (migrationsResult.rows.length === 0) { |
|
|
console.log('未找到迁移记录,数据库可能是干净的。'); |
|
|
await client.end(); |
|
|
rl.close(); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
console.log('\n当前迁移状态:'); |
|
|
console.log('─'.repeat(80)); |
|
|
console.log('迁移名称'.padEnd(30) + '状态'.padEnd(15) + '开始时间'); |
|
|
console.log('─'.repeat(80)); |
|
|
|
|
|
migrationsResult.rows.forEach(row => { |
|
|
const status = row.finished_at ? '已完成' : '失败'; |
|
|
const time = row.started_at.toLocaleString('zh-CN'); |
|
|
console.log(`${row.migration_name.padEnd(30)}${status.padEnd(15)}${time}`); |
|
|
}); |
|
|
|
|
|
|
|
|
const failedMigrations = migrationsResult.rows.filter(row => !row.finished_at); |
|
|
|
|
|
if (failedMigrations.length === 0) { |
|
|
console.log('\n✓ 没有找到失败的迁移记录。'); |
|
|
await client.end(); |
|
|
rl.close(); |
|
|
return; |
|
|
} |
|
|
|
|
|
console.log(`\n发现 ${failedMigrations.length} 个失败的迁移:`); |
|
|
failedMigrations.forEach(row => { |
|
|
console.log(`- ${row.migration_name}`); |
|
|
}); |
|
|
|
|
|
|
|
|
console.log('\n请选择清理策略:'); |
|
|
console.log('1. 仅删除失败的迁移记录'); |
|
|
console.log('2. 删除所有未完成的迁移'); |
|
|
console.log('3. 完全重置迁移表(谨慎使用)'); |
|
|
console.log('0. 退出'); |
|
|
|
|
|
const choice = await question('\n请输入选择 [0-3]: '); |
|
|
|
|
|
switch (choice) { |
|
|
case '1': |
|
|
await cleanupFailedOnly(client, failedMigrations); |
|
|
break; |
|
|
case '2': |
|
|
await cleanupAllIncomplete(client); |
|
|
break; |
|
|
case '3': |
|
|
await resetMigrationsTable(client); |
|
|
break; |
|
|
case '0': |
|
|
console.log('退出程序。'); |
|
|
break; |
|
|
default: |
|
|
console.log('无效选择。'); |
|
|
} |
|
|
|
|
|
|
|
|
console.log('\n检查数据库表结构...'); |
|
|
const tablesResult = await client.query(` |
|
|
SELECT table_name |
|
|
FROM information_schema.tables |
|
|
WHERE table_schema = 'public' |
|
|
AND table_type = 'BASE TABLE' |
|
|
ORDER BY table_name |
|
|
`); |
|
|
|
|
|
console.log('\n当前数据库中的表:'); |
|
|
tablesResult.rows.forEach(row => { |
|
|
console.log(`- ${row.table_name}`); |
|
|
}); |
|
|
|
|
|
await client.end(); |
|
|
|
|
|
} catch (error) { |
|
|
console.error('\n错误:', error.message); |
|
|
if (error.code === '3D000') { |
|
|
console.log('数据库不存在,请先创建数据库。'); |
|
|
} else if (error.code === '28P01') { |
|
|
console.log('认证失败,请检查用户名和密码。'); |
|
|
} else if (error.code === '08006') { |
|
|
console.log('连接失败,请检查网络和连接参数。'); |
|
|
} |
|
|
} |
|
|
|
|
|
rl.close(); |
|
|
} |
|
|
|
|
|
|
|
|
async function cleanupFailedOnly(client, failedMigrations) { |
|
|
console.log('\n=== 清理失败的迁移 ==='); |
|
|
|
|
|
|
|
|
const backup = await question('是否备份失败记录?[Y/n]: '); |
|
|
if (backup.toLowerCase() !== 'n') { |
|
|
await client.query(` |
|
|
CREATE TABLE _prisma_migrations_failed_backup AS |
|
|
SELECT * FROM _prisma_migrations |
|
|
WHERE finished_at IS NULL |
|
|
`); |
|
|
console.log('✓ 备份完成'); |
|
|
} |
|
|
|
|
|
|
|
|
for (const migration of failedMigrations) { |
|
|
await client.query(` |
|
|
DELETE FROM _prisma_migrations |
|
|
WHERE migration_name = '${migration.migration_name}' |
|
|
`); |
|
|
console.log(`✓ 已删除: ${migration.migration_name}`); |
|
|
} |
|
|
|
|
|
console.log('\n✓ 清理完成!'); |
|
|
console.log('\n现在可以重新运行迁移命令:'); |
|
|
console.log('npx prisma migrate dev'); |
|
|
} |
|
|
|
|
|
|
|
|
async function cleanupAllIncomplete(client) { |
|
|
console.log('\n=== 清理所有未完成的迁移 ==='); |
|
|
|
|
|
const confirm = await question('这将删除所有未完成的迁移记录,确定吗?[y/N]: '); |
|
|
if (confirm.toLowerCase() !== 'y') { |
|
|
console.log('操作已取消。'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
await client.query(` |
|
|
CREATE TABLE _prisma_migrations_incomplete_backup AS |
|
|
SELECT * FROM _prisma_migrations |
|
|
WHERE finished_at IS NULL |
|
|
`); |
|
|
console.log('✓ 备份完成'); |
|
|
|
|
|
|
|
|
const result = await client.query(` |
|
|
DELETE FROM _prisma_migrations |
|
|
WHERE finished_at IS NULL |
|
|
`); |
|
|
|
|
|
console.log(`✓ 已删除 ${result.rowCount} 个未完成的迁移`); |
|
|
console.log('\n✓ 清理完成!'); |
|
|
} |
|
|
|
|
|
|
|
|
async function resetMigrationsTable(client) { |
|
|
console.log('\n=== 重置迁移表 ==='); |
|
|
|
|
|
const confirm1 = await question('这将完全清空迁移表,确定吗?[y/N]: '); |
|
|
if (confirm1.toLowerCase() !== 'y') { |
|
|
console.log('操作已取消。'); |
|
|
return; |
|
|
} |
|
|
|
|
|
const confirm2 = await question('这可能导致数据丢失,再次确认?[y/N]: '); |
|
|
if (confirm2.toLowerCase() !== 'y') { |
|
|
console.log('操作已取消。'); |
|
|
return; |
|
|
} |
|
|
|
|
|
|
|
|
await client.query(` |
|
|
CREATE TABLE _prisma_migrations_full_backup AS |
|
|
SELECT * FROM _prisma_migrations |
|
|
`); |
|
|
console.log('✓ 完整备份完成'); |
|
|
|
|
|
|
|
|
await client.query('TRUNCATE _prisma_migrations'); |
|
|
console.log('✓ 迁移表已重置'); |
|
|
|
|
|
console.log('\n⚠️ 警告:迁移表已完全重置'); |
|
|
console.log('下次运行迁移时将从头开始创建所有表结构'); |
|
|
} |
|
|
|
|
|
|
|
|
if (require.main === module) { |
|
|
main().catch(console.error); |
|
|
} |
|
|
|
|
|
module.exports = { |
|
|
main, |
|
|
cleanupFailedOnly, |
|
|
cleanupAllIncomplete, |
|
|
resetMigrationsTable |
|
|
}; |