diff --git a/server/pruef-chat-kanaele.mjs b/server/pruef-chat-kanaele.mjs index 564a8df9..5ce6e265 100644 --- a/server/pruef-chat-kanaele.mjs +++ b/server/pruef-chat-kanaele.mjs @@ -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 ======================================================================= */ diff --git a/server/workspace.js b/server/workspace.js index d614271f..e4add7d8 100644 --- a/server/workspace.js +++ b/server/workspace.js @@ -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) {