Skip to content

Commit 888b50c

Browse files
committed
Update SQL.java
1 parent c1fa94d commit 888b50c

1 file changed

Lines changed: 73 additions & 71 deletions

File tree

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

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

Lines changed: 73 additions & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,13 @@
11
package io.github.intisy.utils.database;
2-
32
import io.github.intisy.simple.logger.EmptyLogger;
43
import io.github.intisy.simple.logger.SimpleLogger;
54

65
import java.io.File;
76
import java.sql.*;
87
import java.util.*;
98
import java.util.regex.Pattern;
10-
119
@SuppressWarnings({"unused", "SqlNoDataSourceInspection", "SqlSourceToSinkFlow"})
1210
public class SQL {
13-
1411
private static final Pattern VALID_IDENTIFIER_PATTERN = Pattern.compile("^[A-Za-z_][A-Za-z0-9_]*$");
1512
private static final int MAX_IDENTIFIER_LENGTH = 64;
1613

@@ -192,7 +189,8 @@ public void close() {
192189
if (!connection.isClosed()) {
193190
connection.setAutoCommit(true);
194191
}
195-
} catch (SQLException ignored) {}
192+
} catch (SQLException ignored) {
193+
}
196194
}
197195
}
198196
if (!connection.isClosed()) {
@@ -231,8 +229,8 @@ public void createTable(String tableName, List<String> columnDefs, List<String>
231229
if (columnDefs == null || columnDefs.isEmpty()) {
232230
throw new IllegalArgumentException("At least one column definition is required.");
233231
}
234-
for(String colDef : columnDefs) {
235-
if(colDef == null || colDef.trim().isEmpty()) {
232+
for (String colDef : columnDefs) {
233+
if (colDef == null || colDef.trim().isEmpty()) {
236234
throw new IllegalArgumentException("Column definition cannot be null or empty.");
237235
}
238236
}
@@ -243,8 +241,8 @@ public void createTable(String tableName, List<String> columnDefs, List<String>
243241
sql.append(String.join(", ", columnDefs));
244242

245243
if (constraints != null) {
246-
for(String constraint : constraints) {
247-
if(constraint != null && !constraint.trim().isEmpty()) {
244+
for (String constraint : constraints) {
245+
if (constraint != null && !constraint.trim().isEmpty()) {
248246
sql.append(", ").append(constraint);
249247
} else {
250248
throw new IllegalArgumentException("Table constraint cannot be null or empty.");
@@ -318,7 +316,7 @@ public List<Map<String, Object>> executeQuery(String sql, List<?> params) {
318316
bindParameters(pstmt, params);
319317

320318
boolean isQuery = sql.trim().toLowerCase().startsWith("select");
321-
319+
322320
if (isQuery) {
323321
try (ResultSet rs = pstmt.executeQuery()) {
324322
ResultSetMetaData metaData = rs.getMetaData();
@@ -344,7 +342,7 @@ public List<Map<String, Object>> executeQuery(String sql, List<?> params) {
344342
resultRow.put("affectedRows", affected);
345343
results.add(resultRow);
346344
}
347-
345+
348346
return results;
349347
} catch (SQLException e) {
350348
logger.error("Query failed: " + e.getMessage() + " [SQL: " + sql + "]");
@@ -658,7 +656,7 @@ public List<Map<String, Object>> selectData(String tableName, List<String> colum
658656
selectColsString = "*";
659657
} else {
660658
List<String> quotedCols = new ArrayList<>();
661-
for(String col : columnsToSelect) {
659+
for (String col : columnsToSelect) {
662660
validateIdentifier(col);
663661
quotedCols.add(quoteIdentifier(col));
664662
}
@@ -730,9 +728,9 @@ public <T> List<T> selectSingleColumn(String tableName, String columnToSelect, M
730728
List<Map<String, Object>> rawResults = selectData(tableName, Collections.singletonList(columnToSelect), whereClause);
731729
List<T> results = new ArrayList<>();
732730

733-
for(Map<String, Object> row : rawResults) {
731+
for (Map<String, Object> row : rawResults) {
734732
Object value = row.get(columnToSelect);
735-
if(value == null) {
733+
if (value == null) {
736734
results.add(null);
737735
} else if (expectedType.isInstance(value)) {
738736
results.add(expectedType.cast(value));
@@ -749,17 +747,16 @@ public <T> List<T> selectSingleColumn(String tableName, String columnToSelect, M
749747
} else if (expectedType == Float.class && value instanceof Number) {
750748
results.add(expectedType.cast(((Number) value).floatValue()));
751749
} else if (expectedType == Boolean.class) {
752-
if(value instanceof Boolean) {
750+
if (value instanceof Boolean) {
753751
results.add(expectedType.cast(value));
754-
} else if(value instanceof Number) {
755-
results.add(expectedType.cast(((Number)value).intValue() != 0));
752+
} else if (value instanceof Number) {
753+
results.add(expectedType.cast(((Number) value).intValue() != 0));
756754
} else if (value instanceof String) {
757-
results.add(expectedType.cast(Boolean.parseBoolean((String)value)));
755+
results.add(expectedType.cast(Boolean.parseBoolean((String) value)));
758756
} else {
759757
throw new ClassCastException("Cannot reliably cast " + value.getClass().getName() + " to Boolean");
760758
}
761-
}
762-
else {
759+
} else {
763760
throw new ClassCastException("Cannot automatically cast value of type " + value.getClass().getName() + " to " + expectedType.getName());
764761
}
765762
} catch (ClassCastException e) {
@@ -782,12 +779,12 @@ public int[] insertBatchData(String tableName, List<Map<String, Object>> dataRow
782779
throw new IllegalArgumentException("First data row map cannot be null or empty.");
783780
}
784781
Set<String> columnSet = new LinkedHashSet<>(firstRow.keySet());
785-
if(columnSet.isEmpty()) {
782+
if (columnSet.isEmpty()) {
786783
throw new IllegalArgumentException("No columns found in the first data row.");
787784
}
788785
List<String> columns = new ArrayList<>(columnSet);
789786
List<String> quotedColumns = new ArrayList<>();
790-
for(String col : columns) {
787+
for (String col : columns) {
791788
validateIdentifier(col);
792789
quotedColumns.add(quoteIdentifier(col));
793790
}
@@ -1033,15 +1030,15 @@ public long count(String tableName, Map<String, Object> whereClause) {
10331030
validateIdentifier(tableName);
10341031

10351032
StringBuilder sql = new StringBuilder("SELECT COUNT(*) FROM ")
1036-
.append(quoteIdentifier(tableName));
1033+
.append(quoteIdentifier(tableName));
10371034

10381035
List<Object> whereValues = new ArrayList<>();
10391036
if (whereClause != null && !whereClause.isEmpty()) {
10401037
List<String> whereConditions = new ArrayList<>();
10411038
for (Map.Entry<String, Object> entry : whereClause.entrySet()) {
10421039
validateIdentifier(entry.getKey());
10431040
whereConditions.add(quoteIdentifier(entry.getKey()) +
1044-
(entry.getValue() == null ? " IS ?" : " = ?"));
1041+
(entry.getValue() == null ? " IS ?" : " = ?"));
10451042
whereValues.add(entry.getValue());
10461043
}
10471044
sql.append(" WHERE ").append(String.join(" AND ", whereConditions));
@@ -1065,7 +1062,7 @@ public long count(String tableName, Map<String, Object> whereClause) {
10651062
}
10661063
} catch (SQLException e) {
10671064
logger.error("Count failed for table '" + tableName + "': " +
1068-
e.getMessage() + " [SQL: " + sqlString + "]");
1065+
e.getMessage() + " [SQL: " + sqlString + "]");
10691066
throw new RuntimeException(e);
10701067
}
10711068
}
@@ -1098,51 +1095,55 @@ public void execute(String sql, Object... params) {
10981095
}
10991096

11001097
public boolean updateTableSchema(String tableName, List<String> newColumnDefs) {
1098+
return updateTableSchema(tableName, newColumnDefs, null);
1099+
}
1100+
1101+
public boolean updateTableSchema(String tableName, List<String> newColumnDefs, List<String> newConstraints) {
11011102
validateIdentifier(tableName);
11021103
if (newColumnDefs == null || newColumnDefs.isEmpty()) {
11031104
throw new IllegalArgumentException("At least one column definition is required.");
11041105
}
1105-
1106+
11061107
try {
11071108
DatabaseMetaData metaData = getConnection().getMetaData();
11081109
List<String> currentColumns = getTableColumns(tableName, metaData);
1109-
1110+
11101111
if (currentColumns.isEmpty()) {
11111112
logger.warn("Table '" + tableName + "' does not exist. Creating it instead.");
1112-
createTable(tableName, newColumnDefs);
1113+
createTable(tableName, newColumnDefs, newConstraints);
11131114
return true;
11141115
}
1115-
1116+
11161117
List<String> newColumnNames = new ArrayList<>();
11171118
Map<String, String> newColumnDefinitions = new HashMap<>();
1118-
1119+
11191120
for (String colDef : newColumnDefs) {
11201121
if (colDef == null || colDef.trim().isEmpty()) {
11211122
throw new IllegalArgumentException("Column definition cannot be null or empty.");
11221123
}
1123-
1124+
11241125
String[] parts = colDef.trim().split("\\s+", 2);
11251126
if (parts.length < 1) {
11261127
throw new IllegalArgumentException("Invalid column definition: " + colDef);
11271128
}
1128-
1129+
11291130
String columnName = parts[0];
1130-
if ((columnName.startsWith("\"") && columnName.endsWith("\"")) ||
1131-
(columnName.startsWith("`") && columnName.endsWith("`"))) {
1131+
if ((columnName.startsWith("\"") && columnName.endsWith("\"")) ||
1132+
(columnName.startsWith("`") && columnName.endsWith("`"))) {
11321133
columnName = columnName.substring(1, columnName.length() - 1);
11331134
}
1134-
1135+
11351136
newColumnNames.add(columnName);
11361137
newColumnDefinitions.put(columnName, colDef);
11371138
}
1138-
1139+
11391140
boolean changes = false;
1140-
1141+
11411142
for (String newCol : newColumnNames) {
11421143
if (!currentColumns.contains(newCol)) {
1143-
String addColumnSql = "ALTER TABLE " + quoteIdentifier(tableName) +
1144-
" ADD COLUMN " + newColumnDefinitions.get(newCol);
1145-
1144+
String addColumnSql = "ALTER TABLE " + quoteIdentifier(tableName) +
1145+
" ADD COLUMN " + newColumnDefinitions.get(newCol);
1146+
11461147
logger.debug("Adding column: " + addColumnSql);
11471148
try (Statement stmt = getConnection().createStatement()) {
11481149
stmt.execute(addColumnSql);
@@ -1151,13 +1152,13 @@ public boolean updateTableSchema(String tableName, List<String> newColumnDefs) {
11511152
}
11521153
}
11531154
}
1154-
1155+
11551156
if (databaseType != DatabaseType.SQLITE) {
11561157
for (String oldCol : currentColumns) {
11571158
if (!newColumnNames.contains(oldCol)) {
1158-
String dropColumnSql = "ALTER TABLE " + quoteIdentifier(tableName) +
1159-
" DROP COLUMN " + quoteIdentifier(oldCol);
1160-
1159+
String dropColumnSql = "ALTER TABLE " + quoteIdentifier(tableName) +
1160+
" DROP COLUMN " + quoteIdentifier(oldCol);
1161+
11611162
logger.debug("Dropping column: " + dropColumnSql);
11621163
try (Statement stmt = getConnection().createStatement()) {
11631164
stmt.execute(dropColumnSql);
@@ -1173,89 +1174,90 @@ public boolean updateTableSchema(String tableName, List<String> newColumnDefs) {
11731174
columnsToRemove.add(oldCol);
11741175
}
11751176
}
1176-
1177+
11771178
if (!columnsToRemove.isEmpty()) {
1178-
recreateTableWithNewSchema(tableName, currentColumns, columnsToRemove, newColumnDefinitions);
1179+
recreateTableWithNewSchema(tableName, currentColumns, columnsToRemove, newColumnDefinitions, newConstraints);
11791180
changes = true;
11801181
}
11811182
}
1182-
1183+
11831184
return changes;
11841185
} catch (SQLException e) {
11851186
logger.error("Failed to update table schema for '" + tableName + "': " + e.getMessage());
11861187
throw new RuntimeException(e);
11871188
}
11881189
}
11891190

1190-
private void recreateTableWithNewSchema(String tableName, List<String> currentColumns,
1191-
List<String> columnsToRemove, Map<String, String> newColumnDefinitions)
1192-
throws SQLException {
1193-
1191+
private void recreateTableWithNewSchema(String tableName, List<String> currentColumns,
1192+
List<String> columnsToRemove, Map<String, String> newColumnDefinitions,
1193+
List<String> newConstraints)
1194+
throws SQLException {
1195+
11941196
logger.debug("Recreating table '" + tableName + "' to remove columns: " + String.join(", ", columnsToRemove));
1195-
1197+
11961198
boolean wasAutoCommit = getConnection().getAutoCommit();
11971199
if (wasAutoCommit) {
11981200
getConnection().setAutoCommit(false);
11991201
}
1200-
1202+
12011203
try {
12021204
String tempTableName = tableName + "_temp_" + System.currentTimeMillis();
1203-
1205+
12041206
List<String> newTableColumns = new ArrayList<>();
12051207
for (String colName : newColumnDefinitions.keySet()) {
12061208
newTableColumns.add(newColumnDefinitions.get(colName));
12071209
}
1208-
1209-
createTable(tempTableName, newTableColumns);
1210-
1210+
1211+
createTable(tempTableName, newTableColumns, newConstraints);
1212+
12111213
List<String> columnsToCopy = new ArrayList<>();
12121214
for (String col : currentColumns) {
12131215
if (!columnsToRemove.contains(col)) {
12141216
columnsToCopy.add(quoteIdentifier(col));
12151217
}
12161218
}
1217-
1218-
String copyDataSql = "INSERT INTO " + quoteIdentifier(tempTableName) +
1219-
" SELECT " + String.join(", ", columnsToCopy) +
1220-
" FROM " + quoteIdentifier(tableName);
1221-
1219+
1220+
String copyDataSql = "INSERT INTO " + quoteIdentifier(tempTableName) +
1221+
" SELECT " + String.join(", ", columnsToCopy) +
1222+
" FROM " + quoteIdentifier(tableName);
1223+
12221224
logger.debug("Copying data: " + copyDataSql);
12231225
try (Statement stmt = getConnection().createStatement()) {
12241226
stmt.execute(copyDataSql);
12251227
}
1226-
1228+
12271229
deleteTable(tableName);
1228-
1229-
String renameSql = "ALTER TABLE " + quoteIdentifier(tempTableName) +
1230-
" RENAME TO " + quoteIdentifier(tableName);
1231-
1230+
1231+
String renameSql = "ALTER TABLE " + quoteIdentifier(tempTableName) +
1232+
" RENAME TO " + quoteIdentifier(tableName);
1233+
12321234
logger.debug("Renaming table: " + renameSql);
12331235
try (Statement stmt = getConnection().createStatement()) {
12341236
stmt.execute(renameSql);
12351237
}
1236-
1238+
12371239
if (wasAutoCommit) {
12381240
getConnection().commit();
12391241
getConnection().setAutoCommit(true);
12401242
}
1241-
1243+
12421244
logger.info("Successfully recreated table '" + tableName + "' with updated schema");
12431245
} catch (SQLException e) {
12441246
try {
12451247
getConnection().rollback();
12461248
} catch (SQLException rollbackEx) {
12471249
logger.error("Failed to rollback transaction: " + rollbackEx.getMessage());
12481250
}
1249-
1251+
12501252
if (wasAutoCommit) {
12511253
try {
12521254
getConnection().setAutoCommit(true);
12531255
} catch (SQLException autoCommitEx) {
12541256
logger.error("Failed to restore autoCommit: " + autoCommitEx.getMessage());
12551257
}
12561258
}
1257-
1259+
12581260
throw e;
12591261
}
12601262
}
1261-
}
1263+
}

0 commit comments

Comments
 (0)