Beim Umbau einer Tabelle gehen ihre Indizes nicht mehr verloren
GEFUNDEN BEIM NACHDENKEN DARUEBER, WAS DIE NAECHSTE AUSLIEFERUNG AUF DEM ECHTEN SERVER TUT -- nicht im Betrieb, nicht von einer Pruefung. checkListeErweitern() baut eine Tabelle neu, wenn eine CHECK-Regel erweitert werden muss: neue Tabelle, Daten hinueber, alte weg, umbenennen. Sechs Aufrufe gehen durch diese Funktion. `DROP TABLE` nimmt aber JEDEN Index der Tabelle mit, und der Bauplan aus sqlite_master beschreibt nur die Tabelle -- die neue stand danach blank da. Gemerkt haette es niemand: Die Abfragen laufen weiter, sie lesen nur die ganze Tabelle. Beim naechsten Neustart waere der Index wieder da (er steht oben im Bauplan). "Bis zum naechsten Neustart falsch" ist trotzdem kein Zustand, den man einbaut -- und bei einem EINDEUTIGEN Index waere es keine Frage der Geschwindigkeit mehr, sondern der Richtigkeit: Die Zusage "einen Kanal je Zustaendigkeit" haette bis zum Neustart still ausgesetzt. Beim Ausliefern der Kanaele wird `chat_raeume` genau so umgebaut. Der Lauf sagt jetzt selbst, was er getan hat: 'kanal' in chat_raeume.art freigeschaltet, 1 Zeilen, 7 Spalten, 1 von 1 Indizes uebernommen, Verweise geprueft. Und er meldet es als ACHTUNG, wenn nicht alle zurueckkommen. DIE PRUEFUNG LAEUFT JETZT AUF EINER ALTEN DATENBANK. Bisher legte pruef-chat-kanaele eine frische an -- dort steht 'kanal' schon im Bauplan, die Umstellung tut nichts, und der gefaehrlichste Weg im Haus blieb ungeprueft. Die Datenbank wird deshalb VOR dem Start in die alte Form gebracht, mit Index und einer Zeile darin. Geprueft wird danach, dass die Regel erweitert ist, die Spalte dazugekommen, die Zeile noch da und der Index zurueck. GEGENPROBE GEFAHREN: Wiederherstellung stillgelegt, Lauf wiederholt -- "FEHL der Index hat den Umbau ueberlebt (idx_chat_kanal_kategorie)". Sie kann also auch nein sagen. Der zweite, aeltere Umbauweg im selben Haus (die Artenumstellung fuer 'bigmatch') hat dieselbe Luecke. Er bleibt hier unangetastet: Das ist eine dritte Abschrift derselben gefaehrlichen Prozedur, und sie ohne eigene Pruefung anzufassen waere genau der Handgriff, der Daten kostet. Notiert, nicht nebenbei erledigt. pruef-chat-kanaele 73 -> 79 · pruef-spicy 60 · pruef-modi-ideen 30 · pruef-rollen 274. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
@@ -53,6 +53,38 @@ process.env.PORT = "4359";
|
||||
process.env.SITE_ACCESS_SECRET = "lokaler-test";
|
||||
process.env.SITE_PUBLIC_LAUNCH_AT = "2020-01-01T00:00:00+01:00";
|
||||
|
||||
/* ---- Die Datenbank steht ZUERST in der ALTEN Form ------------------------
|
||||
|
||||
WARUM DIESE PRUEFUNG NICHT AUF EINER FRISCHEN ANLAGE LAEUFT: Auf einer
|
||||
frischen steht 'kanal' schon im Bauplan, und die Umstellung tut
|
||||
nichts. Auf dem echten Server steht `chat_raeume` aber in der alten
|
||||
Form -- dort wird die Tabelle wirklich neu gebaut, mit Sicherung,
|
||||
Zeilenzaehlung und Umbenennen. Das ist der gefaehrlichste Weg im
|
||||
ganzen Haus, und er war bis heute nur auf dem Papier geprueft.
|
||||
|
||||
Der Index hier ist kein Beiwerk: `DROP TABLE` nimmt jeden Index mit,
|
||||
und der Bauplan aus sqlite_master beschreibt nur die Tabelle. Genau
|
||||
das ist die Zeile, die beweisen muss, dass er zurueckkommt. */
|
||||
{
|
||||
const { DatabaseSync: DB } = await import("node:sqlite");
|
||||
const alt = new DB(process.env.WORKSPACE_DB);
|
||||
alt.exec(`
|
||||
CREATE TABLE chat_raeume (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
art TEXT NOT NULL DEFAULT 'direkt'
|
||||
CHECK (art IN ('direkt','gruppe')),
|
||||
name TEXT,
|
||||
erstellt TEXT NOT NULL,
|
||||
erstellt_von INTEGER,
|
||||
letzte_am TEXT
|
||||
);
|
||||
CREATE INDEX IF NOT EXISTS idx_chat_raeume_letzte ON chat_raeume (letzte_am DESC);
|
||||
INSERT INTO chat_raeume (art, name, erstellt, letzte_am)
|
||||
VALUES ('gruppe','Von vorher','2026-01-01T00:00:00.000Z','2026-01-01T00:00:00.000Z');
|
||||
`);
|
||||
alt.close();
|
||||
}
|
||||
|
||||
const express = (await import("express")).default;
|
||||
const ec = express.response.cookie;
|
||||
express.response.cookie = function (n, w, o) { return ec.call(this, n, w, { ...(o || {}), secure: false }); };
|
||||
@@ -186,6 +218,39 @@ const drei = await anmelden("modi", "CODE-TEAM-0003");
|
||||
ok([dogi, hand, eins, zwei, drei].every((x) => x.ok && x.keks),
|
||||
"alle fuenf sind angemeldet");
|
||||
|
||||
/* =======================================================================
|
||||
0. Die Umstellung der alten Tabelle
|
||||
======================================================================= */
|
||||
console.log("");
|
||||
console.log("=== Die alte Tabelle wurde umgebaut ===");
|
||||
{
|
||||
const plan = d.prepare(
|
||||
"SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'chat_raeume'").get()?.sql || "";
|
||||
const regel = plan.match(/art\s+IN\s*\(([^)]*)\)/i)?.[1] || "";
|
||||
ok(regel.includes("'kanal'"), `die CHECK-Regel kennt jetzt 'kanal' (${regel})`);
|
||||
ok(regel.includes("'direkt'") && regel.includes("'gruppe'"),
|
||||
"und die beiden alten Arten stehen noch darin");
|
||||
|
||||
const spalten = d.prepare("PRAGMA table_info(chat_raeume)").all().map((z) => z.name);
|
||||
ok(spalten.includes("kategorie"), `die Spalte kategorie ist dazugekommen (${spalten.length} Spalten)`);
|
||||
|
||||
/* DIE ZEILE VON VORHER. Ohne sie waere "umgebaut" auch dann gruen,
|
||||
wenn die Tabelle einfach neu und leer waere. */
|
||||
const alt = d.prepare("SELECT name FROM chat_raeume WHERE name = 'Von vorher'").get();
|
||||
ok(!!alt, "die Zeile von vorher steht noch da");
|
||||
|
||||
/* UND DER INDEX. Er wurde vor dem Umbau angelegt, `DROP TABLE` hat
|
||||
ihn mitgenommen, und die Umstellung muss ihn zurueckgebracht
|
||||
haben. */
|
||||
const idx = d.prepare(
|
||||
"SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'chat_raeume'")
|
||||
.all().map((z) => z.name);
|
||||
ok(idx.includes("idx_chat_raeume_letzte"),
|
||||
`der Index hat den Umbau ueberlebt (${idx.join(", ") || "keiner"})`);
|
||||
ok(idx.includes("idx_chat_kanal_kategorie"),
|
||||
"und der neue eindeutige Index steht auch da");
|
||||
}
|
||||
|
||||
/* =======================================================================
|
||||
1. Einen Kanal anlegen -- und wer das darf
|
||||
======================================================================= */
|
||||
|
||||
+40
-1
@@ -799,6 +799,32 @@ function checkListeErweitern(d, tabelle, spalte, marker, werte, jetztStempel) {
|
||||
}
|
||||
const liste = spalten.map((n) => `"${n}"`).join(", ");
|
||||
|
||||
/* DIE INDIZES GEHEN MIT -- SONST GEHEN SIE VERLOREN (10.09.2026).
|
||||
|
||||
`DROP TABLE` nimmt jeden Index der Tabelle mit. Der Bauplan aus
|
||||
sqlite_master beschreibt nur die TABELLE, nicht ihre Indizes; die
|
||||
neue stand danach blank da.
|
||||
|
||||
Aufgefallen ist das nicht im Betrieb, sondern beim Nachdenken
|
||||
darueber, was diese Auslieferung auf dem echten Server tut: Dort
|
||||
wird `chat_raeume` umgebaut, und `idx_chat_raeume_letzte` waere
|
||||
danach weg gewesen. Gemerkt haette es niemand -- die Abfrage laeuft
|
||||
weiter, sie liest nur die ganze Tabelle. Beim naechsten Neustart
|
||||
stuende der Index wieder da (er steht oben im Bauplan), aber "bis
|
||||
zum naechsten Neustart falsch" ist kein Zustand, den man einbaut.
|
||||
|
||||
Bei einem EINDEUTIGEN Index waere es keine Frage der
|
||||
Geschwindigkeit mehr, sondern der Richtigkeit: Die Zusage "einen
|
||||
Kanal je Zustaendigkeit" haette bis zum Neustart still ausgesetzt.
|
||||
|
||||
`sql IS NULL` filtert die Indizes weg, die SQLite sich selbst zu
|
||||
einer UNIQUE-Spalte baut: Die entstehen mit der neuen Tabelle von
|
||||
allein, und ihr Name (`sqlite_autoindex_...`) ist gar nicht
|
||||
anlegbar. */
|
||||
const indizes = d.prepare(
|
||||
"SELECT sql FROM sqlite_master WHERE type = 'index' AND tbl_name = ? AND sql IS NOT NULL")
|
||||
.all(tabelle).map((z) => z.sql);
|
||||
|
||||
/* Fremdschluessel muessen aus sein, weil andere Tabellen auf
|
||||
personen(id) zeigen -- und das laesst sich nicht innerhalb einer
|
||||
Transaktion umschalten. */
|
||||
@@ -819,13 +845,26 @@ function checkListeErweitern(d, tabelle, spalte, marker, werte, jetztStempel) {
|
||||
d.exec(`DROP TABLE ${tabelle};`);
|
||||
d.exec(`ALTER TABLE ${tabelle}_neu RENAME TO ${tabelle};`);
|
||||
d.exec("COMMIT");
|
||||
/* ERST NACH DEM COMMIT. Waere zurueckgerollt worden, stuenden die
|
||||
alten Indizes noch -- ein zweites Anlegen waere dann ein Fehler
|
||||
ohne Anlass. */
|
||||
let indizesZurueck = 0;
|
||||
for (const bau of indizes) {
|
||||
try { d.exec(bau); indizesZurueck++; }
|
||||
catch (f) { console.error("[workspace] Index nach der Umstellung:", f?.message); }
|
||||
}
|
||||
if (indizesZurueck !== indizes.length) {
|
||||
console.error(`[workspace] ACHTUNG: nur ${indizesZurueck} von ${indizes.length} `
|
||||
+ `Indizes auf ${tabelle} wiederhergestellt. Sicherung: ${sicherung}`);
|
||||
}
|
||||
const kaputt = d.prepare("PRAGMA foreign_key_check").all();
|
||||
if (kaputt.length) {
|
||||
console.error("[workspace] ACHTUNG: nach der Umstellung", kaputt.length,
|
||||
"verwaiste Verweise. Sicherung liegt unter", sicherung);
|
||||
} else {
|
||||
console.log(`[workspace] '${marker}' in ${tabelle}.${spalte} freigeschaltet, `
|
||||
+ `${nachher} Zeilen, ${spalten.length} Spalten uebernommen, Verweise geprueft.`);
|
||||
+ `${nachher} Zeilen, ${spalten.length} Spalten, ${indizesZurueck} von `
|
||||
+ `${indizes.length} Indizes uebernommen, Verweise geprueft.`);
|
||||
}
|
||||
}
|
||||
} catch (fehler) {
|
||||
|
||||
Reference in New Issue
Block a user