11package io .github .intisy .utils .database ;
2-
32import io .github .intisy .simple .logger .EmptyLogger ;
43import io .github .intisy .simple .logger .SimpleLogger ;
54
65import java .io .File ;
76import java .sql .*;
87import java .util .*;
98import java .util .regex .Pattern ;
10-
119@ SuppressWarnings ({"unused" , "SqlNoDataSourceInspection" , "SqlSourceToSinkFlow" })
1210public 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