2121import sys
2222
2323from sqlalchemy import create_engine , inspect
24+ from sqlalchemy .exc import IntegrityError
2425from sqlalchemy .orm import Session
2526
2627from microSALT .store .orm_models import (
4950 SystemLock ,
5051]
5152
53+
5254def _columns (model : type ) -> list [str ]:
5355 """Return the list of column attribute names for an ORM model."""
5456 return [c .key for c in inspect (model ).mapper .column_attrs ]
@@ -70,6 +72,7 @@ def migrate_table(src: Session, dst: Session, model: type) -> tuple[int, int]:
7072
7173 inserted = 0
7274 skipped = 0
75+ orphaned = 0
7376
7477 rows = src .query (model ).all ()
7578 for row in rows :
@@ -89,10 +92,14 @@ def migrate_table(src: Session, dst: Session, model: type) -> tuple[int, int]:
8992 # source session's identity map.
9093 new_obj = model (** {col : getattr (row , col ) for col in cols })
9194 dst .add (new_obj )
92- inserted += 1
95+ try :
96+ dst .flush ()
97+ inserted += 1
98+ except IntegrityError :
99+ dst .rollback ()
100+ orphaned += 1
93101
94- dst .flush ()
95- return inserted , skipped
102+ return inserted , skipped , orphaned
96103
97104
98105def main () -> int :
@@ -159,12 +166,13 @@ def main() -> int:
159166 continue
160167
161168 try :
162- inserted , skipped = migrate_table (src_session , dst_session , model )
163- print (
164- f" { table_name :<20} — inserted { inserted } , skipped { skipped } (already present)"
165- )
169+ inserted , skipped , orphaned = migrate_table (src_session , dst_session , model )
170+ summary = f"inserted { inserted } , skipped { skipped } (already present)"
171+ if orphaned :
172+ summary += f", skipped { orphaned } (orphaned, no parent row)"
173+ print (f" { table_name :<20} — { summary } " )
166174 total_inserted += inserted
167- total_skipped += skipped
175+ total_skipped += skipped + orphaned
168176 except Exception as exc :
169177 dst_session .rollback ()
170178 print (f" { table_name :<20} — ERROR: { exc } " , file = sys .stderr )
0 commit comments