Skip to content

Commit 8d3f70a

Browse files
committed
✨ add support for AUTOINCREMENT
1 parent 5369cf2 commit 8d3f70a

File tree

8 files changed

+105
-48
lines changed

8 files changed

+105
-48
lines changed

CHANGELOG.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,7 @@
1+
# 2.1.10
2+
3+
* [FEAT] add support for AUTOINCREMENT
4+
15
# 2.1.9
26

37
* [FIX] pin MySQL Connector/Python to 8.3.0

mysql_to_sqlite3/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Utility to transfer data from MySQL to SQLite 3."""
2-
__version__ = "2.1.9"
2+
3+
__version__ = "2.1.10"
34

45
from .transporter import MySQLtoSQLite

mysql_to_sqlite3/cli.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""The command line interface of MySQLtoSQLite."""
2+
23
import os
34
import sys
45
import typing as t

mysql_to_sqlite3/sqlite_utils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,3 +52,20 @@ def convert_date(value: t.Any) -> date:
5252
return date.fromisoformat(value.decode())
5353
except ValueError as err:
5454
raise ValueError(f"DATE field contains {err}") # pylint: disable=W0707
55+
56+
57+
Integer_Types: t.Set[str] = {
58+
"INTEGER",
59+
"INTEGER UNSIGNED",
60+
"INT",
61+
"INT UNSIGNED",
62+
"BIGINT",
63+
"BIGINT UNSIGNED",
64+
"MEDIUMINT",
65+
"MEDIUMINT UNSIGNED",
66+
"SMALLINT",
67+
"SMALLINT UNSIGNED",
68+
"TINYINT",
69+
"TINYINT UNSIGNED",
70+
"NUMERIC",
71+
}

mysql_to_sqlite3/transporter.py

Lines changed: 61 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
from mysql_to_sqlite3.mysql_utils import CHARSET_INTRODUCERS
2222
from mysql_to_sqlite3.sqlite_utils import (
2323
CollatingSequences,
24+
Integer_Types,
2425
adapt_decimal,
2526
adapt_timedelta,
2627
convert_date,
@@ -384,24 +385,42 @@ def _build_create_table_sql(self, table_name: str) -> str:
384385
column_type=row["Type"], # type: ignore[arg-type]
385386
sqlite_json1_extension_enabled=self._sqlite_json1_extension_enabled,
386387
)
387-
sql += '\n\t"{name}" {type} {notnull} {default} {collation},'.format(
388-
name=row["Field"].decode() if isinstance(row["Field"], bytes) else row["Field"],
389-
type=column_type,
390-
notnull="NULL" if row["Null"] == "YES" else "NOT NULL",
391-
default=self._translate_default_from_mysql_to_sqlite(row["Default"], column_type, row["Extra"]),
392-
collation=self._data_type_collation_sequence(self._collation, column_type),
393-
)
388+
if row["Key"] == "PRI" and row["Extra"] == "auto_increment":
389+
if column_type in Integer_Types:
390+
sql += '\n\t"{name}" INTEGER PRIMARY KEY AUTOINCREMENT,'.format(
391+
name=row["Field"].decode() if isinstance(row["Field"], bytes) else row["Field"],
392+
)
393+
else:
394+
self._logger.warning(
395+
'Primary key "%s" in table "%s" is not an INTEGER type! Skipping.',
396+
row["Field"],
397+
table_name,
398+
)
399+
else:
400+
sql += '\n\t"{name}" {type} {notnull} {default} {collation},'.format(
401+
name=row["Field"].decode() if isinstance(row["Field"], bytes) else row["Field"],
402+
type=column_type,
403+
notnull="NULL" if row["Null"] == "YES" else "NOT NULL",
404+
default=self._translate_default_from_mysql_to_sqlite(row["Default"], column_type, row["Extra"]),
405+
collation=self._data_type_collation_sequence(self._collation, column_type),
406+
)
394407

