fix: Migrationen löschen keine abhängigen Daten mehr
Auf SQLite baut Alembic (render_as_batch) eine Tabelle um, indem es eine Kopie anlegt und das Original löscht. Bei DROP TABLE führt SQLite ein implizites DELETE aus – mit aktiven Fremdschlüsseln schlägt das per ON DELETE CASCADE auf abhängige Tabellen durch. Migration 0013 fügte `categories` nur ein Feld hinzu und vernichtete dabei alle Schlagwörter und Budget-Vorlagen und setzte die Kategorie sämtlicher Buchungen auf NULL. Reproduziert und behoben: Fremdschlüssel werden für die Dauer der Migration abgeschaltet (vor der Transaktion, sonst ignoriert SQLite den Befehl) und danach wieder aktiviert. Lässt sich das Abschalten nicht bestätigen, bricht die Migration ab, statt Daten zu riskieren. Regressionstest legt Daten auf Stand 0012 an, migriert auf head und prüft, dass Schlagwörter, Budget-Vorlagen und Kategoriezuordnungen erhalten bleiben. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -34,14 +34,37 @@ def run_migrations_offline() -> None:
|
||||
|
||||
def run_migrations_online() -> None:
|
||||
with engine.connect() as connection:
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
render_as_batch=True,
|
||||
compare_type=True,
|
||||
)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
# Fremdschlüssel für die Dauer der Migration abschalten.
|
||||
#
|
||||
# ``render_as_batch`` baut eine Tabelle um, indem es eine Kopie anlegt und
|
||||
# das Original löscht. SQLite führt bei DROP TABLE ein implizites DELETE
|
||||
# aus – mit aktiven Fremdschlüsseln schlägt das per ON DELETE CASCADE auf
|
||||
# abhängige Tabellen durch. Ein zusätzliches Feld an ``categories`` löschte
|
||||
# so sämtliche Schlagwörter und Budget-Vorlagen und setzte die Kategorie
|
||||
# aller Buchungen auf NULL.
|
||||
#
|
||||
# Der Befehl muss VOR der Transaktion laufen, sonst ignoriert SQLite ihn.
|
||||
roh = connection.connection
|
||||
roh.rollback()
|
||||
roh.execute("PRAGMA foreign_keys=OFF")
|
||||
aktiv = roh.execute("PRAGMA foreign_keys").fetchone()[0]
|
||||
if aktiv:
|
||||
raise RuntimeError(
|
||||
"Fremdschlüssel liessen sich nicht abschalten – Migration abgebrochen, "
|
||||
"sonst droht Datenverlust beim Tabellen-Umbau."
|
||||
)
|
||||
try:
|
||||
context.configure(
|
||||
connection=connection,
|
||||
target_metadata=target_metadata,
|
||||
render_as_batch=True,
|
||||
compare_type=True,
|
||||
)
|
||||
with context.begin_transaction():
|
||||
context.run_migrations()
|
||||
finally:
|
||||
roh.rollback()
|
||||
roh.execute("PRAGMA foreign_keys=ON")
|
||||
|
||||
|
||||
if context.is_offline_mode():
|
||||
|
||||
Reference in New Issue
Block a user