# Migration auf utf8mb4 — Konzept

> **Status:** Konzept, noch nicht umgesetzt. Prod bleibt bis zur Kundenfreigabe
> unangetastet (siehe [`angebots-grundlage.md`](angebots-grundlage.md)).

## Warum

Die Datenbank speichert Text derzeit in **latin1**, die Anwendung verbindet
sich aber mit **utf8** (`mysqli_set_charset($db, "utf8")` in `db.php`). Das
funktioniert nur zufällig für Zeichen, die es in beiden Zeichensätzen gibt
(a–z, Umlaute, Guillemets). Sobald ein Zeichen ausserhalb von latin1 auftaucht,
scheitert das INSERT unter `STRICT_TRANS_TABLES` **hart und still**.

Konkret aufgefallen bei Pos 9 (Projektstatus-Journal): Der Pfeil `→` (U+2192)
liegt nicht in latin1, das INSERT brach mit
`Incorrect string value: '\xE2\x86\x92 …'` ab. Als Sofortmassnahme steht dort
jetzt der ASCII-Pfeil `->`.

**utf8mb4** ist heute der Standard-Zeichensatz für MySQL/MariaDB: vollständiges
Unicode inklusive Emoji, typografischer Zeichen (`→`, `–`, `…`, „gedankenstrich")
und aller Fremdsprachen. Eine Migration behebt die Fehlerquelle grundsätzlich
statt zeichenweise.

## Ausgangslage prüfen — echtes latin1 oder Doppelkodierung?

Der kritische Punkt jeder latin1→utf8mb4-Migration: Sind die gespeicherten Bytes
**echtes latin1** oder bereits als latin1 abgelegte UTF-8-Bytes („Mojibake",
Doppelkodierung)? Beide brauchen einen **unterschiedlichen** Konvertierungsweg,
und wer sie verwechselt, zerstört die Daten.

Ein Stichprobentest an `tabNotiz` hat gezeigt: `ä` ist als **`0xE4`** gespeichert
— das ist echtes latin1 (nicht `0xC3 0xA4`, was doppelt-kodiertes UTF-8 wäre).

**Das ist die gute Nachricht:** Bei echtem latin1 ist die Konvertierung sauber
über `CONVERT TO CHARACTER SET` bzw. einen korrekt deklarierten Dump möglich.

Vor der Migration trotzdem breit gegenprüfen (nicht nur eine Tabelle):

```sql
-- Verdächtige Doppelkodierung: UTF-8-typische Bytefolgen in latin1-Spalten
-- (z.B. Ã¤ = 0xC3 0xA4). Findet die Query Treffer, liegt Mojibake vor.
SELECT notID, notNotiz FROM tabNotiz WHERE notNotiz LIKE '%Ã%' OR notNotiz LIKE '%Â%';
```

## Konvertierungsweg (bei echtem latin1)

Pro Tabelle:

```sql
ALTER TABLE tabNotiz CONVERT TO CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

`CONVERT TO CHARACTER SET` liest die Bytes im alten Zeichensatz und schreibt sie
korrekt im neuen — der richtige Weg für echtes latin1. (Wäre es Mojibake, müsste
man stattdessen über einen Zwischenschritt `… CONVERT TO CHARACTER SET binary`
gehen — hier laut Prüfung **nicht** nötig.)

Zusätzlich die Datenbank-Defaults und die Verbindung umstellen:

```sql
ALTER DATABASE `<db>` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
```

```php
// db.php
mysqli_set_charset($db, "utf8mb4");   // statt "utf8"
```

## Fallstricke

- **Index-Längen.** utf8mb4 braucht bis zu 4 Byte pro Zeichen. Bei alten
  InnoDB-Formaten (max. 767 Byte Schlüssellänge) können lange
  `VARCHAR`-Indizes die Grenze reissen. Prüfen:
  ```sql
  SELECT TABLE_NAME, COLUMN_NAME, CHARACTER_MAXIMUM_LENGTH
  FROM information_schema.COLUMNS
  WHERE TABLE_SCHEMA = DATABASE()
    AND DATA_TYPE IN ('varchar','char')
    AND CHARACTER_MAXIMUM_LENGTH > 191;
  ```
  Bei den kurzen Feldern des LBM (z.B. `tabStatus.staStatus` = `varchar(20)`)
  ist das unkritisch. `ROW_FORMAT=DYNAMIC` + `innodb_large_prefix` (in
  MariaDB/MySQL ≥ 5.7 Standard) heben die Grenze auf 3072 Byte.
- **Verbindungscharset muss mit.** Spalten und Verbindung gemeinsam umstellen,
  sonst kippt das Bild in die andere Richtung.
- **Collation-Wahl.** `utf8mb4_unicode_ci` (sprachlich korrekte Sortierung) statt
  `utf8mb4_general_ci` — für deutschsprachige Daten die bessere Wahl.
- **Prod-Umgebung.** utf8mb4 ist seit MySQL 5.5.3 / MariaDB 5.5 verfügbar; die
  PHP-Version (prod 7.2) ist irrelevant. Kein Versionshindernis.

## Vorgehen (Reihenfolge)

1. **dev zuerst.** Golden-Master-Baseline aufnehmen (`tests/goldenmaster.sh
   capture`) — als Vorher-Referenz.
2. Doppelkodierung breit gegenprüfen (Query oben) — Erwartung: keine Treffer.
3. Alle Tabellen `CONVERT TO CHARACTER SET utf8mb4`, DB-Default, Verbindung.
4. `db/schema.sql` neu exportieren (versionierte Struktur).
5. Golden-Master gegenprüfen (`… check`) + manuelle Sichtprüfung der Umlaute/
   Sonderzeichen in Journal, Namen, Dokumenttiteln.
6. Pos 9 kosmetisch auf `→` zurückstellen (optional).
7. **Erst nach Kundenfreigabe:** prod mit vorherigem Voll-Backup migrieren.

## Aufwand

Die Konvertierung selbst ist mechanisch und schnell. Der Aufwand steckt in der
**Prüfung** (Doppelkodierung, Index-Längen) und im **Test** (Golden-Master +
Sichtprüfung). Realistisch ein halber bis ganzer Tag inkl. Absicherung — deutlich
günstiger als die dauerhafte Fehlerquelle „stille INSERT-Abbrüche".
