You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
75 lines
2.6 KiB
75 lines
2.6 KiB
/**
|
|
* Ejecuta la migración en una transacción y la deshace.
|
|
*
|
|
* `drizzle-kit migrate` se queda colgado sin decir por qué, así que hay que
|
|
* preguntarle directamente a Postgres.
|
|
*
|
|
* El progreso se escribe a disco de forma SÍNCRONA. La salida normal de Node
|
|
* se acumula en un buffer cuando va a un pipe o a un archivo, y si el proceso
|
|
* se mata por tiempo ese buffer se pierde entero: es justo lo que hacía que
|
|
* pareciera que no llegaba a arrancar.
|
|
*/
|
|
import fs from 'node:fs';
|
|
import pg from 'pg';
|
|
|
|
const ARCHIVO = 'drizzle/0002_catalogo_en_la_base.sql';
|
|
const DIARIO = '.diario-migracion.txt';
|
|
const APLICAR = process.argv.includes('--aplicar');
|
|
|
|
fs.writeFileSync(DIARIO, '');
|
|
const apuntar = (linea) => fs.appendFileSync(DIARIO, linea + '\n');
|
|
|
|
const sql = fs.readFileSync(ARCHIVO, 'utf8');
|
|
const sentencias = sql
|
|
.split('--> statement-breakpoint')
|
|
.map((s) => s.trim())
|
|
// Sin expresión regular. `(--[^\n]*\n?)+` tarda catorce segundos en un
|
|
// comentario de quinientos caracteres: cuando no encaja, puede repartir el
|
|
// mismo texto de exponencialmente muchas formas antes de rendirse. Línea a
|
|
// línea es lineal, y además se lee.
|
|
.filter((s) => s && s.split('\n').some((l) => l.trim() && !l.trim().startsWith('--')));
|
|
|
|
apuntar(`${sentencias.length} sentencias`);
|
|
|
|
const cliente = new pg.Client({
|
|
connectionString: process.env.DATABASE_URL,
|
|
// Que no se quede esperando eternamente si el servidor no contesta.
|
|
connectionTimeoutMillis: 10_000,
|
|
query_timeout: 30_000
|
|
});
|
|
|
|
apuntar('conectando...');
|
|
await cliente.connect();
|
|
apuntar('conectado');
|
|
|
|
await cliente.query('BEGIN');
|
|
// Que falle rápido en vez de colgarse esperando un bloqueo que no llega.
|
|
await cliente.query("SET LOCAL lock_timeout = '5s'");
|
|
await cliente.query("SET LOCAL statement_timeout = '30s'");
|
|
apuntar('transacción abierta');
|
|
|
|
let hechas = 0;
|
|
let rota = null;
|
|
|
|
for (const [i, sentencia] of sentencias.entries()) {
|
|
const resumen = sentencia.split('\n').find((l) => l.trim() && !l.trim().startsWith('--')) ?? '';
|
|
apuntar(`[${i + 1}/${sentencias.length}] ${resumen.trim().slice(0, 80)}`);
|
|
try {
|
|
await cliente.query(sentencia);
|
|
hechas++;
|
|
} catch (fallo) {
|
|
rota = { i, sentencia, fallo };
|
|
apuntar(`\n FALLA: ${fallo.message}`);
|
|
if (fallo.hint) apuntar(` pista: ${fallo.hint}`);
|
|
if (fallo.position) apuntar(` posición: ${fallo.position}`);
|
|
apuntar('\n' + sentencia.slice(0, 600));
|
|
break;
|
|
}
|
|
}
|
|
|
|
const completa = !rota && hechas === sentencias.length;
|
|
await cliente.query(APLICAR && completa ? 'COMMIT' : 'ROLLBACK');
|
|
apuntar(completa ? `\nLas ${hechas} sentencias pasan.` : `\nParó en la ${hechas + 1}.`);
|
|
apuntar(APLICAR && completa ? 'APLICADA.' : '(deshecha)');
|
|
await cliente.end();
|