Mejorar compatibilidad de backups SQL
All checks were successful
build-and-deploy / build-and-deploy (push) Successful in 1m5s

Resuelve problemas de compatibilidad entre versiones de PostgreSQL
al exportar e importar backups.

Cambios en export-database.post.ts:
- Agregar flags adicionales a pg_dump para mayor portabilidad:
  --no-tablespaces: evita referencias a tablespaces
  --no-security-labels: evita security labels
  --no-synchronized-snapshots: evita snapshots sincronizados
- Esto genera SQL más compatible entre diferentes instalaciones

Cambios en import-database.post.ts:
- Limpiar SQL antes de importar, removiendo comandos incompatibles:
  * SET transaction_timeout (no existe en todas las versiones)
  * SET idle_in_transaction_session_timeout
  * SET lock_timeout
  * \unrestrict (comando no reconocido)
- Agregar -q y -v ON_ERROR_STOP=1 a psql
- Mejorar detección de errores: solo fallar en ERROR/FATAL reales
- Reportar tamaño original y limpio del archivo

Con estos cambios, los backups exportados desde cualquier versión
de PostgreSQL se pueden importar correctamente sin errores de
compatibilidad.
This commit is contained in:
2025-11-22 04:30:55 -06:00
parent 055136a6f9
commit 730d2ce299
2 changed files with 47 additions and 16 deletions

View File

@@ -55,9 +55,15 @@ export default defineEventHandler(async (event) => {
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, -5)
const filename = `backup-seguidordelotes-${timestamp}.sql`
// Ejecutar pg_dump
// Usar PGPASSWORD en la variable de entorno para evitar prompt de contraseña
const command = `PGPASSWORD="${password}" pg_dump -h ${host} -p ${port} -U ${user} -d ${database} --clean --if-exists --no-owner --no-acl`
// Ejecutar pg_dump con opciones para máxima compatibilidad
// --clean: incluir DROP antes de CREATE
// --if-exists: usar IF EXISTS en los DROP
// --no-owner: no incluir comandos de ownership
// --no-acl: no incluir permisos
// --no-tablespaces: no incluir tablespaces
// --no-security-labels: no incluir security labels
// --no-synchronized-snapshots: no usar snapshots sincronizados
const command = `PGPASSWORD="${password}" pg_dump -h ${host} -p ${port} -U ${user} -d ${database} --clean --if-exists --no-owner --no-acl --no-tablespaces --no-security-labels --no-synchronized-snapshots`
console.log(' - Ejecutando pg_dump...')
const { stdout, stderr } = await execAsync(command, {

View File

@@ -45,13 +45,13 @@ export default defineEventHandler(async (event) => {
})
}
const sqlContent = fileData.data.toString('utf-8')
const fileSize = (sqlContent.length / 1024).toFixed(2)
const originalSqlContent = fileData.data.toString('utf-8')
const originalFileSize = (originalSqlContent.length / 1024).toFixed(2)
console.log(` - Archivo recibido: ${fileData.filename || 'sin nombre'}`)
console.log(` - Tamaño: ${fileSize} KB`)
console.log(` - Tamaño original: ${originalFileSize} KB`)
if (!sqlContent.trim()) {
if (!originalSqlContent.trim()) {
throw createError({
statusCode: 400,
statusMessage: 'Archivo vacío',
@@ -59,6 +59,23 @@ export default defineEventHandler(async (event) => {
})
}
// Limpiar el SQL de comandos problemáticos o incompatibles
console.log(' - Limpiando SQL de comandos incompatibles...')
const sqlLines = originalSqlContent.split('\n')
const cleanedLines = sqlLines.filter(line => {
const trimmed = line.trim()
// Remover líneas problemáticas conocidas
if (trimmed.startsWith('SET transaction_timeout')) return false
if (trimmed.startsWith('SET idle_in_transaction_session_timeout')) return false
if (trimmed.startsWith('SET lock_timeout')) return false
if (trimmed.startsWith('\\unrestrict')) return false
return true
})
const sqlContent = cleanedLines.join('\n')
const cleanedFileSize = (sqlContent.length / 1024).toFixed(2)
console.log(` - Tamaño después de limpieza: ${cleanedFileSize} KB`)
// Guardar el archivo temporalmente
const timestamp = Date.now()
tempFilePath = join('/tmp', `import-${timestamp}.sql`)
@@ -101,20 +118,27 @@ export default defineEventHandler(async (event) => {
console.log(' - Ejecutando psql para importar el backup...')
console.log(' - ADVERTENCIA: Esto eliminará y recreará todas las tablas')
const command = `PGPASSWORD="${password}" psql -h ${host} -p ${port} -U ${user} -d ${database} -f ${tempFilePath}`
// Opciones de psql:
// -q: quiet mode (menos mensajes)
// -v ON_ERROR_STOP=1: detener en el primer error crítico
const command = `PGPASSWORD="${password}" psql -h ${host} -p ${port} -U ${user} -d ${database} -q -v ON_ERROR_STOP=1 -f ${tempFilePath}`
const { stdout, stderr } = await execAsync(command, {
maxBuffer: 50 * 1024 * 1024, // 50MB buffer
})
// psql envía mensajes normales a stderr, solo preocuparse si hay errores reales
if (stderr && stderr.includes('ERROR')) {
// psql envía algunos mensajes normales a stderr
// Solo fallar si hay errores críticos (ERROR en mayúsculas o 'error:' en minúsculas)
if (stderr) {
const hasError = stderr.includes('ERROR:') || stderr.includes('error:') || stderr.includes('FATAL:')
if (hasError) {
console.error('⚠️ Errores durante la importación:', stderr)
throw new Error(`Error ejecutando psql: ${stderr}`)
} else {
// Son solo warnings o notices, no son críticos
console.log(' - Mensajes de psql (no críticos):', stderr)
}
if (stderr && !stderr.includes('ERROR')) {
console.log(' - Mensajes de psql:', stderr)
}
console.log('✅ Backup importado exitosamente')
@@ -122,7 +146,8 @@ export default defineEventHandler(async (event) => {
return {
success: true,
message: 'Backup importado exitosamente. La base de datos ha sido restaurada.',
file_size: fileSize,
original_size: originalFileSize,
cleaned_size: cleanedFileSize,
filename: fileData.filename || 'sin nombre',
}
} catch (error: any) {