Skip to content

Commit 3757f33

Browse files
committed
✨ add --defer-foreign-keys option to defer foreign key constraints during data transfer to SQLite
1 parent 8247bd5 commit 3757f33

File tree

3 files changed

+29
-3
lines changed

3 files changed

+29
-3
lines changed

src/mysql_to_sqlite3/cli.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@
9393
help="Prefix indices with their corresponding tables. "
9494
"This ensures that their names remain unique across the SQLite database.",
9595
)
96+
@click.option(
97+
"-D", "--defer-foreign-keys", is_flag=True, help="Defer foreign key constraints until the end of the transfer."
98+
)
9699
@click.option("-X", "--without-foreign-keys", is_flag=True, help="Do not transfer foreign keys.")
97100
@click.option(
98101
"-Z",
@@ -164,6 +167,7 @@ def cli(
164167
limit_rows: int,
165168
collation: t.Optional[str],
166169
prefix_indices: bool,
170+
defer_foreign_keys: bool,
167171
without_foreign_keys: bool,
168172
without_tables: bool,
169173
without_data: bool,
@@ -212,6 +216,11 @@ def cli(
212216
limit_rows=limit_rows,
213217
collation=collation,
214218
prefix_indices=prefix_indices,
219+
defer_foreign_keys=(
220+
defer_foreign_keys
221+
if not without_foreign_keys and not (mysql_tables is not None and len(mysql_tables) > 0)
222+
else False
223+
),
215224
without_foreign_keys=without_foreign_keys or (mysql_tables is not None and len(mysql_tables) > 0),
216225
without_tables=without_tables,
217226
without_data=without_data,

src/mysql_to_sqlite3/transporter.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,16 @@ def __init__(self, **kwargs: tx.Unpack[MySQLtoSQLiteParams]) -> None:
9595
else:
9696
self._without_foreign_keys = bool(kwargs.get("without_foreign_keys", False))
9797

98+
if not self._without_foreign_keys and not bool(self._mysql_tables) and not bool(self._exclude_mysql_tables):
99+
self._defer_foreign_keys = bool(kwargs.get("defer_foreign_keys", False))
100+
if self._defer_foreign_keys and sqlite3.sqlite_version_info < (3, 6, 19):
101+
self._logger.warning(
102+
"SQLite %s lacks DEFERRABLE support – ignoring --defer-fks.", sqlite3.sqlite_version
103+
)
104+
self._defer_foreign_keys = False
105+
else:
106+
self._defer_foreign_keys = False
107+
98108
self._without_data = bool(kwargs.get("without_data", False))
99109
self._without_tables = bool(kwargs.get("without_tables", False))
100110

@@ -557,10 +567,12 @@ def _build_create_table_sql(self, table_name: str) -> str:
557567
)
558568
for foreign_key in self._mysql_cur_dict.fetchall():
559569
if foreign_key is not None:
570+
deferrable_clause = " DEFERRABLE INITIALLY DEFERRED" if self._defer_foreign_keys else ""
560571
sql += (
561572
',\n\tFOREIGN KEY("{column}") REFERENCES "{ref_table}" ("{ref_column}") '
562573
"ON UPDATE {on_update} "
563-
"ON DELETE {on_delete}".format(**foreign_key) # type: ignore[str-bytes-safe]
574+
"ON DELETE {on_delete}"
575+
"{deferrable}".format(**foreign_key, deferrable=deferrable_clause) # type: ignore[str-bytes-safe]
564576
)
565577

566578
sql += "\n);"
@@ -765,12 +777,15 @@ def transfer(self) -> None:
765777
self._logger.warning(
766778
"Foreign key constraint violations found (%d violation%s):",
767779
len(fk_violations),
768-
"s" if len(fk_violations) != 1 else ""
780+
"s" if len(fk_violations) != 1 else "",
769781
)
770782
for violation in fk_violations:
771783
self._logger.warning(
772784
" → Table '%s' (row %s) references missing key in '%s' (constraint #%s)",
773-
violation[0], violation[1], violation[2], violation[3]
785+
violation[0],
786+
violation[1],
787+
violation[2],
788+
violation[3],
774789
)
775790
else:
776791
self._logger.info("All foreign key constraints are valid.")

src/mysql_to_sqlite3/types.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ class MySQLtoSQLiteParams(tx.TypedDict):
3535
vacuum: t.Optional[bool]
3636
without_tables: t.Optional[bool]
3737
without_data: t.Optional[bool]
38+
defer_foreign_keys: t.Optional[bool]
3839
without_foreign_keys: t.Optional[bool]
3940

4041

@@ -71,4 +72,5 @@ class MySQLtoSQLiteAttributes:
7172
_sqlite_json1_extension_enabled: bool
7273
_vacuum: bool
7374
_without_data: bool
75+
_defer_foreign_keys: bool
7476
_without_foreign_keys: bool

0 commit comments

Comments
 (0)