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.
112 lines
3.6 KiB
112 lines
3.6 KiB
/**
|
|
* Aplica las migraciones pendientes.
|
|
*
|
|
* npm run db:migrate # aplica lo que falte
|
|
* npm run db:migrate -- --ver # solo dice qué falta
|
|
*
|
|
* ## Por qué no `drizzle-kit migrate`
|
|
*
|
|
* Porque miente. Su troceador de SQL no entiende los cuerpos `$$` de las
|
|
* funciones en plpgsql —y este esquema tiene dos, la que comprueba que el
|
|
* reparto de autoría suma cien y la que impide ciclos en el linaje de
|
|
* géneros—, así que a partir de esa migración deja de aplicar nada **y termina
|
|
* sin error**. Se descubrió porque una columna que se había borrado en una
|
|
* migración seguía existiendo dos migraciones después.
|
|
*
|
|
* Esto trocea por el mismo separador que escribe Drizzle, aplica en orden,
|
|
* anota en su misma tabla y con su mismo hash —el SHA-256 del archivo—, y si
|
|
* algo falla dice **en qué sentencia** y deshace.
|
|
*/
|
|
|
|
import { createHash } from 'node:crypto';
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import pg from 'pg';
|
|
|
|
const CARPETA = 'drizzle';
|
|
const SOLO_VER = process.argv.includes('--ver');
|
|
|
|
/** Trocea por el separador de Drizzle y descarta lo que solo son comentarios. */
|
|
function sentenciasDe(sql) {
|
|
return (
|
|
sql
|
|
.split('--> statement-breakpoint')
|
|
.map((s) => s.trim())
|
|
// Línea a línea y sin expresión regular: `(--[^\n]*\n?)+` tarda catorce
|
|
// segundos en un comentario de quinientos caracteres, porque cuando no
|
|
// encaja puede repartir el mismo texto de exponencialmente muchas formas.
|
|
.filter((s) => s && s.split('\n').some((l) => l.trim() && !l.trim().startsWith('--')))
|
|
);
|
|
}
|
|
|
|
const archivos = fs
|
|
.readdirSync(CARPETA)
|
|
.filter((n) => n.endsWith('.sql'))
|
|
.sort();
|
|
|
|
const c = new pg.Client({ connectionString: process.env.DATABASE_URL, query_timeout: 120_000 });
|
|
await c.connect();
|
|
|
|
await c.query('CREATE SCHEMA IF NOT EXISTS drizzle');
|
|
await c.query(`CREATE TABLE IF NOT EXISTS drizzle.__drizzle_migrations (
|
|
id SERIAL PRIMARY KEY, hash text NOT NULL, created_at bigint)`);
|
|
|
|
const { rows: hechas } = await c.query('SELECT hash FROM drizzle.__drizzle_migrations');
|
|
const yaEstan = new Set(hechas.map((f) => f.hash));
|
|
|
|
const pendientes = archivos
|
|
.map((nombre) => {
|
|
const sql = fs.readFileSync(path.join(CARPETA, nombre), 'utf8');
|
|
return { nombre, sql, hash: createHash('sha256').update(sql).digest('hex') };
|
|
})
|
|
.filter((m) => !yaEstan.has(m.hash));
|
|
|
|
if (pendientes.length === 0) {
|
|
console.log(`Al día: ${archivos.length} migraciones, ninguna pendiente.`);
|
|
await c.end();
|
|
process.exit(0);
|
|
}
|
|
|
|
console.log(`Pendientes ${pendientes.length} de ${archivos.length}:`);
|
|
for (const m of pendientes) console.log(` ${m.nombre}`);
|
|
|
|
if (SOLO_VER) {
|
|
console.log('\n(solo mirando: no se ha tocado nada)');
|
|
await c.end();
|
|
process.exit(0);
|
|
}
|
|
|
|
for (const m of pendientes) {
|
|
const sentencias = sentenciasDe(m.sql);
|
|
process.stdout.write(`\n${m.nombre} · ${sentencias.length} sentencias `);
|
|
|
|
await c.query('BEGIN');
|
|
try {
|
|
for (const [i, sentencia] of sentencias.entries()) {
|
|
try {
|
|
await c.query(sentencia);
|
|
} catch (fallo) {
|
|
const primera =
|
|
sentencia.split('\n').find((l) => l.trim() && !l.trim().startsWith('--')) ?? '';
|
|
console.log(`\n\n Falla la sentencia ${i + 1}:\n ${primera.trim().slice(0, 90)}`);
|
|
console.log(` → ${fallo.message}`);
|
|
throw fallo;
|
|
}
|
|
}
|
|
await c.query('INSERT INTO drizzle.__drizzle_migrations (hash, created_at) VALUES ($1, $2)', [
|
|
m.hash,
|
|
String(Date.now())
|
|
]);
|
|
await c.query('COMMIT');
|
|
console.log('· aplicada');
|
|
} catch {
|
|
await c.query('ROLLBACK').catch(() => {});
|
|
console.log('\nSe deshizo. Las anteriores siguen aplicadas.');
|
|
await c.end();
|
|
process.exit(1);
|
|
}
|
|
}
|
|
|
|
console.log('\nAl día.');
|
|
await c.end();
|