@@ -135,27 +135,140 @@ public void addColumn(String column, DataType dataType) {
135135 }
136136 }
137137
138+ /**
139+ * Decide if a column actually needs to be altered, based on information_schema.
140+ * This avoids doing expensive ALTER TABLEs on every startup.
141+ */
142+ private boolean columnNeedsAlter (Connection conn , String column , String newType ) throws SQLException {
143+ String sql = "SELECT DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, COLUMN_DEFAULT " + "FROM information_schema.COLUMNS "
144+ + "WHERE TABLE_SCHEMA = DATABASE() " + "AND TABLE_NAME = ? " + "AND COLUMN_NAME = ?" ;
145+
146+ try (PreparedStatement ps = conn .prepareStatement (sql )) {
147+ ps .setString (1 , getName ());
148+ ps .setString (2 , column );
149+ try (ResultSet rs = ps .executeQuery ()) {
150+ if (!rs .next ()) {
151+ // Column doesn't exist yet (should be created by checkColumn, but be safe)
152+ return true ;
153+ }
154+
155+ String dataType = rs .getString ("DATA_TYPE" ); // e.g. "varchar", "int", "mediumtext"
156+ Object lenObj = rs .getObject ("CHARACTER_MAXIMUM_LENGTH" );
157+ Long length = null ;
158+ if (lenObj instanceof Number ) {
159+ length = ((Number ) lenObj ).longValue (); // handles BigInteger, Long, Integer, etc.
160+ }
161+ String defaultVal = rs .getString ("COLUMN_DEFAULT" );
162+
163+ String typeUpper = newType .toUpperCase ().trim ();
164+
165+ // MEDIUMTEXT
166+ if (typeUpper .equals ("MEDIUMTEXT" )) {
167+ return !dataType .equalsIgnoreCase ("mediumtext" );
168+ }
169+
170+ // TEXT
171+ if (typeUpper .equals ("TEXT" )) {
172+ return !dataType .equalsIgnoreCase ("text" );
173+ }
174+
175+ // LONGTEXT
176+ if (typeUpper .equals ("LONGTEXT" )) {
177+ return !dataType .equalsIgnoreCase ("longtext" );
178+ }
179+
180+ // VARCHAR(N)
181+ if (typeUpper .startsWith ("VARCHAR(" )) {
182+ int open = typeUpper .indexOf ('(' );
183+ int close = typeUpper .indexOf (')' , open + 1 );
184+ if (open != -1 && close != -1 ) {
185+ String lenStr = typeUpper .substring (open + 1 , close );
186+ int expectedLen ;
187+ try {
188+ expectedLen = Integer .parseInt (lenStr );
189+ } catch (NumberFormatException ex ) {
190+ // if we can't parse, be safe and ALTER
191+ return true ;
192+ }
193+
194+ boolean typeMatches = dataType .equalsIgnoreCase ("varchar" );
195+ boolean lengthMatches = (length != null && length == expectedLen );
196+ return !(typeMatches && lengthMatches );
197+ }
198+ // malformed definition, fallback to altering
199+ return true ;
200+ }
201+
202+ // INT [DEFAULT '0'] style
203+ if (typeUpper .startsWith ("INT" )) {
204+ boolean isIntFamily = dataType .equalsIgnoreCase ("int" ) || dataType .equalsIgnoreCase ("integer" )
205+ || dataType .equalsIgnoreCase ("mediumint" ) || dataType .equalsIgnoreCase ("smallint" )
206+ || dataType .equalsIgnoreCase ("tinyint" ) || dataType .equalsIgnoreCase ("bigint" );
207+
208+ boolean expectDefaultZero = typeUpper .contains ("DEFAULT '0'" ) || typeUpper .contains ("DEFAULT 0" );
209+
210+ if (!expectDefaultZero ) {
211+ // Only care that it's some INT-like type
212+ return !isIntFamily ;
213+ } else {
214+ boolean defaultIsZero = defaultVal != null && defaultVal .trim ().equals ("0" );
215+ return !(isIntFamily && defaultIsZero );
216+ }
217+ }
218+
219+ // For any types we don't explicitly handle, be conservative and ALTER.
220+ return true ;
221+ }
222+ }
223+ }
224+
138225 public void alterColumnType (final String column , final String newType ) {
139226 checkColumn (column , DataType .STRING );
140- debugLog ("Altering column `" + column + "` to " + newType );
141- if (newType .contains ("INT" )) {
142- try {
143- Query query = new Query (mysql , "UPDATE " + getName () + " SET `" + column
144- + "` = '0' where trim(coalesce(" + column + ", '')) = '';" );
145- query .executeUpdateAsync ();
146- } catch (SQLException e ) {
147- e .printStackTrace ();
227+
228+ // First inspect existing type; skip ALTER if it's already correct
229+ try (Connection conn = mysql .getConnectionManager ().getConnection ()) {
230+ if (!columnNeedsAlter (conn , column , newType )) {
231+ debugLog ("GlobalMySQL: Column `" + column + "` already matches " + newType + ", skipping ALTER" );
232+ // Make sure intColumns stays in sync if we've decided it's an int
233+ if (newType .toUpperCase ().contains ("INT" ) && !intColumns .contains (column )) {
234+ intColumns .add (column );
235+ }
236+ return ;
148237 }
149- } else {
150- Query query ;
238+ } catch (SQLException e ) {
239+ // If inspection fails, log and fall back to doing the ALTER anyway
240+ debugLog ("GlobalMySQL: Failed to inspect column " + getName () + "." + column + " – running ALTER anyway" );
241+ debugEx (e );
242+ }
243+
244+ debugLog ("Altering column `" + column + "` to " + newType );
245+
246+ // If going to INT, normalise empty strings to 0 first to avoid conversion
247+ // issues.
248+ if (newType .toUpperCase ().contains ("INT" )) {
151249 try {
152- query = new Query (mysql , "ALTER TABLE " + getName () + " MODIFY `" + column + "` " + newType + ";" );
153- query .executeUpdateAsync ();
250+ Query fixQuery = new Query (mysql , "UPDATE " + getName () + " SET `" + column
251+ + "` = '0' WHERE TRIM(COALESCE(" + column + ", '')) = '';" );
252+ fixQuery .executeUpdateAsync ();
154253 } catch (SQLException e ) {
155254 e .printStackTrace ();
156255 }
157256 }
158257
258+ try {
259+ Query alterQuery = new Query (mysql ,
260+ "ALTER TABLE " + getName () + " MODIFY `" + column + "` " + newType + ";" );
261+ alterQuery .executeUpdateAsync ();
262+ } catch (SQLException e ) {
263+ e .printStackTrace ();
264+ }
265+
266+ // Track int columns for getExact/isIntColumn
267+ if (newType .toUpperCase ().contains ("INT" )) {
268+ if (!intColumns .contains (column )) {
269+ intColumns .add (column );
270+ }
271+ }
159272 }
160273
161274 public void checkColumn (String column , DataType dataType ) {
0 commit comments