@@ -903,6 +903,21 @@ def _createViews(self, viewsDict, force=False):
903903 return createView
904904 return S_OK ()
905905
906+ def _parseForeignKeyReference (self , auxTable , defaultKey ):
907+ """
908+ Parse foreign key reference in format 'Table' or 'Table.key'
909+
910+ :param str auxTable: Foreign key reference (e.g., 'MyTable' or 'MyTable.id')
911+ :param str defaultKey: Default key name if not specified in auxTable
912+ :return: tuple (table_name, key_name)
913+ """
914+ if "." in auxTable :
915+ parts = auxTable .split ("." , 1 )
916+ if len (parts ) != 2 :
917+ raise ValueError (f"Invalid foreign key reference format: { auxTable } " )
918+ return parts [0 ], parts [1 ]
919+ return auxTable , defaultKey
920+
906921 def _createTables (self , tableDict , force = False ):
907922 """
908923 tableDict:
@@ -957,55 +972,67 @@ def _createTables(self, tableDict, force=False):
957972 if "Fields" not in thisTable :
958973 return S_ERROR (DErrno .EMYSQL , f"Missing `Fields` key in `{ table } ` table dictionary" )
959974
960- tableCreationList = [[]]
961-
975+ # Build dependency-ordered list of tables to create
976+ # Tables with foreign keys must be created after their referenced tables
977+ tableCreationList = []
962978 auxiliaryTableList = []
963979
964- i = 0
980+ # Get list of existing tables in the database to handle migrations
981+ existingTablesResult = self ._query ("SHOW TABLES" )
982+ if not existingTablesResult ["OK" ]:
983+ return existingTablesResult
984+ existingTables = [t [0 ] for t in existingTablesResult ["Value" ]]
985+
965986 extracted = True
966987 while tableList and extracted :
967988 # iterate extracting tables from list if they only depend on
968989 # already extracted tables.
969990 extracted = False
970- auxiliaryTableList += tableCreationList [i ]
971- i += 1
972- tableCreationList .append ([])
991+ currentLevelTables = []
992+
973993 for table in list (tableList ):
974994 toBeExtracted = True
975995 thisTable = tableDict [table ]
976996 if "ForeignKeys" in thisTable :
977997 thisKeys = thisTable ["ForeignKeys" ]
978998 for key , auxTable in thisKeys .items ():
979- forTable = auxTable .split ("." )[0 ]
980- forKey = key
981- if forTable != auxTable :
982- forKey = auxTable .split ("." )[1 ]
983- if forTable not in auxiliaryTableList :
999+ try :
1000+ forTable , forKey = self ._parseForeignKeyReference (auxTable , key )
1001+ except ValueError as e :
1002+ return S_ERROR (DErrno .EMYSQL , str (e ))
1003+
1004+ # Check if the referenced table is either being created or already exists
1005+ if forTable not in auxiliaryTableList and forTable not in existingTables :
9841006 toBeExtracted = False
9851007 break
9861008 if key not in thisTable ["Fields" ]:
9871009 return S_ERROR (
9881010 DErrno .EMYSQL ,
9891011 f"ForeignKey `{ key } ` -> `{ forKey } ` not defined in Primary table `{ table } `." ,
9901012 )
991- if forKey not in tableDict [forTable ]["Fields" ]:
1013+ # Only validate field existence if the referenced table is in tableDict
1014+ if forTable in tableDict and forKey not in tableDict [forTable ]["Fields" ]:
9921015 return S_ERROR (
9931016 DErrno .EMYSQL ,
994- "ForeignKey `%s` -> `%s` not defined in Auxiliary table `%s`."
995- % (key , forKey , forTable ),
1017+ f"ForeignKey `{ key } ` -> `{ forKey } ` not defined in Auxiliary table `{ forTable } `." ,
9961018 )
9971019
9981020 if toBeExtracted :
9991021 # self.log.debug('Table %s ready to be created' % table)
10001022 extracted = True
10011023 tableList .remove (table )
1002- tableCreationList [i ].append (table )
1024+ currentLevelTables .append (table )
1025+
1026+ if currentLevelTables :
1027+ tableCreationList .append (currentLevelTables )
1028+ auxiliaryTableList .extend (currentLevelTables )
10031029
10041030 if tableList :
10051031 return S_ERROR (DErrno .EMYSQL , f"Recursive Foreign Keys in { ', ' .join (tableList )} " )
10061032
1007- for tableList in tableCreationList :
1008- for table in tableList :
1033+ # Create tables level by level
1034+ for levelTables in tableCreationList :
1035+ for table in levelTables :
10091036 # Check if Table exist
10101037 retDict = self .__checkTable (table , force = force )
10111038 if not retDict ["OK" ]:
@@ -1035,18 +1062,17 @@ def _createTables(self, tableDict, force=False):
10351062 for index in indexDict :
10361063 indexedFields = "`, `" .join (indexDict [index ])
10371064 cmdList .append (f"UNIQUE INDEX `{ index } ` ( `{ indexedFields } ` )" )
1065+
10381066 if "ForeignKeys" in thisTable :
10391067 thisKeys = thisTable ["ForeignKeys" ]
10401068 for key , auxTable in thisKeys .items ():
1041- forTable = auxTable . split ( "." )[ 0 ]
1042- forKey = key
1043- if forTable != auxTable :
1044- forKey = auxTable . split ( "." )[ 1 ]
1069+ try :
1070+ forTable , forKey = self . _parseForeignKeyReference ( auxTable , key )
1071+ except ValueError as e :
1072+ return S_ERROR ( DErrno . EMYSQL , str ( e ))
10451073
1046- # cmdList.append( '`%s` %s' % ( forTable, tableDict[forTable]['Fields'][forKey] )
10471074 cmdList .append (
1048- "FOREIGN KEY ( `%s` ) REFERENCES `%s` ( `%s` )"
1049- " ON DELETE RESTRICT" % (key , forTable , forKey )
1075+ f"FOREIGN KEY ( `{ key } ` ) REFERENCES `{ forTable } ` ( `{ forKey } ` ) ON DELETE RESTRICT"
10501076 )
10511077
10521078 engine = thisTable .get ("Engine" , "InnoDB" )
@@ -1058,7 +1084,7 @@ def _createTables(self, tableDict, force=False):
10581084 engine ,
10591085 charset ,
10601086 )
1061- retDict = self ._transaction ([ cmd ] )
1087+ retDict = self ._update ( cmd )
10621088 if not retDict ["OK" ]:
10631089 return retDict
10641090 # self.log.debug('Table %s created' % table)
0 commit comments