Migrationen erzeugen, ohne die Suche kaputtzumachen

"prisma migrate diff" kennt die Trigramm-Indizes aus
prisma/sql/volltextsuche.sql nicht und schreibt deshalb in jede
Migration ein DROP INDEX dafuer. Fuenfmal mussten die Zeilen von Hand
raus. Auf dem Server waere es folgenlos geblieben - update.sh legt die
Indizes hinterher neu an -, in der Entwicklung dagegen waere die Suche
still langsam geworden.

  npm run db:migration -- <name> [--anwenden]
  npm run db:pruefen

Aufbau:
- Die Liste der geschuetzten Indizes steht an einer Stelle. Wer in
  prisma/sql/ einen hinzufuegt, traegt ihn dort ein.
- dropZeilenEntfernen() ist bewusst eng: Nur eine DROP-Anweisung, die
  einen geschuetzten Index nennt, faellt weg. Ein DROP INDEX auf einen
  Prisma-eigenen Index bleibt stehen - der soll ja weg. Ein Kommentar,
  der den Namen bloss erwaehnt, ebenfalls.
- Der Kopf der erzeugten Datei sagt, was entfernt wurde und warum.
- Nach --anwenden wird gegen die Datenbank geprueft, ob die Indizes
  wirklich noch stehen. Das ist der Unterschied zwischen "sollte
  gutgegangen sein" und "nachgesehen".

Beim Probelauf gefunden und behoben: Stimmen Schema und Datenbank
ueberein, besteht der ganze Diff aus den beiden bekannten DROP-Zeilen.
Das Skript legte dafuer eine leere Migration an - und eine leere
Migration in der Historie ist schlimmer als keine, weil sie als
angewendet gilt und niemand mehr weiss, wofuer sie gut war. Jetzt bricht
es vorher ab, ohne einen Ordner zu hinterlassen.

Die Typen liegen als .d.mts daneben: Das Skript bleibt .mjs und laeuft
ohne Bauschritt, die Tests laufen trotzdem nicht gegen "any".

Geprueft: zwoelf Einheitstests, dazu von Hand der Lauf mit echter
Schemaaenderung und der Lauf ohne.

