Skip to content

Commit c1fa94d

Browse files
committed
Update SQL.java
1 parent f85c9c7 commit c1fa94d

1 file changed

Lines changed: 162 additions & 0 deletions

File tree

  • src/main/java/io/github/intisy/utils/database

src/main/java/io/github/intisy/utils/database/SQL.java

Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)