Read the whole ALTER TABLE, not just its first clause
The schema/type drift check has been red in CI. It reported seven
discrepancies, and all seven were the checker's own fault.
The migrations put several clauses in one statement:
alter table employees
drop column if exists division_id,
drop column if exists team_id,
drop column if exists manager_id,
drop column if exists org_level,
drop column if exists is_lead;
The old pattern matched `alter table (\w+)\s+drop column (\w+)` as a single
regex, which finds exactly the first clause. So division_id was dropped from
the model and the other four stayed in it — the checker insisted four columns
existed that the OM cutover removed a year ago. The same cut the other way for
`add column`: kuendigungsschutz_bis, teilzeit_bis and aufenthaltstitel_bis are
each the second clause of their statement, so the checker never saw them and
called them typed-but-absent.
Now the statement is collected up to its terminating semicolon — with paren
depth tracked, so a semicolon inside a check constraint does not end it early
— and every clause inside is applied.
Verified by mutation, not by the green result alone: putting an invented
column into types.ts is caught, and reverting the parser to read only the
first clause brings back exactly those seven messages. That is the diagnosis
confirmed, not merely a passing run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
@@ -80,16 +80,51 @@ function parseMigrations() {
|
|||||||
tables.set(name, columns);
|
tables.set(name, columns);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const m of sql.matchAll(/alter table (?:if exists )?(\w+)\s+add column (?:if not exists )?(\w+)/gi)) {
|
// ── ALTER TABLE, vollständig ────────────────────────────────────
|
||||||
tables.get(m[1])?.add(m[2].toLowerCase());
|
//
|
||||||
}
|
// Eine Anweisung kann mehrere Klauseln tragen, durch Komma getrennt:
|
||||||
for (const m of sql.matchAll(/alter table (?:if exists )?(\w+)\s+drop column (?:if exists )?(\w+)/gi)) {
|
//
|
||||||
tables.get(m[1])?.delete(m[2].toLowerCase());
|
// alter table employees
|
||||||
}
|
// drop column if exists division_id,
|
||||||
for (const m of sql.matchAll(/alter table (?:if exists )?(\w+)\s+rename column (\w+) to (\w+)/gi)) {
|
// drop column if exists team_id,
|
||||||
const t = tables.get(m[1]);
|
// drop column if exists manager_id;
|
||||||
if (t?.delete(m[2].toLowerCase())) t.add(m[3].toLowerCase());
|
//
|
||||||
|
// Wer nur nach `alter table … drop column …` als **einem** Muster sucht,
|
||||||
|
// findet davon genau die erste. Die übrigen bleiben im Modell stehen, und der
|
||||||
|
// Abgleich meldet Spalten als vorhanden, die es seit einem Jahr nicht
|
||||||
|
// mehr gibt — genau die vier aus dem OM-Umbau. Umgekehrt fehlten drei
|
||||||
|
// später ergänzte Spalten, weil sie jeweils die zweite Klausel eines
|
||||||
|
// `add column` waren.
|
||||||
|
//
|
||||||
|
// Deshalb wird hier erst die ganze Anweisung bis zum Semikolon
|
||||||
|
// aufgesammelt und dann jede Klausel darin ausgewertet. Klammern zählen
|
||||||
|
// mit, damit ein Semikolon in einer Prüfbedingung die Anweisung nicht
|
||||||
|
// vorzeitig beendet.
|
||||||
|
for (const kopf of sql.matchAll(/alter table (?:if exists )?(\w+)/gi)) {
|
||||||
|
const tabelle = tables.get(kopf[1]);
|
||||||
|
if (!tabelle) continue;
|
||||||
|
|
||||||
|
let tiefe = 0;
|
||||||
|
let ende = kopf.index + kopf[0].length;
|
||||||
|
for (; ende < sql.length; ende++) {
|
||||||
|
const c = sql[ende];
|
||||||
|
if (c === "(") tiefe++;
|
||||||
|
else if (c === ")") tiefe--;
|
||||||
|
else if (c === ";" && tiefe === 0) break;
|
||||||
|
}
|
||||||
|
const anweisung = sql.slice(kopf.index + kopf[0].length, ende);
|
||||||
|
|
||||||
|
for (const m of anweisung.matchAll(/\badd column (?:if not exists )?(\w+)/gi)) {
|
||||||
|
tabelle.add(m[1].toLowerCase());
|
||||||
|
}
|
||||||
|
for (const m of anweisung.matchAll(/\bdrop column (?:if exists )?(\w+)/gi)) {
|
||||||
|
tabelle.delete(m[1].toLowerCase());
|
||||||
|
}
|
||||||
|
for (const m of anweisung.matchAll(/\brename column (\w+) to (\w+)/gi)) {
|
||||||
|
if (tabelle.delete(m[1].toLowerCase())) tabelle.add(m[2].toLowerCase());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const m of sql.matchAll(/drop table (?:if exists )?(\w+)/gi)) {
|
for (const m of sql.matchAll(/drop table (?:if exists )?(\w+)/gi)) {
|
||||||
tables.delete(m[1]);
|
tables.delete(m[1]);
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user