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.
senzapaura_es/scripts/db/comprobar-guardas.mjs

289 lines
11 KiB

Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
/**
* Comprueba que las guardas del esquema hacen lo que dicen.
*
* Que una restricción exista en `pg_constraint` no significa que impida nada:
* se prueba intentando lo que tiene que rechazar. Todo va dentro de una
* transacción que se deshace, así que no deja rastro.
*/
import fs from 'node:fs';
import pg from 'pg';
const DIARIO = '.diario-guardas.txt';
fs.writeFileSync(DIARIO, '');
const apuntar = (l) => fs.appendFileSync(DIARIO, l + '\n');
const c = new pg.Client({ connectionString: process.env.DATABASE_URL, query_timeout: 15_000 });
await c.connect();
await c.query('BEGIN');
const resumen = await c.query(`
select
(select count(*)::int from information_schema.tables
where table_schema = 'public' and table_type = 'BASE TABLE') as tablas,
(select count(*)::int from information_schema.views
where table_schema = 'public') as vistas,
(select count(*)::int from information_schema.table_constraints
where constraint_schema = 'public' and constraint_type = 'FOREIGN KEY') as fks,
(select count(*)::int from pg_trigger where not tgisinternal) as disparadores`);
const r = resumen.rows[0];
apuntar(
`tablas ${r.tablas} · vistas ${r.vistas} · claves foráneas ${r.fks} · disparadores ${r.disparadores}\n`
);
/** Prueba que algo se rechaza. Si pasa, es que la guarda no guarda. */
async function rechaza(nombre, sql, valores = []) {
await c.query('SAVEPOINT prueba');
try {
await c.query(sql, valores);
await c.query('ROLLBACK TO SAVEPOINT prueba');
apuntar(` ✗ ${nombre} — SE PERMITIÓ, y no debería`);
return false;
} catch (fallo) {
await c.query('ROLLBACK TO SAVEPOINT prueba');
const motivo = fallo.message.split('\n')[0].slice(0, 70);
apuntar(` ✓ ${nombre} — rechazado: ${motivo}`);
return true;
}
}
/**
* Como `rechaza`, pero forzando la comprobación de las restricciones
* aplazadas.
*
* El disparador del reparto de autoría es `DEFERRABLE INITIALLY DEFERRED`, y
* con razón: al insertar tres autores, los estados intermedios no suman cien y
* comprobarlo fila a fila haría imposible cualquier inserción. Pero eso
* significa que solo salta al cerrar la transacción, y una prueba que nunca
* cierra no lo despierta: la primera versión de esto daba por buena una guarda
* que no había llegado a ejecutarse.
*/
async function rechazaAlCerrar(nombre, preparar, sql) {
/*
* Conexión propia y transacción propia. Dentro de un savepoint,
* `SET CONSTRAINTS ALL IMMEDIATE` no despertaba los eventos aplazados y la
* prueba daba por bueno lo que el disparador sí rechaza —comprobado
* interrogándolo aparte—. Una guarda que solo se puede comprobar al cerrar
* necesita una transacción que se pueda cerrar.
*/
const otro = new pg.Client({ connectionString: process.env.DATABASE_URL, query_timeout: 15_000 });
await otro.connect();
await otro.query('BEGIN');
try {
for (const linea of preparar) await otro.query(linea);
await otro.query(sql);
await otro.query('SET CONSTRAINTS ALL IMMEDIATE');
apuntar(` ✗ ${nombre} — SE PERMITIÓ, y no debería`);
return false;
} catch (fallo) {
apuntar(
` ✓ ${nombre} — rechazado: ${fallo.message.split(String.fromCharCode(10))[0].slice(0, 70)}`
);
return true;
} finally {
await otro.query('ROLLBACK').catch(() => {});
await otro.end();
}
}
/** Y que lo legítimo sí pasa: una guarda que lo bloquea todo tampoco sirve. */
async function permite(nombre, sql, valores = []) {
await c.query('SAVEPOINT prueba');
try {
await c.query(sql, valores);
await c.query('RELEASE SAVEPOINT prueba');
apuntar(` ✓ ${nombre} — permitido, como debe`);
return true;
} catch (fallo) {
await c.query('ROLLBACK TO SAVEPOINT prueba');
apuntar(` ✗ ${nombre} — SE RECHAZÓ: ${fallo.message.split('\n')[0].slice(0, 70)}`);
return false;
}
}
Las compras dejan de apuntar a un texto y apuntan a la canción Fase 3 del modelo, que iba primero por ser lo único que podía costarle dinero a alguien: siete tablas guardaban `cancion_slug` sin clave foránea, porque cuando se escribieron el catálogo vivía en archivos y no había a qué apuntar. Renombrar un slug dejaba huérfana una compra en silencio, y quien la había pagado se enteraba al intentar descargar. Ahora apuntan a `cancion.id`, que es opaco y no cambia. El borrado no se propaga igual en todas: `compra`, `pedido_item` y `descarga` lo RESTRINGEN —una canción pagada no se puede borrar, y el registro de entrega tiene que sobrevivir a lo que prueba—; `carrito_item`, `favorito`, `valoracion` y `reproduccion` caen con ella. En dos migraciones y no en una: entre la 0007 y la 0008 conviven las dos columnas, así que el despliegue no tiene que parar el sitio. La 0007 se niega a seguir si alguna compra apunta a un slug que ya no existe: borrarla en silencio sería repetir el fallo que esto viene a arreglar. Por fuera nada cambia de forma. Los módulos siguen recibiendo slugs —es lo que hay en las URL— y traducen en el borde, en `$lib/server/canciones`. Al hacerlo, tres funciones pasan a devolver `false` cuando el slug no existe, que antes se guardaba tal cual: se podía meter en el carrito un tema inventado. El script de guardas estaba roto desde que se volcó el catálogo —usaba slugs de verdad y moría en la primera inserción por clave duplicada, sin comprobar nada—. Ahora usa el prefijo `zz-` y el país ZZ, que ISO 3166 reserva para uso privado. Y la prueba de la valoración pasaba por el motivo equivocado: rechazaba el seis porque la columna ya no existía, no por el rango. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
/* --- Datos mínimos para poder probar ----------------------------------
*
* Todo con el prefijo `zz-`, y el país `ZZ`, que ISO 3166 reserva para uso
* privado. Antes usaba slugs de verdad —«baladas», «como-si-nada»— y funcionó
* mientras la base estuvo vacía; en cuanto se volcó el catálogo, el script se
* caía en la primera inserción por clave duplicada y dejaba de comprobar nada.
*
* `e1` tampoco es principal: ya hay un estilo principal de verdad, y el índice
* único parcial —que es justo una de las guardas que esto prueba— no admite
* dos.
*/
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
Las compras dejan de apuntar a un texto y apuntan a la canción Fase 3 del modelo, que iba primero por ser lo único que podía costarle dinero a alguien: siete tablas guardaban `cancion_slug` sin clave foránea, porque cuando se escribieron el catálogo vivía en archivos y no había a qué apuntar. Renombrar un slug dejaba huérfana una compra en silencio, y quien la había pagado se enteraba al intentar descargar. Ahora apuntan a `cancion.id`, que es opaco y no cambia. El borrado no se propaga igual en todas: `compra`, `pedido_item` y `descarga` lo RESTRINGEN —una canción pagada no se puede borrar, y el registro de entrega tiene que sobrevivir a lo que prueba—; `carrito_item`, `favorito`, `valoracion` y `reproduccion` caen con ella. En dos migraciones y no en una: entre la 0007 y la 0008 conviven las dos columnas, así que el despliegue no tiene que parar el sitio. La 0007 se niega a seguir si alguna compra apunta a un slug que ya no existe: borrarla en silencio sería repetir el fallo que esto viene a arreglar. Por fuera nada cambia de forma. Los módulos siguen recibiendo slugs —es lo que hay en las URL— y traducen en el borde, en `$lib/server/canciones`. Al hacerlo, tres funciones pasan a devolver `false` cuando el slug no existe, que antes se guardaba tal cual: se podía meter en el carrito un tema inventado. El script de guardas estaba roto desde que se volcó el catálogo —usaba slugs de verdad y moría en la primera inserción por clave duplicada, sin comprobar nada—. Ahora usa el prefijo `zz-` y el país ZZ, que ISO 3166 reserva para uso privado. Y la prueba de la valoración pasaba por el motivo equivocado: rechazaba el seis porque la columna ya no existía, no por el rango. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
await c.query(`insert into pais (codigo, nombre) values ('ZZ', 'País de prueba')`);
await c.query(`insert into estilo (id, slug, nombre) values ('e1', 'zz-baladas', 'Baladas')`);
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
await c.query(
`insert into cancion (id, slug, titulo, estilo_id, idioma) values ('c1', 'zz-como-si-nada', 'Como si nada', 'e1', 'es')`
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
);
await c.query(
`insert into cancion (id, slug, titulo, estilo_id, idioma, version_de_id, version_nombre, version_codigo)
values ('c2', 'zz-como-si-nada-maqueta', 'Como si nada', 'e1', 'es', 'c1', 'Maqueta 2018', 'maqueta')`
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
);
Las compras dejan de apuntar a un texto y apuntan a la canción Fase 3 del modelo, que iba primero por ser lo único que podía costarle dinero a alguien: siete tablas guardaban `cancion_slug` sin clave foránea, porque cuando se escribieron el catálogo vivía en archivos y no había a qué apuntar. Renombrar un slug dejaba huérfana una compra en silencio, y quien la había pagado se enteraba al intentar descargar. Ahora apuntan a `cancion.id`, que es opaco y no cambia. El borrado no se propaga igual en todas: `compra`, `pedido_item` y `descarga` lo RESTRINGEN —una canción pagada no se puede borrar, y el registro de entrega tiene que sobrevivir a lo que prueba—; `carrito_item`, `favorito`, `valoracion` y `reproduccion` caen con ella. En dos migraciones y no en una: entre la 0007 y la 0008 conviven las dos columnas, así que el despliegue no tiene que parar el sitio. La 0007 se niega a seguir si alguna compra apunta a un slug que ya no existe: borrarla en silencio sería repetir el fallo que esto viene a arreglar. Por fuera nada cambia de forma. Los módulos siguen recibiendo slugs —es lo que hay en las URL— y traducen en el borde, en `$lib/server/canciones`. Al hacerlo, tres funciones pasan a devolver `false` cuando el slug no existe, que antes se guardaba tal cual: se podía meter en el carrito un tema inventado. El script de guardas estaba roto desde que se volcó el catálogo —usaba slugs de verdad y moría en la primera inserción por clave duplicada, sin comprobar nada—. Ahora usa el prefijo `zz-` y el país ZZ, que ISO 3166 reserva para uso privado. Y la prueba de la valoración pasaba por el motivo equivocado: rechazaba el seis porque la columna ya no existía, no por el rango. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
await c.query(`insert into persona (id, slug, nombre) values ('p1', 'zz-juan', 'Juan')`);
await c.query(`insert into genero (id, slug, nombre) values ('g1', 'zz-bolero', 'Bolero')`);
await c.query(`insert into genero (id, slug, nombre) values ('g2', 'zz-son', 'Son cubano')`);
await c.query(`insert into genero (id, slug, nombre) values ('g3', 'zz-bolero-son', 'Bolero-son')`);
await c.query(`insert into usuario (id, email) values ('u9', 'zz@example.com')`);
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
await c.query(
Las compras dejan de apuntar a un texto y apuntan a la canción Fase 3 del modelo, que iba primero por ser lo único que podía costarle dinero a alguien: siete tablas guardaban `cancion_slug` sin clave foránea, porque cuando se escribieron el catálogo vivía en archivos y no había a qué apuntar. Renombrar un slug dejaba huérfana una compra en silencio, y quien la había pagado se enteraba al intentar descargar. Ahora apuntan a `cancion.id`, que es opaco y no cambia. El borrado no se propaga igual en todas: `compra`, `pedido_item` y `descarga` lo RESTRINGEN —una canción pagada no se puede borrar, y el registro de entrega tiene que sobrevivir a lo que prueba—; `carrito_item`, `favorito`, `valoracion` y `reproduccion` caen con ella. En dos migraciones y no en una: entre la 0007 y la 0008 conviven las dos columnas, así que el despliegue no tiene que parar el sitio. La 0007 se niega a seguir si alguna compra apunta a un slug que ya no existe: borrarla en silencio sería repetir el fallo que esto viene a arreglar. Por fuera nada cambia de forma. Los módulos siguen recibiendo slugs —es lo que hay en las URL— y traducen en el borde, en `$lib/server/canciones`. Al hacerlo, tres funciones pasan a devolver `false` cuando el slug no existe, que antes se guardaba tal cual: se podía meter en el carrito un tema inventado. El script de guardas estaba roto desde que se volcó el catálogo —usaba slugs de verdad y moría en la primera inserción por clave duplicada, sin comprobar nada—. Ahora usa el prefijo `zz-` y el país ZZ, que ISO 3166 reserva para uso privado. Y la prueba de la valoración pasaba por el motivo equivocado: rechazaba el seis porque la columna ya no existía, no por el rango. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
`insert into pedido (id, usuario_id, sesion_pago_id, estado, packs, total_centimos)
values ('o9', 'u9', 'cs_zz', 'pagado', 1, 500)`
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
);
apuntar('LAS VERSIONES');
await permite(
'una canción puede tener versiones',
`insert into cancion (id, slug, titulo, estilo_id, idioma, version_de_id, version_nombre, version_codigo)
values ('c3', 'zz-orquestal', 'Como si nada', 'e1', 'es', 'c1', 'Orquestal', 'orquestal')`
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
);
await rechaza(
'una versión no puede tener versiones',
`insert into cancion (id, slug, titulo, estilo_id, idioma, version_de_id, version_nombre, version_codigo)
values ('c4', 'zz-x', 'X', 'e1', 'es', 'c2', 'X', 'x')`
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
);
await rechaza(
'una canción no es versión de sí misma',
`update cancion set version_de_id = 'c1' where id = 'c1'`
);
apuntar('\nLA VISTA DE PRINCIPALES');
const vista = await c.query('select count(*)::int n from cancion_principal');
const todas = await c.query('select count(*)::int n from cancion');
const principales = await c.query(
'select count(*)::int n from cancion where version_de_id is null'
);
const bien = vista.rows[0].n === principales.rows[0].n && vista.rows[0].n < todas.rows[0].n;
apuntar(
` ${bien ? '✓' : '✗'} cancion tiene ${todas.rows[0].n} filas y cancion_principal ${vista.rows[0].n}, que son las principales`
);
apuntar('\nLOS ATRIBUTOS DE CANCIÓN');
await permite(
'idioma, formación vocal y letra se guardan por grabación',
`update cancion set idioma = 'pt-BR', formacion_vocal = 'dueto-mixto', tiene_letra = false
where id = 'c2'`
);
await rechaza(
'el idioma tiene que ser una etiqueta BCP 47',
`update cancion set idioma = 'francés' where id = 'c1'`
);
await rechaza(
'no se puede inventar una formación vocal',
`update cancion set formacion_vocal = 'trío' where id = 'c1'`
);
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
apuntar('\nEL LINAJE DE GÉNEROS');
await permite(
'el bolero-son deriva de dos géneros',
`insert into genero_origen (genero_id, origen_id, tipo, principal) values
('g3', 'g1', 'fusiona', true), ('g3', 'g2', 'fusiona', false)`
);
await rechaza(
'un género no deriva de sí mismo',
`insert into genero_origen (genero_id, origen_id, tipo) values ('g1', 'g1', 'deriva')`
);
await rechaza(
'no se admiten ciclos en el linaje',
`insert into genero_origen (genero_id, origen_id, tipo) values ('g1', 'g3', 'deriva')`
);
await rechaza(
'solo una arista principal por género',
`update genero_origen set principal = true where genero_id = 'g3' and origen_id = 'g2'`
);
apuntar('\nEL REPARTO DE AUTORÍA');
await permite(
'un reparto que suma cien pasa',
`insert into autoria (cancion_id, persona_id, rol, porcentaje)
values ('c1', 'p1', 'letra', 60), ('c1', 'p1', 'musica', 40)`
);
/*
* Sobre otra canción y con un rol libre, para que el rechazo venga de la suma y
* no de la clave primaria. La primera versión de esta prueba pasaba por el
* motivo equivocado —clave duplicada— y dejaba el disparador sin comprobar, que
* es peor que no tener prueba.
*/
await rechazaAlCerrar(
'un reparto que suma noventa, no',
[
Las compras dejan de apuntar a un texto y apuntan a la canción Fase 3 del modelo, que iba primero por ser lo único que podía costarle dinero a alguien: siete tablas guardaban `cancion_slug` sin clave foránea, porque cuando se escribieron el catálogo vivía en archivos y no había a qué apuntar. Renombrar un slug dejaba huérfana una compra en silencio, y quien la había pagado se enteraba al intentar descargar. Ahora apuntan a `cancion.id`, que es opaco y no cambia. El borrado no se propaga igual en todas: `compra`, `pedido_item` y `descarga` lo RESTRINGEN —una canción pagada no se puede borrar, y el registro de entrega tiene que sobrevivir a lo que prueba—; `carrito_item`, `favorito`, `valoracion` y `reproduccion` caen con ella. En dos migraciones y no en una: entre la 0007 y la 0008 conviven las dos columnas, así que el despliegue no tiene que parar el sitio. La 0007 se niega a seguir si alguna compra apunta a un slug que ya no existe: borrarla en silencio sería repetir el fallo que esto viene a arreglar. Por fuera nada cambia de forma. Los módulos siguen recibiendo slugs —es lo que hay en las URL— y traducen en el borde, en `$lib/server/canciones`. Al hacerlo, tres funciones pasan a devolver `false` cuando el slug no existe, que antes se guardaba tal cual: se podía meter en el carrito un tema inventado. El script de guardas estaba roto desde que se volcó el catálogo —usaba slugs de verdad y moría en la primera inserción por clave duplicada, sin comprobar nada—. Ahora usa el prefijo `zz-` y el país ZZ, que ISO 3166 reserva para uso privado. Y la prueba de la valoración pasaba por el motivo equivocado: rechazaba el seis porque la columna ya no existía, no por el rango. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
`insert into estilo (id, slug, nombre) values ('z1', 'zz-z', 'Z')`,
`insert into cancion (id, slug, titulo, estilo_id, idioma) values ('z2', 'zz-z', 'Z', 'z1', 'es')`,
Las compras dejan de apuntar a un texto y apuntan a la canción Fase 3 del modelo, que iba primero por ser lo único que podía costarle dinero a alguien: siete tablas guardaban `cancion_slug` sin clave foránea, porque cuando se escribieron el catálogo vivía en archivos y no había a qué apuntar. Renombrar un slug dejaba huérfana una compra en silencio, y quien la había pagado se enteraba al intentar descargar. Ahora apuntan a `cancion.id`, que es opaco y no cambia. El borrado no se propaga igual en todas: `compra`, `pedido_item` y `descarga` lo RESTRINGEN —una canción pagada no se puede borrar, y el registro de entrega tiene que sobrevivir a lo que prueba—; `carrito_item`, `favorito`, `valoracion` y `reproduccion` caen con ella. En dos migraciones y no en una: entre la 0007 y la 0008 conviven las dos columnas, así que el despliegue no tiene que parar el sitio. La 0007 se niega a seguir si alguna compra apunta a un slug que ya no existe: borrarla en silencio sería repetir el fallo que esto viene a arreglar. Por fuera nada cambia de forma. Los módulos siguen recibiendo slugs —es lo que hay en las URL— y traducen en el borde, en `$lib/server/canciones`. Al hacerlo, tres funciones pasan a devolver `false` cuando el slug no existe, que antes se guardaba tal cual: se podía meter en el carrito un tema inventado. El script de guardas estaba roto desde que se volcó el catálogo —usaba slugs de verdad y moría en la primera inserción por clave duplicada, sin comprobar nada—. Ahora usa el prefijo `zz-` y el país ZZ, que ISO 3166 reserva para uso privado. Y la prueba de la valoración pasaba por el motivo equivocado: rechazaba el seis porque la columna ya no existía, no por el rango. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
`insert into persona (id, slug, nombre) values ('z3', 'zz-z', 'Z')`
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
],
`insert into autoria (cancion_id, persona_id, rol, porcentaje)
values ('z2', 'z3', 'letra', 90)`
);
await permite(
'y el de c3 sí, si llega a cien',
`insert into autoria (cancion_id, persona_id, rol, porcentaje)
values ('c3', 'p1', 'letra', 90), ('c3', 'p1', 'musica', 10)`
);
apuntar('\nEL ESTILO PRINCIPAL');
await rechaza(
'no puede haber dos estilos principales',
Las compras dejan de apuntar a un texto y apuntan a la canción Fase 3 del modelo, que iba primero por ser lo único que podía costarle dinero a alguien: siete tablas guardaban `cancion_slug` sin clave foránea, porque cuando se escribieron el catálogo vivía en archivos y no había a qué apuntar. Renombrar un slug dejaba huérfana una compra en silencio, y quien la había pagado se enteraba al intentar descargar. Ahora apuntan a `cancion.id`, que es opaco y no cambia. El borrado no se propaga igual en todas: `compra`, `pedido_item` y `descarga` lo RESTRINGEN —una canción pagada no se puede borrar, y el registro de entrega tiene que sobrevivir a lo que prueba—; `carrito_item`, `favorito`, `valoracion` y `reproduccion` caen con ella. En dos migraciones y no en una: entre la 0007 y la 0008 conviven las dos columnas, así que el despliegue no tiene que parar el sitio. La 0007 se niega a seguir si alguna compra apunta a un slug que ya no existe: borrarla en silencio sería repetir el fallo que esto viene a arreglar. Por fuera nada cambia de forma. Los módulos siguen recibiendo slugs —es lo que hay en las URL— y traducen en el borde, en `$lib/server/canciones`. Al hacerlo, tres funciones pasan a devolver `false` cuando el slug no existe, que antes se guardaba tal cual: se podía meter en el carrito un tema inventado. El script de guardas estaba roto desde que se volcó el catálogo —usaba slugs de verdad y moría en la primera inserción por clave duplicada, sin comprobar nada—. Ahora usa el prefijo `zz-` y el país ZZ, que ISO 3166 reserva para uso privado. Y la prueba de la valoración pasaba por el motivo equivocado: rechazaba el seis porque la columna ya no existía, no por el rango. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
`insert into estilo (id, slug, nombre, principal) values ('e2', 'zz-rock', 'Rock', true)`
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
);
await permite(
'los no principales, los que hagan falta',
Las compras dejan de apuntar a un texto y apuntan a la canción Fase 3 del modelo, que iba primero por ser lo único que podía costarle dinero a alguien: siete tablas guardaban `cancion_slug` sin clave foránea, porque cuando se escribieron el catálogo vivía en archivos y no había a qué apuntar. Renombrar un slug dejaba huérfana una compra en silencio, y quien la había pagado se enteraba al intentar descargar. Ahora apuntan a `cancion.id`, que es opaco y no cambia. El borrado no se propaga igual en todas: `compra`, `pedido_item` y `descarga` lo RESTRINGEN —una canción pagada no se puede borrar, y el registro de entrega tiene que sobrevivir a lo que prueba—; `carrito_item`, `favorito`, `valoracion` y `reproduccion` caen con ella. En dos migraciones y no en una: entre la 0007 y la 0008 conviven las dos columnas, así que el despliegue no tiene que parar el sitio. La 0007 se niega a seguir si alguna compra apunta a un slug que ya no existe: borrarla en silencio sería repetir el fallo que esto viene a arreglar. Por fuera nada cambia de forma. Los módulos siguen recibiendo slugs —es lo que hay en las URL— y traducen en el borde, en `$lib/server/canciones`. Al hacerlo, tres funciones pasan a devolver `false` cuando el slug no existe, que antes se guardaba tal cual: se podía meter en el carrito un tema inventado. El script de guardas estaba roto desde que se volcó el catálogo —usaba slugs de verdad y moría en la primera inserción por clave duplicada, sin comprobar nada—. Ahora usa el prefijo `zz-` y el país ZZ, que ISO 3166 reserva para uso privado. Y la prueba de la valoración pasaba por el motivo equivocado: rechazaba el seis porque la columna ya no existía, no por el rango. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
`insert into estilo (id, slug, nombre) values ('e3', 'zz-clasica', 'Clásica')`
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
);
apuntar('\nLAS FACTURAS');
await rechaza(
'una rectificativa tiene que decir a cuál rectifica',
`insert into factura (id, pedido_id, serie, numero, tipo, base_centimos, iva_tipo,
iva_cuota_centimos, total_centimos, datos_congelados, pais_codigo)
Las compras dejan de apuntar a un texto y apuntan a la canción Fase 3 del modelo, que iba primero por ser lo único que podía costarle dinero a alguien: siete tablas guardaban `cancion_slug` sin clave foránea, porque cuando se escribieron el catálogo vivía en archivos y no había a qué apuntar. Renombrar un slug dejaba huérfana una compra en silencio, y quien la había pagado se enteraba al intentar descargar. Ahora apuntan a `cancion.id`, que es opaco y no cambia. El borrado no se propaga igual en todas: `compra`, `pedido_item` y `descarga` lo RESTRINGEN —una canción pagada no se puede borrar, y el registro de entrega tiene que sobrevivir a lo que prueba—; `carrito_item`, `favorito`, `valoracion` y `reproduccion` caen con ella. En dos migraciones y no en una: entre la 0007 y la 0008 conviven las dos columnas, así que el despliegue no tiene que parar el sitio. La 0007 se niega a seguir si alguna compra apunta a un slug que ya no existe: borrarla en silencio sería repetir el fallo que esto viene a arreglar. Por fuera nada cambia de forma. Los módulos siguen recibiendo slugs —es lo que hay en las URL— y traducen en el borde, en `$lib/server/canciones`. Al hacerlo, tres funciones pasan a devolver `false` cuando el slug no existe, que antes se guardaba tal cual: se podía meter en el carrito un tema inventado. El script de guardas estaba roto desde que se volcó el catálogo —usaba slugs de verdad y moría en la primera inserción por clave duplicada, sin comprobar nada—. Ahora usa el prefijo `zz-` y el país ZZ, que ISO 3166 reserva para uso privado. Y la prueba de la valoración pasaba por el motivo equivocado: rechazaba el seis porque la columna ya no existía, no por el rango. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
values ('f1', 'inexistente', 'A', 1, 'rectificativa', 100, 21, 21, 121, '{}', 'ZZ')`
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
);
La puerta del panel, y una portada que dice qué falta Fase 6. Está lo que todo lo demás necesita: quién entra y por dónde. El rol vive en `usuario.rol`, con un CHECK que impide inventarse uno, y se da con `npm run db:admin`. En una variable de entorno con correos habría sido más rápido, pero entonces revocar a alguien pide un despliegue y a la pregunta «quién administra esto» solo sabe contestar quien pueda leer la configuración del servidor. La guarda está en el `+layout.server.ts` de la carpeta y no en cada ruta: una pantalla nueva queda protegida por el hecho de estar dentro. Sin sesión, a entrar con la vuelta puesta; con sesión y sin el rol, 404 y no 403, porque un 403 confirma que el panel existe y dónde vive. Los tres casos, comprobados contra la aplicación de verdad. La portada contesta «qué falta» y no «cuánto hay». Cada sección trae lo que tiene, lo que está a medias y los nombres de lo que está a medias: decir «1 género sin ficha» obliga a ir a buscar cuál, y esa búsqueda ya la ha hecho la consulta. Dos viajes a la base para las siete filas. Fuera Sveltia CMS y `static/admin/`. Editaba los Markdown de `src/content/`, que desde la fase 4 no lee nadie: guardaba cambios que no salían en pantalla, y además tapaba la ruta nueva. Y de paso, la prueba de contacto que fallaba en la tanda completa y pasaba a solas. No era aislamiento: `page.goto` vuelve al cargar, no al hidratar, y al hidratar Svelte reescribe el valor de cada `input` con el del componente. Lo tecleado antes de ese instante se perdía, y solo le pasaba al primer campo y solo con la máquina cargada. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
4 weeks ago
apuntar('\nEL ROL DE LA CUENTA');
await rechaza('no se puede inventar un rol', `update usuario set rol = 'adminn' where id = 'u9'`);
await permite('y quien administra, sí', `update usuario set rol = 'admin' where id = 'u9'`);
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
apuntar('\nLA VALORACIÓN');
await rechaza(
'no se puede puntuar con un seis',
Las compras dejan de apuntar a un texto y apuntan a la canción Fase 3 del modelo, que iba primero por ser lo único que podía costarle dinero a alguien: siete tablas guardaban `cancion_slug` sin clave foránea, porque cuando se escribieron el catálogo vivía en archivos y no había a qué apuntar. Renombrar un slug dejaba huérfana una compra en silencio, y quien la había pagado se enteraba al intentar descargar. Ahora apuntan a `cancion.id`, que es opaco y no cambia. El borrado no se propaga igual en todas: `compra`, `pedido_item` y `descarga` lo RESTRINGEN —una canción pagada no se puede borrar, y el registro de entrega tiene que sobrevivir a lo que prueba—; `carrito_item`, `favorito`, `valoracion` y `reproduccion` caen con ella. En dos migraciones y no en una: entre la 0007 y la 0008 conviven las dos columnas, así que el despliegue no tiene que parar el sitio. La 0007 se niega a seguir si alguna compra apunta a un slug que ya no existe: borrarla en silencio sería repetir el fallo que esto viene a arreglar. Por fuera nada cambia de forma. Los módulos siguen recibiendo slugs —es lo que hay en las URL— y traducen en el borde, en `$lib/server/canciones`. Al hacerlo, tres funciones pasan a devolver `false` cuando el slug no existe, que antes se guardaba tal cual: se podía meter en el carrito un tema inventado. El script de guardas estaba roto desde que se volcó el catálogo —usaba slugs de verdad y moría en la primera inserción por clave duplicada, sin comprobar nada—. Ahora usa el prefijo `zz-` y el país ZZ, que ISO 3166 reserva para uso privado. Y la prueba de la valoración pasaba por el motivo equivocado: rechazaba el seis porque la columna ya no existía, no por el rango. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
`insert into valoracion (usuario_id, cancion_id, puntuacion)
values ('u9', 'c1', 6)`
);
apuntar('');
apuntar('LAS COMPRAS APUNTAN A ALGO');
/*
* Lo que la fase 3 vino a arreglar. Antes de ella estas tablas guardaban el
* slug como texto suelto, así que las tres cosas que aquí se rechazan pasaban
* sin que saltara nada: se podía marcar como favorito un tema inventado y
* borrar del catálogo uno que alguien había pagado.
*
* La cuenta y el pedido se preparan antes de la prueba de la valoración
* porque esta los necesita: un seis tiene que rechazarse por el rango, no por
* una clave foránea que no encuentra al usuario.
*/
await permite(
'una compra apunta a una canción que existe',
`insert into compra (usuario_id, cancion_id, pedido_id) values ('u9', 'c1', 'o9')`
);
await rechaza(
'no se puede comprar un tema que no existe',
`insert into compra (usuario_id, cancion_id, pedido_id) values ('u9', 'no-existe', 'o9')`
);
await rechaza(
'ni borrar del catálogo un tema que alguien ha pagado',
`delete from cancion where id = 'c1'`
);
await rechaza(
'ni marcar como favorito un tema inventado',
`insert into favorito (usuario_id, cancion_id) values ('u9', 'no-existe')`
);
await permite(
'un tema sin comprar sí se retira del catálogo',
`update cancion set estado = 'retirado' where id = 'c3'`
Fase 2: el esquema entero en PostgreSQL 52 tablas nuevas, una vista y las guardas que Drizzle no sabe expresar. El sitio sigue leyendo de los archivos: esto solo crea el sitio donde caben. El esquema pasa de archivo a carpeta —con el catálogo dentro pasaría de las dos mil líneas—, y el corte se hizo por rangos de línea para no reescribir los comentarios que explican decisiones. Se comprobó que era un no-op: «No schema changes, nothing to migrate». Lo que Drizzle no genera va escrito a mano al final de la migración, y es justo lo que impide que el modelo mienta: la clave foránea de las versiones contra su propia tabla, dos columnas generadas y una clave compuesta que impiden a la vez la versión de una versión y los ciclos, la vista `cancion_principal`, el disparador que exige que el reparto de autoría sume cien, el que impide ciclos en el linaje de géneros, y los índices parciales del estilo principal y de la arista principal. Y las guardas se comprueban intentando lo que deben rechazar, en `scripts/db/comprobar-guardas.mjs`. Que una restricción exista en `pg_constraint` no significa que impida nada. Tres cosas que salieron de hacerlo y no de suponerlo: - `creado_en` era obligatorio SIN valor por defecto en la base: lo ponía JavaScript al insertar con Drizzle, así que cualquier INSERT escrito a mano fallaba y la invariante la sostenía la aplicación, no la tabla. Ahora es `defaultNow()`. - `drizzle-kit migrate` se atasca con los cuerpos `$$` de plpgsql. La migración se aplicó con `scripts/db/probar-migracion.mjs`, que además dice en qué sentencia falla, y se anotó en su registro. - Dos pruebas pasaban por el motivo equivocado: una por clave duplicada en vez de por la suma, y otra porque un disparador aplazado no salta dentro de un savepoint que nunca se cierra. Las dos corregidas; una guarda dada por buena sin ejecutarse es peor que no tenerla. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 month ago
);
await c.query('ROLLBACK');
apuntar('\n(todo deshecho: la base queda como estaba)');
await c.end();

Powered by TurnKey Linux.