Refs #24
This commit is contained in:
Florian Wilde
2026-08-23 19:25:09 +02:00
parent 98d1338eae
commit 3153e6179d
4 changed files with 458 additions and 0 deletions
+128
View File
@@ -0,0 +1,128 @@
import { describe, expect, it } from 'vitest'
import {
dropZeilenEntfernen,
hatAnweisungen,
kopfBauen,
ordnernameBauen,
} from '../scripts/migration-erzeugen.mjs'
/**
* Der Migrations-Wrapper.
*
* Die eine Sache, die hier schiefgehen darf und nicht schiefgehen soll:
* zu viel entfernen. Ein DROP INDEX, das Prisma zu Recht schreibt, muss
* stehen bleiben - sonst repariert das Skript die Suche und zerlegt
* dafuer das Schema.
*/
const GESCHUETZT = [
{ name: 'dokument_absender_trgm', quelle: 'prisma/sql/volltextsuche.sql' },
{ name: 'vorgang_titel_trgm', quelle: 'prisma/sql/volltextsuche.sql' },
]
describe('DROP-Zeilen entfernen', () => {
it('nimmt die geschützte Anweisung samt Überschrift heraus', () => {
const roh = [
'-- DropIndex',
'DROP INDEX "dokument_absender_trgm";',
'',
'-- AlterTable',
'ALTER TABLE "kontakt" ADD COLUMN "anrede" TEXT;',
].join('\n')
const { sql, entfernt } = dropZeilenEntfernen(roh, GESCHUETZT)
expect(entfernt).toEqual(['dokument_absender_trgm'])
expect(sql).not.toContain('DROP INDEX')
expect(sql).not.toContain('DropIndex')
expect(sql).toContain('ALTER TABLE "kontakt"')
})
it('lässt ein DROP INDEX auf einen Prisma-eigenen Index stehen', () => {
// Der Kern der Sache: Das Skript darf nicht alles wegräumen, was nach
// DROP INDEX aussieht - manche sollen wirklich weg.
const roh = ['-- DropIndex', 'DROP INDEX "vertrag_betreuteId_status_idx";'].join('\n')
const { sql, entfernt } = dropZeilenEntfernen(roh, GESCHUETZT)
expect(entfernt).toEqual([])
expect(sql).toContain('DROP INDEX "vertrag_betreuteId_status_idx";')
expect(sql).toContain('-- DropIndex')
})
it('entfernt mehrere geschützte Anweisungen und meldet beide', () => {
const roh = [
'-- DropIndex',
'DROP INDEX "dokument_absender_trgm";',
'',
'-- DropIndex',
'DROP INDEX "vorgang_titel_trgm";',
'',
'-- CreateTable',
'CREATE TABLE "schreibvorlage" ();',
].join('\n')
const { sql, entfernt } = dropZeilenEntfernen(roh, GESCHUETZT)
expect(entfernt).toEqual(['dokument_absender_trgm', 'vorgang_titel_trgm'])
expect(sql.startsWith('-- CreateTable')).toBe(true)
})
it('verträgt IF EXISTS und abweichende Schreibweise', () => {
const roh = 'drop index if exists "vorgang_titel_trgm";'
expect(dropZeilenEntfernen(roh, GESCHUETZT).entfernt).toEqual(['vorgang_titel_trgm'])
})
it('lässt eine Migration ohne Treffer unverändert', () => {
const roh = ['-- AlterTable', 'ALTER TABLE "vorgang" ADD COLUMN "x" TEXT;'].join('\n')
const { sql, entfernt } = dropZeilenEntfernen(roh, GESCHUETZT)
expect(entfernt).toEqual([])
expect(sql).toBe(roh)
})
it('greift nicht bei einem bloßen Vorkommen des Namens im Text', () => {
// Ein Kommentar, der den Index nennt, ist keine Anweisung.
const roh = '-- dokument_absender_trgm bleibt bestehen'
const { sql, entfernt } = dropZeilenEntfernen(roh, GESCHUETZT)
expect(entfernt).toEqual([])
expect(sql).toBe(roh)
})
})
describe('Leere Migration erkennen', () => {
it('sieht eine echte Anweisung', () => {
expect(hatAnweisungen('-- AlterTable\nALTER TABLE "x" ADD COLUMN "y" TEXT;')).toBe(true)
})
it('hält reinen Kommentartext für leer', () => {
// Der Normalfall: Schema und Datenbank stimmen überein, der ganze
// Diff bestand aus den zwei bekannten DROP-Zeilen. Dann darf keine
// Migration entstehen — eine leere in der Historie ist schlimmer als
// keine.
expect(hatAnweisungen('-- nur ein Kommentar\n\n-- noch einer')).toBe(false)
})
it('hält Leerraum für leer', () => {
expect(hatAnweisungen(' \n\n ')).toBe(false)
})
})
describe('Kopfkommentar', () => {
it('benennt die entfernten Indizes und ihre Quelle', () => {
const kopf = kopfBauen('ansprechpartner', ['vorgang_titel_trgm'], GESCHUETZT)
expect(kopf).toContain('vorgang_titel_trgm')
expect(kopf).toContain('prisma/sql/volltextsuche.sql')
})
it('sagt auch, wenn nichts zu entfernen war', () => {
expect(kopfBauen('leer', [], GESCHUETZT)).toContain('Nichts zu entfernen')
})
})
describe('Ordnername', () => {
it('baut den Zeitstempel im Prisma-Format', () => {
expect(ordnernameBauen('anrede', new Date(2026, 7, 23, 9, 5, 3))).toBe(
'20260823090503_anrede',
)
})
})