---
name: android-db-migration
description: >
  Room-Datenbankmigration in der Bollwerk-App: Schema-Version erhöhen, @AutoMigration hinzufügen,
  manuelle Migration schreiben, MigrationTestHelper-Tests erstellen, fallbackToDestructiveMigration
  entfernen/vermeiden. Nutze diesen Skill immer wenn es um Room-Schema-Änderungen, DB-Versionen,
  Migrationen, Spalten umbenennen/löschen, neue Tabellen oder Migrationstests geht.
  Trigger-Phrasen: "migration", "DB-Version", "Schema", "Room", "AutoMigration", "fallback",
  "Datenverlust", "Spalte hinzufügen/umbenennen", "BollwerkDatabase", "DatabaseModule".
---

# Skill: Android DB-Migration (Room)

Vollständige Anleitung zur sicheren Room-Migration in der Bollwerk-App – vom Schema-Bump über @AutoMigration bis zum MigrationTestHelper-Test.

---

## Projektkontext

| Datei                                                           | Zweck                                                                    |
| --------------------------------------------------------------- | ------------------------------------------------------------------------ |
| `app/src/main/java/de/bollwerk/app/data/db/BollwerkDatabase.kt` | `@Database`-Annotation, Versionsnummer, `autoMigrations`-Liste           |
| `app/src/main/java/de/bollwerk/app/data/db/Migrations.kt`       | Manuelle `Migration(X, Y)`-Objekte (nur bei AutoMigration-Grenzen nötig) |
| `app/src/main/java/de/bollwerk/app/di/DatabaseModule.kt`        | Room Builder – enthält **kein** `fallbackToDestructiveMigration()`       |
| `app/schemas/de.bollwerk.app.data.db.BollwerkDatabase/`         | Exportierte Schema-JSON-Dateien (auto-generiert via KSP)                 |
| `app/src/androidTest/.../BollwerkDatabaseMigrationTest.kt`      | `MigrationTestHelper`-Tests                                              |

**KSP-Konfiguration** (`app/build.gradle.kts`):

```kotlin
ksp { arg("room.schemaLocation", "$projectDir/schemas") }
```

```kotlin
sourceSets { getByName("androidTest").assets.srcDirs("$projectDir/schemas") }
```

→ Schema-JSONs werden bei jedem Build automatisch aktualisiert und sind als Assets für Migrationstests verfügbar.

---

## Goldene Regeln

1. **Niemals** `fallbackToDestructiveMigration()` verwenden – die App crasht bewusst, wenn kein Pfad definiert ist (Datenverlust bleibt sichtbar).
2. **Jede** Versionserhöhung braucht einen `@AutoMigration`- oder manuellen `Migration`-Eintrag.
3. **Immer** nach dem Schema-Bump bauen, damit die neue `N.json` generiert wird, bevor Tests geschrieben werden.
4. Für Spalten-Umbenennungen **immer** `@RenameColumn`-Spec angeben (AutoMigration erkennt Rename nicht automatisch).

---

## Checkliste: Schema-Änderung

```
[ ] 1. Entity-Datei anpassen (Spalte/Tabelle hinzufügen, umbenennen, löschen)
[ ] 2. BollwerkDatabase.kt: version auf N+1 erhöhen
[ ] 3. BollwerkDatabase.kt: autoMigrations um AutoMigration(from=N, to=N+1) ergänzen
         → Bei Rename: spec = MeineRenameSpec::class angeben
         → Bei komplexer Migration: manuelles Migration-Objekt in Migrations.kt
[ ] 4. Build ausführen → N+1.json wird generiert
[ ] 5. MigrationTestHelper-Test in BollwerkDatabaseMigrationTest.kt schreiben
[ ] 6. Build + Tests grün
```

---

## BollwerkDatabase.kt – Muster

```kotlin
@Database(
    entities = [CategoryEntity::class, LocationEntity::class, ItemEntity::class,
                SettingsEntity::class, PendingSyncOpEntity::class, MessageEntity::class],
    version = 8,                        // ← erhöhen
    exportSchema = true,
    autoMigrations = [
        AutoMigration(from = 5, to = 6),   // No-op (gleiche Schemas)
        AutoMigration(from = 6, to = 7),   // No-op (Infrastruktur-Baseline)
        AutoMigration(from = 7, to = 8),   // z.B. neue Spalte
        // AutoMigration(from = 8, to = 9, spec = MeineSpec::class)  ← bei Rename
    ]
)
@TypeConverters(LocalDateConverter::class)
internal abstract class BollwerkDatabase : RoomDatabase() { ... }
```

---

## AutoMigration-Fälle

### Fall 1: Neue Spalte mit Default

```kotlin
// Entity: neue Spalte mit @ColumnInfo(defaultValue = "")
@ColumnInfo(name = "barcode", defaultValue = "") val barcode: String = ""

// @Database:
AutoMigration(from = 7, to = 8)
// Kein Spec nötig – Room erkennt ADD COLUMN automatisch
```

### Fall 2: Spalte löschen

```kotlin
// Spec-Klasse anlegen (in Migrations.kt oder eigenem File):
@DeleteColumn(tableName = "items", columnName = "legacy_field")
class Migration7To8Spec : AutoMigrationSpec

// @Database:
AutoMigration(from = 7, to = 8, spec = Migration7To8Spec::class)
```

