6666 key : value for key , value in sqlglot_mysql .MySQL .INVERSE_TIME_MAPPING .items () if key != "%H:%M:%S"
6767}
6868SQLGLOT_MYSQL_INVERSE_TIME_TRIE : t .Dict [str , t .Any ] = new_trie (SQLGLOT_MYSQL_INVERSE_TIME_MAPPING )
69+ MYSQL_TABLE_PREFIX_PATTERN : t .Pattern [str ] = re .compile (r"^[A-Za-z][A-Za-z0-9_]{0,31}$" )
6970
7071
7172class SQLite3toMySQL (SQLite3toMySQLAttributes ):
@@ -91,6 +92,7 @@ class SQLite3toMySQL(SQLite3toMySQLAttributes):
9192 re .IGNORECASE ,
9293 )
9394 NUMERIC_LITERAL_PATTERN : t .Pattern [str ] = re .compile (r"^[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?$" )
95+ TABLE_PREFIX_PATTERN : t .Pattern [str ] = MYSQL_TABLE_PREFIX_PATTERN
9496
9597 MYSQL_CONNECTOR_VERSION : version .Version = version .parse (mysql_connector_version_string )
9698
@@ -174,6 +176,14 @@ def __init__(self, **kwargs: Unpack[SQLite3toMySQLParams]):
174176 if not kwargs .get ("mysql_collation" ) and self ._mysql_collation == "utf8mb4_0900_ai_ci" :
175177 self ._mysql_collation = "utf8mb4_unicode_ci"
176178
179+ mysql_table_prefix : str = str (kwargs .get ("mysql_table_prefix" , "" ) or "" )
180+ if mysql_table_prefix and not self .TABLE_PREFIX_PATTERN .match (mysql_table_prefix ):
181+ raise ValueError (
182+ "MySQL table prefix must start with a letter and contain only letters, numbers, or underscores "
183+ "with a maximum length of 32 characters."
184+ )
185+ self ._mysql_table_prefix = mysql_table_prefix
186+
177187 self ._ignore_duplicate_keys = kwargs .get ("ignore_duplicate_keys" , False ) or False
178188
179189 self ._use_fulltext = kwargs .get ("use_fulltext" , False ) or False
@@ -339,6 +349,13 @@ def _sqlite_table_has_rowid(self, table: str) -> bool:
339349 except sqlite3 .OperationalError :
340350 return False
341351
352+ def _mysql_table_name (self , table_name : str ) -> str :
353+ """Return the MySQL table name with any configured prefix applied."""
354+ prefix : str = getattr (self , "_mysql_table_prefix" , "" )
355+ if prefix :
356+ return safe_identifier_length (f"{ prefix } { table_name } " )
357+ return safe_identifier_length (table_name )
358+
342359 def _create_database (self ) -> None :
343360 try :
344361 self ._mysql_cur .execute (
@@ -881,8 +898,9 @@ def _create_mysql_view(self, view_name: str, view_sql: str) -> None:
881898
882899 def _create_table (self , table_name : str , transfer_rowid : bool = False , skip_default : bool = False ) -> None :
883900 primary_keys : t .List [t .Dict [str , str ]] = []
901+ mysql_table_name : str = self ._mysql_table_name (table_name )
884902
885- sql : str = f"CREATE TABLE IF NOT EXISTS `{ safe_identifier_length ( table_name ) } ` ( "
903+ sql : str = f"CREATE TABLE IF NOT EXISTS `{ mysql_table_name } ` ( "
886904
887905 if transfer_rowid :
888906 sql += " `rowid` BIGINT NOT NULL, "
@@ -960,7 +978,7 @@ def _create_table(self, table_name: str, transfer_rowid: bool = False, skip_defa
960978 )
961979
962980 if transfer_rowid :
963- sql += f", CONSTRAINT `{ safe_identifier_length ( table_name ) } _rowid` UNIQUE (`rowid`)"
981+ sql += f", CONSTRAINT `{ mysql_table_name } _rowid` UNIQUE (`rowid`)"
964982
965983 sql += f" ) ENGINE=InnoDB DEFAULT CHARSET={ self ._mysql_charset } COLLATE={ self ._mysql_collation } "
966984
@@ -971,19 +989,20 @@ def _create_table(self, table_name: str, transfer_rowid: bool = False, skip_defa
971989 if err .errno == errorcode .ER_INVALID_DEFAULT and not skip_default :
972990 self ._logger .warning (
973991 "MySQL failed creating table %s with DEFAULT values: %s. Retrying without DEFAULT values ..." ,
974- safe_identifier_length ( table_name ) ,
992+ mysql_table_name ,
975993 err ,
976994 )
977995 return self ._create_table (table_name , transfer_rowid , skip_default = True )
978996 else :
979997 self ._logger .error (
980998 "MySQL failed creating table %s: %s" ,
981- safe_identifier_length ( table_name ) ,
999+ mysql_table_name ,
9821000 err ,
9831001 )
9841002 raise
9851003
9861004 def _truncate_table (self , table_name : str ) -> None :
1005+ mysql_table_name : str = self ._mysql_table_name (table_name )
9871006 self ._mysql_cur .execute (
9881007 """
9891008 SELECT `TABLE_NAME`
@@ -992,14 +1011,15 @@ def _truncate_table(self, table_name: str) -> None:
9921011 AND `TABLE_NAME` = %s
9931012 LIMIT 1
9941013 """ ,
995- (self ._mysql_database , safe_identifier_length ( table_name ) ),
1014+ (self ._mysql_database , mysql_table_name ),
9961015 )
9971016 if len (self ._mysql_cur .fetchall ()) > 0 :
998- self ._logger .info ("Truncating table %s" , safe_identifier_length ( table_name ) )
999- self ._mysql_cur .execute (f"TRUNCATE TABLE `{ safe_identifier_length ( table_name ) } `" )
1017+ self ._logger .info ("Truncating table %s" , mysql_table_name )
1018+ self ._mysql_cur .execute (f"TRUNCATE TABLE `{ mysql_table_name } `" )
10001019
10011020 def _add_indices (self , table_name : str ) -> None :
10021021 quoted_table_name : str = self ._sqlite_quote_ident (table_name )
1022+ mysql_table_name : str = self ._mysql_table_name (table_name )
10031023
10041024 self ._sqlite_cur .execute (f'PRAGMA table_info("{ quoted_table_name } ")' )
10051025 table_columns : t .Dict [str , str ] = {}
@@ -1063,7 +1083,7 @@ def _add_indices(self, table_name: str) -> None:
10631083 self ._logger .warning (
10641084 """Failed adding index to column "%s" in table %s: Column not found!""" ,
10651085 ", " .join (safe_identifier_length (index_info ["name" ]) for index_info in index_infos ),
1066- safe_identifier_length ( table_name ) ,
1086+ mysql_table_name ,
10671087 )
10681088 continue
10691089
@@ -1107,12 +1127,13 @@ def _add_index(
11071127 index_infos : t .Tuple [t .Dict [str , t .Any ], ...],
11081128 index_iteration : int = 0 ,
11091129 ) -> None :
1130+ mysql_table_name : str = self ._mysql_table_name (table_name )
11101131 sql : str = (
11111132 """
11121133 ALTER TABLE `{table}`
11131134 ADD {index_type} `{name}`({columns})
11141135 """ .format (
1115- table = safe_identifier_length ( table_name ) ,
1136+ table = mysql_table_name ,
11161137 index_type = index_type ,
11171138 name = (
11181139 safe_identifier_length (index ["name" ])
@@ -1128,14 +1149,13 @@ def _add_index(
11281149 """Adding %s to column "%s" in table %s""" ,
11291150 "unique index" if int (index ["unique" ]) == 1 else "index" ,
11301151 ", " .join (safe_identifier_length (index_info ["name" ]) for index_info in index_infos ),
1131- safe_identifier_length ( table_name ) ,
1152+ mysql_table_name ,
11321153 )
11331154 self ._mysql_cur .execute (sql )
11341155 self ._mysql .commit ()
11351156 except mysql .connector .Error as err :
11361157 if err .errno == errorcode .ER_DUP_KEYNAME :
11371158 if not self ._ignore_duplicate_keys :
1138- # handle a duplicate key name
11391159 self ._add_index (
11401160 table_name = table_name ,
11411161 index_type = index_type ,
@@ -1147,59 +1167,59 @@ def _add_index(
11471167 self ._logger .warning (
11481168 """Duplicate key "%s" in table %s detected! Trying to create new key "%s_%s" ...""" ,
11491169 safe_identifier_length (index ["name" ]),
1150- safe_identifier_length ( table_name ) ,
1170+ mysql_table_name ,
11511171 safe_identifier_length (index ["name" ]),
11521172 index_iteration + 1 ,
11531173 )
11541174 else :
11551175 self ._logger .warning (
11561176 """Ignoring duplicate key "%s" in table %s!""" ,
11571177 safe_identifier_length (index ["name" ]),
1158- safe_identifier_length ( table_name ) ,
1178+ mysql_table_name ,
11591179 )
11601180 elif err .errno == errorcode .ER_DUP_ENTRY :
11611181 self ._logger .warning (
11621182 """Ignoring duplicate entry when adding index to column "%s" in table %s!""" ,
11631183 ", " .join (safe_identifier_length (index_info ["name" ]) for index_info in index_infos ),
1164- safe_identifier_length ( table_name ) ,
1184+ mysql_table_name ,
11651185 )
11661186 elif err .errno == errorcode .ER_DUP_FIELDNAME :
11671187 self ._logger .warning (
11681188 """Failed adding index to column "%s" in table %s: Duplicate field name! Ignoring...""" ,
11691189 ", " .join (safe_identifier_length (index_info ["name" ]) for index_info in index_infos ),
1170- safe_identifier_length ( table_name ) ,
1190+ mysql_table_name ,
11711191 )
11721192 elif err .errno == errorcode .ER_TOO_MANY_KEYS :
11731193 self ._logger .warning (
11741194 """Failed adding index to column "%s" in table %s: Too many keys! Ignoring...""" ,
11751195 ", " .join (safe_identifier_length (index_info ["name" ]) for index_info in index_infos ),
1176- safe_identifier_length ( table_name ) ,
1196+ mysql_table_name ,
11771197 )
11781198 elif err .errno == errorcode .ER_TOO_LONG_KEY :
11791199 self ._logger .warning (
11801200 """Failed adding index to column "%s" in table %s: Key length too long! Ignoring...""" ,
11811201 ", " .join (safe_identifier_length (index_info ["name" ]) for index_info in index_infos ),
1182- safe_identifier_length ( table_name ) ,
1202+ mysql_table_name ,
11831203 )
11841204 elif err .errno == errorcode .ER_BAD_FT_COLUMN :
1185- # handle bad FULLTEXT index
11861205 self ._logger .warning (
11871206 """Failed adding FULLTEXT index to column "%s" in table %s. Retrying without FULLTEXT ...""" ,
11881207 ", " .join (safe_identifier_length (index_info ["name" ]) for index_info in index_infos ),
1189- safe_identifier_length ( table_name ) ,
1208+ mysql_table_name ,
11901209 )
11911210 raise
11921211 else :
11931212 self ._logger .error (
11941213 """MySQL failed adding index to column "%s" in table %s: %s""" ,
11951214 ", " .join (safe_identifier_length (index_info ["name" ]) for index_info in index_infos ),
1196- safe_identifier_length ( table_name ) ,
1215+ mysql_table_name ,
11971216 err ,
11981217 )
11991218 raise
12001219
12011220 def _add_foreign_keys (self , table_name : str ) -> None :
12021221 quoted_table_name : str = self ._sqlite_quote_ident (table_name )
1222+ mysql_table_name : str = self ._mysql_table_name (table_name )
12031223 self ._sqlite_cur .execute (f'PRAGMA foreign_key_list("{ quoted_table_name } ")' )
12041224
12051225 foreign_keys : t .Dict [int , t .List [t .Dict [str , t .Any ]]] = {}
@@ -1219,7 +1239,7 @@ def _add_foreign_keys(self, table_name: str) -> None:
12191239 self ._logger .warning (
12201240 'Skipping foreign key "%s" in table %s: partially defined reference columns.' ,
12211241 safe_identifier_length (fk_rows [0 ]["from" ]),
1222- safe_identifier_length ( table_name ) ,
1242+ mysql_table_name ,
12231243 )
12241244 continue
12251245
@@ -1228,14 +1248,15 @@ def _add_foreign_keys(self, table_name: str) -> None:
12281248 self ._logger .warning (
12291249 'Skipping foreign key "%s" in table %s: unable to resolve referenced primary key columns from table %s.' ,
12301250 safe_identifier_length (fk_rows [0 ]["from" ]),
1231- safe_identifier_length ( table_name ) ,
1232- safe_identifier_length (ref_table ),
1251+ mysql_table_name ,
1252+ self . _mysql_table_name (ref_table ),
12331253 )
12341254 continue
12351255 referenced_columns = primary_keys
12361256 else :
12371257 referenced_columns = [safe_identifier_length (fk_row ["to" ]) for fk_row in fk_rows ]
12381258
1259+ mysql_ref_table_name : str = self ._mysql_table_name (ref_table )
12391260 sql = """
12401261 ALTER TABLE `{table}`
12411262 ADD CONSTRAINT `{table}_FK_{id}_{seq}`
@@ -1246,9 +1267,9 @@ def _add_foreign_keys(self, table_name: str) -> None:
12461267 """ .format (
12471268 id = fk_id ,
12481269 seq = fk_rows [0 ]["seq" ],
1249- table = safe_identifier_length ( table_name ) ,
1270+ table = mysql_table_name ,
12501271 columns = ", " .join (f"`{ column } `" for column in from_columns ),
1251- ref_table = safe_identifier_length ( ref_table ) ,
1272+ ref_table = mysql_ref_table_name ,
12521273 ref_columns = ", " .join (f"`{ column } `" for column in referenced_columns ),
12531274 on_delete = (
12541275 fk_rows [0 ]["on_delete" ].upper () if fk_rows [0 ]["on_delete" ].upper () != "SET DEFAULT" else "NO ACTION"
@@ -1261,19 +1282,19 @@ def _add_foreign_keys(self, table_name: str) -> None:
12611282 try :
12621283 self ._logger .info (
12631284 "Adding foreign key to %s.(%s) referencing %s.(%s)" ,
1264- safe_identifier_length ( table_name ) ,
1285+ mysql_table_name ,
12651286 ", " .join (from_columns ),
1266- safe_identifier_length ( ref_table ) ,
1287+ mysql_ref_table_name ,
12671288 ", " .join (referenced_columns ),
12681289 )
12691290 self ._mysql_cur .execute (sql )
12701291 self ._mysql .commit ()
12711292 except mysql .connector .Error as err :
12721293 self ._logger .error (
12731294 "MySQL failed adding foreign key to %s.(%s) referencing %s.(%s): %s" ,
1274- safe_identifier_length ( table_name ) ,
1295+ mysql_table_name ,
12751296 ", " .join (from_columns ),
1276- safe_identifier_length ( ref_table ) ,
1297+ mysql_ref_table_name ,
12771298 ", " .join (referenced_columns ),
12781299 err ,
12791300 )
@@ -1320,6 +1341,7 @@ def transfer(self) -> None:
13201341 table_name : str = table ["name" ]
13211342 object_type : str = table .get ("type" , "table" )
13221343 quoted_table_name : str = self ._sqlite_quote_ident (table_name )
1344+ mysql_table_name : str = self ._mysql_table_name (table_name )
13231345
13241346 # check if we're transferring rowid
13251347 transfer_rowid : bool = self ._with_rowid and self ._sqlite_table_has_rowid (table_name )
@@ -1391,7 +1413,7 @@ def transfer(self) -> None:
13911413 {values_clause}
13921414 ON DUPLICATE KEY UPDATE {field_updates}
13931415 """ .format (
1394- table = safe_identifier_length ( table_name ) ,
1416+ table = mysql_table_name ,
13951417 fields = ("`{}`, " * len (columns )).rstrip (" ," ).format (* columns ),
13961418 values_clause = (
13971419 "VALUES ({placeholders}) AS `__new__`"
@@ -1411,7 +1433,7 @@ def transfer(self) -> None:
14111433 VALUES ({placeholders})
14121434 """ .format (
14131435 ignore = "IGNORE" if self ._mysql_insert_method .upper () == "IGNORE" else "" ,
1414- table = safe_identifier_length ( table_name ) ,
1436+ table = mysql_table_name ,
14151437 fields = ("`{}`, " * len (columns )).rstrip (" ," ).format (* columns ),
14161438 placeholders = ("%s, " * len (columns )).rstrip (" ," ),
14171439 )
@@ -1421,7 +1443,7 @@ def transfer(self) -> None:
14211443 self ._logger .error (
14221444 "MySQL transfer failed inserting data into %s %s: %s" ,
14231445 "view" if object_type == "view" else "table" ,
1424- safe_identifier_length ( table_name ) ,
1446+ mysql_table_name ,
14251447 err ,
14261448 )
14271449 raise
0 commit comments