Skip to content

Commit 0818ddc

Browse files
committed
Update SQL.java
1 parent 3bb09e4 commit 0818ddc

1 file changed

Lines changed: 81 additions & 123 deletions

File tree

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

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

Lines changed: 81 additions & 123 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
import java.sql.*;
88
import java.util.*;
99
import java.util.regex.Pattern;
10+
import java.util.stream.Collectors;
1011

1112
@SuppressWarnings({"unused", "SqlNoDataSourceInspection", "SqlSourceToSinkFlow"})
1213
public 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

Comments
 (0)