Skip to content

Commit 8bcb34e

Browse files
committed
refactor: enhance migration logic to handle orphaned rows and improve transaction safety
1 parent 4f49459 commit 8bcb34e

File tree

1 file changed

+12
-10
lines changed

1 file changed

+12
-10
lines changed

scripts/migrate_sqlite_to_mysql.py

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -61,12 +61,11 @@ def _pk_columns(model: type) -> list[str]:
6161
return [col.name for col in inspect(model).mapper.primary_key]
6262

6363

64-
def migrate_table(src: Session, dst: Session, model: type) -> tuple[int, int]:
64+
def migrate_table(src: Session, dst: Session, model: type) -> tuple[int, int, int]:
6565
"""Copy rows from src to dst for the given model.
6666
67-
Returns (inserted, skipped) counts.
67+
Returns (inserted, skipped, orphaned) counts.
6868
"""
69-
table_name = model.__tablename__
7069
cols = _columns(model)
7170
pk_cols = _pk_columns(model)
7271

@@ -91,12 +90,15 @@ def migrate_table(src: Session, dst: Session, model: type) -> tuple[int, int]:
9190
# Build a fresh detached copy so we don't accidentally modify the
9291
# source session's identity map.
9392
new_obj = model(**{col: getattr(row, col) for col in cols})
94-
dst.add(new_obj)
9593
try:
96-
dst.flush()
94+
# Use a SAVEPOINT so that a FK violation rolls back only this one
95+
# row — not the entire transaction (which would destroy all parent
96+
# rows flushed in earlier loop iterations or earlier tables).
97+
with dst.begin_nested():
98+
dst.add(new_obj)
99+
dst.flush()
97100
inserted += 1
98101
except IntegrityError:
99-
dst.rollback()
100102
orphaned += 1
101103

102104
return inserted, skipped, orphaned
@@ -173,15 +175,15 @@ def main() -> int:
173175
print(f" {table_name:<20}{summary}")
174176
total_inserted += inserted
175177
total_skipped += skipped + orphaned
178+
# Commit after every table so parent rows are durable before
179+
# child tables run their FK-constrained inserts.
180+
dst_session.commit()
176181
except Exception as exc:
177182
dst_session.rollback()
178183
print(f" {table_name:<20} — ERROR: {exc}", file=sys.stderr)
179-
print("Rolling back entire transaction.", file=sys.stderr)
184+
print("Rolling back this table's changes.", file=sys.stderr)
180185
return 1
181186

182-
if not args.dry_run:
183-
dst_session.commit()
184-
185187
print(f"\nDone. Inserted {total_inserted} rows, skipped {total_skipped} duplicates.")
186188
return 0
187189

0 commit comments

Comments
 (0)