395408
self._mysql_cur_dict.execute(
396409
"""
397-
SELECT INDEX_NAME AS `name`,
398-
IF (NON_UNIQUE = 0 AND INDEX_NAME = 'PRIMARY', 1, 0) AS `primary`,
399-
IF (NON_UNIQUE = 0 AND INDEX_NAME <> 'PRIMARY', 1, 0) AS `unique`,
400-
GROUP_CONCAT(COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS `columns`
401-
FROM information_schema.STATISTICS
402-
WHERE TABLE_SCHEMA = %s
403-
AND TABLE_NAME = %s
404-
GROUP BY INDEX_NAME, NON_UNIQUE
410+
SELECT s.INDEX_NAME AS `name`,
411+
IF (NON_UNIQUE = 0 AND s.INDEX_NAME = 'PRIMARY', 1, 0) AS `primary`,
412+
IF (NON_UNIQUE = 0 AND s.INDEX_NAME <> 'PRIMARY', 1, 0) AS `unique`,
413+
IF (c.EXTRA = 'auto_increment', 1, 0) AS `auto_increment`,
414+
GROUP_CONCAT(s.COLUMN_NAME ORDER BY SEQ_IN_INDEX) AS `columns`,
415+
GROUP_CONCAT(c.COLUMN_TYPE ORDER BY SEQ_IN_INDEX) AS `types`
416+
FROM information_schema.STATISTICS AS s
417+
JOIN information_schema.COLUMNS AS c
418+
ON s.TABLE_SCHEMA = c.TABLE_SCHEMA
419+
AND s.TABLE_NAME = c.TABLE_NAME
420+
AND s.COLUMN_NAME = c.COLUMN_NAME
421+
WHERE s.TABLE_SCHEMA = %s
422+
AND s.TABLE_NAME = %s
423+
GROUP BY s.INDEX_NAME, s.NON_UNIQUE, c.EXTRA
405424
""",
406425
(self._mysql_database, table_name),
407426
)
@@ -437,17 +456,33 @@ def _build_create_table_sql(self, table_name: str) -> str:
437456
elif isinstance(index["columns"], str):
438457
columns = index["columns"]
439458

