77import java .sql .*;
88import java .util .*;
99import java .util .regex .Pattern ;
10+ import java .util .stream .Collectors ;
1011
1112@ SuppressWarnings ({"unused" , "SqlNoDataSourceInspection" , "SqlSourceToSinkFlow" })
1213public class SQL {
@@ -1107,133 +1108,93 @@ public boolean updateTableSchema(String tableName, List<String> newColumnDefs, L
11071108 throw new IllegalArgumentException ("Column definitions list cannot be null." );
11081109 }
11091110
1110- List <String > actualColumnDefs = new ArrayList <>();
1111- List <String > allConstraints = (newConstraints == null ) ? new ArrayList <>() : new ArrayList <>(newConstraints );
1112- Set <String > constraintKeywords = new HashSet <>(Arrays .asList ("CONSTRAINT" , "PRIMARY" , "UNIQUE" , "FOREIGN" , "CHECK" ));
1111+ try {
1112+ List <String > actualColumnDefs = new ArrayList <>();
1113+ List <String > allConstraints = (newConstraints == null ) ? new ArrayList <>() : new ArrayList <>(newConstraints );
1114+ Set <String > constraintKeywords = new HashSet <>(Arrays .asList ("CONSTRAINT" , "PRIMARY" , "UNIQUE" , "FOREIGN" , "CHECK" ));
11131115
1114- for (String def : newColumnDefs ) {
1115- if (def == null || def .trim ().isEmpty ()) {
1116- throw new IllegalArgumentException ("Column definition or constraint cannot be null or empty." );
1117- }
1118- String firstWord = def .trim ().split ("[\\ s(]+" )[0 ].toUpperCase ();
1119- if (constraintKeywords .contains (firstWord )) {
1120- allConstraints .add (def );
1121- } else {
1122- actualColumnDefs .add (def );
1116+ for (String def : newColumnDefs ) {
1117+ if (def == null || def .trim ().isEmpty ()) {
1118+ throw new IllegalArgumentException ("Column definition or constraint cannot be null or empty." );
1119+ }
1120+ String firstWord = def .trim ().split ("[\\ s(]+" )[0 ].toUpperCase ();
1121+ if (constraintKeywords .contains (firstWord )) {
1122+ allConstraints .add (def );
1123+ } else {
1124+ actualColumnDefs .add (def );
1125+ }
11231126 }
1124- }
1125-
1126- if (actualColumnDefs .isEmpty () && tableExists (tableName )) {
1127- logger .warn ("No column definitions provided for schema update of existing table '" + tableName + "'. Only constraints will be processed." );
1128- } else if (actualColumnDefs .isEmpty ()) {
1129- throw new IllegalArgumentException ("At least one column definition is required for a new table." );
1130- }
11311127
1132- try {
11331128 if (!tableExists (tableName )) {
1129+ if (actualColumnDefs .isEmpty ()) {
1130+ throw new IllegalArgumentException ("At least one column definition is required for a new table." );
1131+ }
11341132 logger .warn ("Table '" + tableName + "' does not exist. Creating it instead." );
11351133 createTable (tableName , actualColumnDefs , allConstraints );
11361134 return true ;
11371135 }
11381136
1139- DatabaseMetaData metaData = getConnection ().getMetaData ();
1140- List < String > currentColumns = getTableColumns ( tableName , metaData );
1137+ Map < String , String > currentColumnsMap = getTableColumns ( tableName , getConnection ().getMetaData ()). stream ()
1138+ . collect ( Collectors . toMap ( String :: toLowerCase , col -> col , ( c1 , c2 ) -> c1 ) );
11411139
1142- List <String > newColumnNames = new ArrayList <>();
11431140 Map <String , String > newColumnDefinitions = new LinkedHashMap <>();
1144-
11451141 for (String colDef : actualColumnDefs ) {
1146- String [] parts = colDef .trim ().split ("\\ s+" , 2 );
1147- String columnName = parts [0 ].replace ("`" , "" ).replace ("\" " , "" );
1148- newColumnNames .add (columnName );
1149- newColumnDefinitions .put (columnName , colDef );
1142+ String originalName = colDef .trim ().split ("\\ s+" )[0 ].replace ("`" , "" ).replace ("\" " , "" );
1143+ newColumnDefinitions .put (originalName .toLowerCase (), colDef );
11501144 }
11511145
1152- boolean changes = false ;
1146+ Set <String > currentLower = currentColumnsMap .keySet ();
1147+ Set <String > newLower = newColumnDefinitions .keySet ();
11531148
1154- if (databaseType == DatabaseType .SQLITE ) {
1155- List <String > columnsToRemove = new ArrayList <>();
1156- for (String oldCol : currentColumns ) {
1157- if (!newColumnNames .isEmpty () && !newColumnNames .contains (oldCol )) {
1158- columnsToRemove .add (oldCol );
1159- }
1160- }
1161- boolean hasNewColumns = false ;
1162- for (String newCol : newColumnNames ) {
1163- if (!currentColumns .contains (newCol )) {
1164- hasNewColumns = true ;
1165- break ;
1166- }
1167- }
1149+ Set <String > toRemove = new HashSet <>(currentLower );
1150+ toRemove .removeAll (newLower );
1151+
1152+ Set <String > toAdd = new HashSet <>(newLower );
1153+ toAdd .removeAll (currentLower );
1154+
1155+ boolean changesMade = false ;
11681156
1169- if (!columnsToRemove .isEmpty () || !allConstraints .isEmpty ()) {
1170- recreateTableWithNewSchema (tableName , currentColumns , columnsToRemove , newColumnDefinitions , allConstraints );
1157+ if (databaseType == DatabaseType .SQLITE ) {
1158+ if (!toRemove .isEmpty () || !allConstraints .isEmpty ()) {
1159+ recreateTableWithNewSchema (tableName , currentColumnsMap , newColumnDefinitions , allConstraints );
11711160 return true ;
11721161 }
1173- if (hasNewColumns ) {
1174- for (String newCol : newColumnNames ) {
1175- if (!currentColumns .contains (newCol )) {
1176- String addColumnSql = "ALTER TABLE " + quoteIdentifier (tableName ) + " ADD COLUMN " + newColumnDefinitions .get (newCol );
1177- logger .debug ("Adding column: " + addColumnSql );
1178- execute (addColumnSql );
1179- changes = true ;
1180- }
1162+ if (!toAdd .isEmpty ()) {
1163+ for (String colLower : toAdd ) {
1164+ String colDef = newColumnDefinitions .get (colLower );
1165+ execute ("ALTER TABLE " + quoteIdentifier (tableName ) + " ADD COLUMN " + colDef );
1166+ changesMade = true ;
11811167 }
11821168 }
1183- return changes ;
1184- }
1185-
1186- for (String newCol : newColumnNames ) {
1187- if (!currentColumns .contains (newCol )) {
1188- String addColumnSql = "ALTER TABLE " + quoteIdentifier (tableName ) + " ADD COLUMN " + newColumnDefinitions .get (newCol );
1189- logger .debug ("Adding column: " + addColumnSql );
1190- execute (addColumnSql );
1191- changes = true ;
1192- }
1193- }
1194-
1195- for (String oldCol : currentColumns ) {
1196- if (!newColumnNames .isEmpty () && !newColumnNames .contains (oldCol )) {
1197- String dropColumnSql = "ALTER TABLE " + quoteIdentifier (tableName ) + " DROP COLUMN " + quoteIdentifier (oldCol );
1198- logger .debug ("Dropping column: " + dropColumnSql );
1199- execute (dropColumnSql );
1200- changes = true ;
1169+ } else {
1170+ if (!toAdd .isEmpty ()) {
1171+ for (String colLower : toAdd ) {
1172+ String colDef = newColumnDefinitions .get (colLower );
1173+ execute ("ALTER TABLE " + quoteIdentifier (tableName ) + " ADD COLUMN " + colDef );
1174+ changesMade = true ;
1175+ }
12011176 }
1202- }
1203-
1204- for (String constraint : allConstraints ) {
1205- String addConstraintSql = "ALTER TABLE " + quoteIdentifier (tableName ) + " ADD " + constraint ;
1206- try {
1207- logger .debug ("Adding constraint: " + addConstraintSql );
1208- execute (addConstraintSql );
1209- changes = true ;
1210- } catch (RuntimeException e ) {
1211- if (e .getCause () instanceof SQLException ) {
1212- String msg = e .getCause ().getMessage ().toLowerCase ();
1213- if (msg .contains ("duplicate" ) || msg .contains ("already exist" )) {
1214- logger .debug ("Constraint might already exist, skipping: " + constraint );
1215- } else {
1216- throw e ;
1217- }
1218- } else {
1219- throw e ;
1177+ if (!toRemove .isEmpty ()) {
1178+ for (String colLower : toRemove ) {
1179+ String originalColName = currentColumnsMap .get (colLower );
1180+ execute ("ALTER TABLE " + quoteIdentifier (tableName ) + " DROP COLUMN " + quoteIdentifier (originalColName ));
1181+ changesMade = true ;
12201182 }
12211183 }
12221184 }
12231185
1224- return changes ;
1186+ return changesMade ;
12251187 } catch (SQLException e ) {
12261188 logger .error ("Failed to update table schema for '" + tableName + "': " + e .getMessage ());
12271189 throw new RuntimeException (e );
12281190 }
12291191 }
12301192
1231- private void recreateTableWithNewSchema (String tableName , List <String > currentColumns ,
1232- List <String > columnsToRemove , Map <String , String > newColumnDefinitions ,
1233- List <String > newConstraints )
1193+ private void recreateTableWithNewSchema (String tableName , Map <String , String > currentColumnsMap ,
1194+ Map <String , String > newColumnDefinitions , List <String > newConstraints )
12341195 throws SQLException {
12351196
1236- logger .debug ("Recreating table '" + tableName + "' for schema update ." );
1197+ logger .warn ("Recreating table '" + tableName + "' to apply schema changes ." );
12371198
12381199 boolean wasAutoCommit = getConnection ().getAutoCommit ();
12391200 if (wasAutoCommit ) {
@@ -1243,53 +1204,50 @@ private void recreateTableWithNewSchema(String tableName, List<String> currentCo
12431204 try {
12441205 String tempTableName = tableName + "_temp_" + System .currentTimeMillis ();
12451206
1246- List < String > newTableColumnDefs = new ArrayList <>(newColumnDefinitions .values ());
1207+ createTable ( tempTableName , new ArrayList <>(newColumnDefinitions .values ()), newConstraints );
12471208
1248- createTable (tempTableName , newTableColumnDefs , newConstraints );
1209+ Set <String > currentLower = currentColumnsMap .keySet ();
1210+ Set <String > newLower = newColumnDefinitions .keySet ();
1211+ Set <String > commonLower = new HashSet <>(currentLower );
1212+ commonLower .retainAll (newLower );
12491213
1250- List <String > columnsToCopy = new ArrayList <>();
1251- for (String col : currentColumns ) {
1252- if (!columnsToRemove .contains (col ) && newColumnDefinitions .containsKey (col .replace ("`" , "" ).replace ("\" " , "" ))) {
1253- columnsToCopy .add (quoteIdentifier (col ));
1254- }
1255- }
1214+ if (!commonLower .isEmpty ()) {
1215+ String selectCols = commonLower .stream ()
1216+ .map (currentColumnsMap ::get )
1217+ .map (this ::quoteIdentifier )
1218+ .collect (Collectors .joining (", " ));
12561219
1257- if (!columnsToCopy .isEmpty ()) {
1258- String joinedCols = String .join (", " , columnsToCopy );
1259- String copyDataSql = "INSERT INTO " + quoteIdentifier (tempTableName ) +
1260- " (" + joinedCols + ")" +
1261- " SELECT " + joinedCols +
1262- " FROM " + quoteIdentifier (tableName );
1220+ String insertCols = commonLower .stream ()
1221+ .map (newColumnDefinitions ::get )
1222+ .map (def -> def .trim ().split ("\\ s+" )[0 ])
1223+ .map (this ::quoteIdentifier )
1224+ .collect (Collectors .joining (", " ));
12631225
1264- logger .debug ("Copying data: " + copyDataSql );
1265- try (Statement stmt = getConnection ().createStatement ()) {
1266- stmt .execute (copyDataSql );
1267- }
1226+ String copyDataSql = "INSERT INTO " + quoteIdentifier (tempTableName ) + " (" + insertCols + ")" +
1227+ " SELECT " + selectCols + " FROM " + quoteIdentifier (tableName );
1228+
1229+ logger .debug ("Copying data to new table: " + copyDataSql );
1230+ execute (copyDataSql );
12681231 }
12691232
12701233 deleteTable (tableName );
12711234
1272- String renameSql = "ALTER TABLE " + quoteIdentifier (tempTableName ) +
1273- " RENAME TO " + quoteIdentifier (tableName );
1274-
1275- logger .debug ("Renaming table: " + renameSql );
1276- try (Statement stmt = getConnection ().createStatement ()) {
1277- stmt .execute (renameSql );
1278- }
1235+ execute ("ALTER TABLE " + quoteIdentifier (tempTableName ) + " RENAME TO " + quoteIdentifier (tableName ));
12791236
12801237 if (wasAutoCommit ) {
12811238 getConnection ().commit ();
12821239 }
1240+ logger .info ("Successfully recreated table '" + tableName + "' with updated schema." );
12831241
1284- logger . info ( "Successfully recreated table '" + tableName + "' with updated schema" );
1285- } catch ( SQLException e ) {
1242+ } catch ( Exception e ) {
1243+ logger . error ( "Error during table recreation, attempting to rollback." );
12861244 try {
12871245 getConnection ().rollback ();
12881246 } catch (SQLException rollbackEx ) {
12891247 logger .error ("Failed to rollback transaction: " + rollbackEx .getMessage ());
12901248 }
1291-
1292- throw e ;
1249+ if ( e instanceof RuntimeException ) throw ( RuntimeException ) e ;
1250+ throw new RuntimeException ( "Failed to recreate table" , e ) ;
12931251 } finally {
12941252 if (wasAutoCommit ) {
12951253 try {
0 commit comments