@@ -1096,4 +1096,166 @@ public void execute(String sql, List<?> params) {
10961096 public void execute (String sql , Object ... params ) {
10971097 execute (sql , Arrays .asList (params ));
10981098 }
1099+
1100+ public boolean updateTableSchema (String tableName , List <String > newColumnDefs ) {
1101+ validateIdentifier (tableName );
1102+ if (newColumnDefs == null || newColumnDefs .isEmpty ()) {
1103+ throw new IllegalArgumentException ("At least one column definition is required." );
1104+ }
1105+
1106+ try {
1107+ DatabaseMetaData metaData = getConnection ().getMetaData ();
1108+ List <String > currentColumns = getTableColumns (tableName , metaData );
1109+
1110+ if (currentColumns .isEmpty ()) {
1111+ logger .warn ("Table '" + tableName + "' does not exist. Creating it instead." );
1112+ createTable (tableName , newColumnDefs );
1113+ return true ;
1114+ }
1115+
1116+ List <String > newColumnNames = new ArrayList <>();
1117+ Map <String , String > newColumnDefinitions = new HashMap <>();
1118+
1119+ for (String colDef : newColumnDefs ) {
1120+ if (colDef == null || colDef .trim ().isEmpty ()) {
1121+ throw new IllegalArgumentException ("Column definition cannot be null or empty." );
1122+ }
1123+
1124+ String [] parts = colDef .trim ().split ("\\ s+" , 2 );
1125+ if (parts .length < 1 ) {
1126+ throw new IllegalArgumentException ("Invalid column definition: " + colDef );
1127+ }
1128+
1129+ String columnName = parts [0 ];
1130+ if ((columnName .startsWith ("\" " ) && columnName .endsWith ("\" " )) ||
1131+ (columnName .startsWith ("`" ) && columnName .endsWith ("`" ))) {
1132+ columnName = columnName .substring (1 , columnName .length () - 1 );
1133+ }
1134+
1135+ newColumnNames .add (columnName );
1136+ newColumnDefinitions .put (columnName , colDef );
1137+ }
1138+
1139+ boolean changes = false ;
1140+
1141+ for (String newCol : newColumnNames ) {
1142+ if (!currentColumns .contains (newCol )) {
1143+ String addColumnSql = "ALTER TABLE " + quoteIdentifier (tableName ) +
1144+ " ADD COLUMN " + newColumnDefinitions .get (newCol );
1145+
1146+ logger .debug ("Adding column: " + addColumnSql );
1147+ try (Statement stmt = getConnection ().createStatement ()) {
1148+ stmt .execute (addColumnSql );
1149+ logger .info ("Added column '" + newCol + "' to table '" + tableName + "'" );
1150+ changes = true ;
1151+ }
1152+ }
1153+ }
1154+
1155+ if (databaseType != DatabaseType .SQLITE ) {
1156+ for (String oldCol : currentColumns ) {
1157+ if (!newColumnNames .contains (oldCol )) {
1158+ String dropColumnSql = "ALTER TABLE " + quoteIdentifier (tableName ) +
1159+ " DROP COLUMN " + quoteIdentifier (oldCol );
1160+
1161+ logger .debug ("Dropping column: " + dropColumnSql );
1162+ try (Statement stmt = getConnection ().createStatement ()) {
1163+ stmt .execute (dropColumnSql );
1164+ logger .info ("Dropped column '" + oldCol + "' from table '" + tableName + "'" );
1165+ changes = true ;
1166+ }
1167+ }
1168+ }
1169+ } else {
1170+ List <String > columnsToRemove = new ArrayList <>();
1171+ for (String oldCol : currentColumns ) {
1172+ if (!newColumnNames .contains (oldCol )) {
1173+ columnsToRemove .add (oldCol );
1174+ }
1175+ }
1176+
1177+ if (!columnsToRemove .isEmpty ()) {
1178+ recreateTableWithNewSchema (tableName , currentColumns , columnsToRemove , newColumnDefinitions );
1179+ changes = true ;
1180+ }
1181+ }
1182+
1183+ return changes ;
1184+ } catch (SQLException e ) {
1185+ logger .error ("Failed to update table schema for '" + tableName + "': " + e .getMessage ());
1186+ throw new RuntimeException (e );
1187+ }
1188+ }
1189+
1190+ private void recreateTableWithNewSchema (String tableName , List <String > currentColumns ,
1191+ List <String > columnsToRemove , Map <String , String > newColumnDefinitions )
1192+ throws SQLException {
1193+
1194+ logger .debug ("Recreating table '" + tableName + "' to remove columns: " + String .join (", " , columnsToRemove ));
1195+
1196+ boolean wasAutoCommit = getConnection ().getAutoCommit ();
1197+ if (wasAutoCommit ) {
1198+ getConnection ().setAutoCommit (false );
1199+ }
1200+
1201+ try {
1202+ String tempTableName = tableName + "_temp_" + System .currentTimeMillis ();
1203+
1204+ List <String > newTableColumns = new ArrayList <>();
1205+ for (String colName : newColumnDefinitions .keySet ()) {
1206+ newTableColumns .add (newColumnDefinitions .get (colName ));
1207+ }
1208+
1209+ createTable (tempTableName , newTableColumns );
1210+
1211+ List <String > columnsToCopy = new ArrayList <>();
1212+ for (String col : currentColumns ) {
1213+ if (!columnsToRemove .contains (col )) {
1214+ columnsToCopy .add (quoteIdentifier (col ));
1215+ }
1216+ }
1217+
1218+ String copyDataSql = "INSERT INTO " + quoteIdentifier (tempTableName ) +
1219+ " SELECT " + String .join (", " , columnsToCopy ) +
1220+ " FROM " + quoteIdentifier (tableName );
1221+
1222+ logger .debug ("Copying data: " + copyDataSql );
1223+ try (Statement stmt = getConnection ().createStatement ()) {
1224+ stmt .execute (copyDataSql );
1225+ }
1226+
1227+ deleteTable (tableName );
1228+
1229+ String renameSql = "ALTER TABLE " + quoteIdentifier (tempTableName ) +
1230+ " RENAME TO " + quoteIdentifier (tableName );
1231+
1232+ logger .debug ("Renaming table: " + renameSql );
1233+ try (Statement stmt = getConnection ().createStatement ()) {
1234+ stmt .execute (renameSql );
1235+ }
1236+
1237+ if (wasAutoCommit ) {
1238+ getConnection ().commit ();
1239+ getConnection ().setAutoCommit (true );
1240+ }
1241+
1242+ logger .info ("Successfully recreated table '" + tableName + "' with updated schema" );
1243+ } catch (SQLException e ) {
1244+ try {
1245+ getConnection ().rollback ();
1246+ } catch (SQLException rollbackEx ) {
1247+ logger .error ("Failed to rollback transaction: " + rollbackEx .getMessage ());
1248+ }
1249+
1250+ if (wasAutoCommit ) {
1251+ try {
1252+ getConnection ().setAutoCommit (true );
1253+ } catch (SQLException autoCommitEx ) {
1254+ logger .error ("Failed to restore autoCommit: " + autoCommitEx .getMessage ());
1255+ }
1256+ }
1257+
1258+ throw e ;
1259+ }
1260+ }
10991261}
0 commit comments