### Fall 3: Spalte umbenennen

```kotlin
@RenameColumn(tableName = "items", fromColumnName = "kcal_per_kg", toColumnName = "kcal_per_unit")
class Migration7To8Spec : AutoMigrationSpec

// @Database:
AutoMigration(from = 7, to = 8, spec = Migration7To8Spec::class)
```

### Fall 4: Komplexe Migration (Daten transformieren)

Wenn AutoMigration nicht reicht (z. B. Daten kopieren, Tabellen zusammenführen):

```kotlin
// In Migrations.kt:
internal val MIGRATION_7_8 = object : Migration(7, 8) {
    override fun migrate(db: SupportSQLiteDatabase) {
        db.execSQL("ALTER TABLE items ADD COLUMN quantity_unit TEXT NOT NULL DEFAULT 'Stück'")
        db.execSQL("UPDATE items SET quantity_unit = unit WHERE unit IS NOT NULL")
    }
}

// In DatabaseModule.kt:
Room.databaseBuilder(context, BollwerkDatabase::class.java, "bollwerk.db")
    .addCallback(DefaultDataCallback)
    .addMigrations(MIGRATION_7_8)
    .build()
```

---

## MigrationTestHelper-Test

```kotlin
@get:Rule
val migrationTestHelper = MigrationTestHelper(
    InstrumentationRegistry.getInstrumentation(),
    BollwerkDatabase::class.java
)

@Test
fun migrate7To8_preservesData() {
    // 1. DB im alten Zustand anlegen und Testdaten einfügen
    migrationTestHelper.createDatabase(dbName, 7).use { db ->
        db.execSQL("INSERT INTO categories (name) VALUES ('Lebensmittel')")
        db.execSQL(
            "INSERT INTO items (id, name, category_id, quantity, unit, unit_price, " +
            "kcal_per_unit, expiry_date, location_id, notes, last_updated) " +
            "VALUES ('item-1', 'Wasser', 1, 10.0, 'Liter', 0.5, NULL, NULL, 1, '', 0)"
        )
    }

    // 2. Migration durchführen und Schema validieren
    migrationTestHelper.runMigrationsAndValidate(dbName, 8, true)
        .use { db ->
            // 3. Datenpersistenz prüfen
            db.query("SELECT id, name FROM items").use { cursor ->
                assertTrue(cursor.moveToFirst())
                assertEquals("item-1", cursor.getString(0))
            }
        }
}
```

**Wichtig:** Die Schema-JSON-Dateien aus `app/schemas/` müssen als Assets im androidTest-Quellsatz liegen (bereits konfiguriert). Der `MigrationTestHelper` liest sie automatisch.

---

## Häufige Fehler & Lösungen

| Fehler                                                                      | Ursache                                                     | Lösung                                                           |
| --------------------------------------------------------------------------- | ----------------------------------------------------------- | ---------------------------------------------------------------- |
| `IllegalStateException: A migration from X to Y was required but not found` | Version erhöht ohne AutoMigration-Eintrag                   | `@AutoMigration(from=X, to=Y)` in `@Database` eintragen          |
| `Cannot find implementation for AutoMigration`                              | Build noch nicht nach Schema-Änderung ausgeführt            | `./gradlew assembleDebug` ausführen, `Y.json` muss existieren    |
| `Expected X migrations, found Y`                                            | `addMigrations()` fehlt für manuelle Migration              | In `DatabaseModule.kt` `.addMigrations(MIGRATION_X_Y)` eintragen |
| `MigrationTestHelper: No schema file found for version X`                   | Schema-JSON für Version X existiert nicht in `app/schemas/` | Build mit Version X zuerst ausführen, JSON committen             |
| Rename-Spalte → Datenverlust                                                | AutoMigration interpretiert Rename als DROP+ADD             | `@RenameColumn`-Spec zwingend angeben                            |

---

## Aktuelle DB-Version

Aktuelle Produktionsversion: **7** (`BollwerkDatabase.kt`, `version = 7`)

Migrations-Pfade vorhanden:

- v5 → v6: No-op (identische Schemas, `@AutoMigration`)
- v6 → v7: No-op (Infrastruktur-Baseline, `@AutoMigration`)

Nächste Version für neue Schema-Änderung: **v8**

---

## Hinweis: Server-Migration (Flyway)

Für Schema-Änderungen auf dem **Server** (PostgreSQL + Exposed):

- SQL-Skripte unter `server/src/main/resources/db/migration/`
- Namenskonvention: `V{N}__{beschreibung}.sql`
- Flyway ist integriert (`baselineOnMigrate = true`) – läuft beim Server-Start automatisch
- Bestehende Datenbanken werden auf V1 gebaselined; neue Skripte werden einmalig ausgeführt

```sql
-- Beispiel: server/src/main/resources/db/migration/V3__add_barcode_column.sql
ALTER TABLE items ADD COLUMN IF NOT EXISTS barcode VARCHAR(100);
```

`IF NOT EXISTS` / `IF EXISTS` in SQL verwenden, um Idempotenz zu gewährleisten.