459+
types: str = ""
460+
if isinstance(index["types"], bytes):
461+
types = index["types"].decode()
462+
elif isinstance(index["types"], str):
463+
types = index["types"]
464+
440465
if len(columns) > 0:
441466
if index["primary"] in {1, "1"}:
442-
primary += "\n\tPRIMARY KEY ({})".format(
443-
", ".join(f'"{column}"' for column in columns.split(","))
444-
)
467+
if (index["auto_increment"] not in {1, "1"}) or any(
468+
self._translate_type_from_mysql_to_sqlite(
469+
column_type=_type,
470+
sqlite_json1_extension_enabled=self._sqlite_json1_extension_enabled,
471+
)
472+
not in Integer_Types
473+
for _type in types.split(",")
474+
):
475+
primary += "\n\tPRIMARY KEY ({})".format(
476+
", ".join(f'"{column}"' for column in columns.split(","))
477+
)
445478
else:
446479
indices += """CREATE {unique} INDEX IF NOT EXISTS "{name}" ON "{table}" ({columns});""".format(
447480
unique="UNIQUE" if index["unique"] in {1, "1"} else "",
448-
name=f"{table_name}_{index_name}"
449-
if (table_collisions > 0 or self._prefix_indices)
450-
else index_name,
481+
name=(
482+
f"{table_name}_{index_name}"
483+
if (table_collisions > 0 or self._prefix_indices)
484+
else index_name
485+
),
451486
table=table_name,
452487
columns=", ".join(f'"{column}"' for column in columns.split(",")),
453488
)
@@ -481,9 +516,11 @@ def _build_create_table_sql(self, table_name: str) -> str:
481516
c.UPDATE_RULE,
482517
c.DELETE_RULE
483518
""".format(
484-
JOIN="JOIN"
485-
if (server_version is not None and server_version[0] == 8 and server_version[2] > 19)
486-
else "LEFT JOIN"
519+
JOIN=(
520+
"JOIN"
521+
if (server_version is not None and server_version[0] == 8 and server_version[2] > 19)
522+
else "LEFT JOIN"
523+
)
487524
),
488525
(self._mysql_database, table_name, "FOREIGN KEY"),
489526
)

mysql_to_sqlite3/types.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
"""Types for mysql-to-sqlite3."""
2+
23
import os
34
import typing as t
45
from logging import Logger

tests/func/mysql_to_sqlite3_test.py

Lines changed: 18 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -433,14 +433,14 @@ def test_transfer_transfers_all_tables_from_mysql_to_sqlite(
433433
mysql_inspect: Inspector = inspect(mysql_engine)
434434
mysql_tables: t.List[str] = mysql_inspect.get_table_names()
435435

436-
mysql_connector_connection: t.Union[
437-
PooledMySQLConnection, MySQLConnection, CMySQLConnection
438-
] = mysql.connector.connect(
439-
user=mysql_credentials.user,
440-
password=mysql_credentials.password,
441-
host=mysql_credentials.host,
442-
port=mysql_credentials.port,
443-
database=mysql_credentials.database,
436+
mysql_connector_connection: t.Union[PooledMySQLConnection, MySQLConnection, CMySQLConnection] = (
437+
mysql.connector.connect(
438+
user=mysql_credentials.user,
439+
password=mysql_credentials.password,
440+
host=mysql_credentials.host,
441+
port=mysql_credentials.port,
442+
database=mysql_credentials.database,
443+
)
444444
)
445445
server_version: t.Tuple[int, ...] = mysql_connector_connection.get_server_version()
446446

@@ -490,9 +490,7 @@ def test_transfer_transfers_all_tables_from_mysql_to_sqlite(
490490
AND i.CONSTRAINT_TYPE = :constraint_type
491491
""".format(
492492
# MySQL 8.0.19 still works with "LEFT JOIN" everything above requires "JOIN"
493-
JOIN="JOIN"
494-
if (server_version[0] == 8 and server_version[2] > 19)
495-
else "LEFT JOIN"
493+
JOIN="JOIN" if (server_version[0] == 8 and server_version[2] > 19) else "LEFT JOIN"
496494
)
497495
).bindparams(
498496
table_schema=mysql_credentials.database,
@@ -1183,14 +1181,14 @@ def test_transfer_limited_rows_from_mysql_to_sqlite(
11831181
mysql_inspect: Inspector = inspect(mysql_engine)
11841182
mysql_tables: t.List[str] = mysql_inspect.get_table_names()
11851183

1186-
mysql_connector_connection: t.Union[
1187-
PooledMySQLConnection, MySQLConnection, CMySQLConnection
1188-
] = mysql.connector.connect(
1189-
user=mysql_credentials.user,
1190-
password=mysql_credentials.password,
1191-
host=mysql_credentials.host,
1192-
port=mysql_credentials.port,
1193-
database=mysql_credentials.database,
1184+
mysql_connector_connection: t.Union[PooledMySQLConnection, MySQLConnection, CMySQLConnection] = (
1185+
mysql.connector.connect(
1186+
user=mysql_credentials.user,
1187+
password=mysql_credentials.password,
1188+
host=mysql_credentials.host,
1189+
port=mysql_credentials.port,
1190+
database=mysql_credentials.database,
1191+
)
11941192
)
11951193
server_version: t.Tuple[int, ...] = mysql_connector_connection.get_server_version()
11961194

@@ -1240,9 +1238,7 @@ def test_transfer_limited_rows_from_mysql_to_sqlite(
12401238
AND i.CONSTRAINT_TYPE = :constraint_type
12411239
""".format(
12421240
# MySQL 8.0.19 still works with "LEFT JOIN" everything above requires "JOIN"
1243-
JOIN="JOIN"
1244-
if (server_version[0] == 8 and server_version[2] > 19)
1245-
else "LEFT JOIN"
1241+
JOIN="JOIN" if (server_version[0] == 8 and server_version[2] > 19) else "LEFT JOIN"
12461242
)
12471243
).bindparams(
12481244
table_schema=mysql_credentials.database,

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ deps =
8080
mypy>=1.3.0
8181
-rrequirements_dev.txt
8282
commands =
83-
mypy mysql_to_sqlite3 --enable-incomplete-feature=Unpack
83+
mypy mysql_to_sqlite3
8484

8585
[testenv:linters]
8686
basepython = python3

0 commit comments

Comments
 (0)