Skip to content

Commit 3c11764

Browse files
committed
Optimize column alteration checks in MySQL
Prevent unnecessary ALTER TABLE operations
1 parent 92117c4 commit 3c11764

File tree

2 files changed

+225
-12
lines changed

2 files changed

+225
-12
lines changed

AdvancedCore/src/main/java/com/bencodez/advancedcore/api/user/userstorage/mysql/MySQL.java

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,19 @@ public void addColumn(String column, DataType dataType) {
149149

150150
public void alterColumnType(final String column, final String newType) {
151151
checkColumn(column, DataType.STRING);
152+
153+
// First inspect existing type; skip ALTER if it's already correct
154+
try (Connection conn = mysql.getConnectionManager().getConnection()) {
155+
if (!columnNeedsAlter(conn, column, newType)) {
156+
plugin.debug("MYSQL: Column `" + column + "` already matches " + newType + ", skipping ALTER");
157+
return;
158+
}
159+
} catch (SQLException e) {
160+
// If inspection fails, log and fall back to doing the ALTER anyway
161+
plugin.debug("MYSQL: Failed to inspect column " + getName() + "." + column + " – running ALTER anyway");
162+
plugin.debug(e);
163+
}
164+
152165
plugin.debug("MYSQL QUERY: Altering column `" + column + "` to " + newType);
153166
try {
154167
Query query = new Query(mysql, "ALTER TABLE " + getName() + " MODIFY `" + column + "` " + newType + ";");
@@ -158,6 +171,93 @@ public void alterColumnType(final String column, final String newType) {
158171
}
159172
}
160173

174+
/**
175+
* Decide if a column actually needs to be altered, based on information_schema.
176+
* This avoids doing expensive ALTER TABLEs on every startup.
177+
*/
178+
private boolean columnNeedsAlter(Connection conn, String column, String newType) throws SQLException {
179+
String sql = "SELECT DATA_TYPE, CHARACTER_MAXIMUM_LENGTH, COLUMN_DEFAULT " + "FROM information_schema.COLUMNS "
180+
+ "WHERE TABLE_SCHEMA = DATABASE() " + "AND TABLE_NAME = ? " + "AND COLUMN_NAME = ?";
181+
182+
try (PreparedStatement ps = conn.prepareStatement(sql)) {
183+
ps.setString(1, getName());
184+
ps.setString(2, column);
185+
try (ResultSet rs = ps.executeQuery()) {
186+
if (!rs.next()) {
187+
// Column doesn't exist yet (should be created by checkColumn, but be safe)
188+
return true;
189+
}
190+
191+
String dataType = rs.getString("DATA_TYPE"); // e.g. "varchar", "int", "mediumtext"
192+
Object lenObj = rs.getObject("CHARACTER_MAXIMUM_LENGTH");
193+
Long length = null;
194+
if (lenObj instanceof Number) {
195+
length = ((Number) lenObj).longValue(); // handles BigInteger, Long, Integer, etc.
196+
}
197+
String defaultVal = rs.getString("COLUMN_DEFAULT");
198+
199+
String typeUpper = newType.toUpperCase().trim();
200+
201+
// MEDIUMTEXT
202+
if (typeUpper.equals("MEDIUMTEXT")) {
203+
return !dataType.equalsIgnoreCase("mediumtext");
204+
}
205+
206+
// TEXT
207+
if (typeUpper.equals("TEXT")) {
208+
return !dataType.equalsIgnoreCase("text");
209+
}
210+
211+
// LONGTEXT
212+
if (typeUpper.equals("LONGTEXT")) {
213+
return !dataType.equalsIgnoreCase("longtext");
214+
}
215+
216+
// VARCHAR(N)
217+
if (typeUpper.startsWith("VARCHAR(")) {
218+
int open = typeUpper.indexOf('(');
219+
int close = typeUpper.indexOf(')', open + 1);
220+
if (open != -1 && close != -1) {
221+
String lenStr = typeUpper.substring(open + 1, close);
222+
int expectedLen;
223+
try {
224+
expectedLen = Integer.parseInt(lenStr);
225+
} catch (NumberFormatException ex) {
226+
// if we can't parse, be safe and ALTER
227+
return true;
228+
}
229+
230+
boolean typeMatches = dataType.equalsIgnoreCase("varchar");
231+
boolean lengthMatches = (length != null && length == expectedLen);
232+
return !(typeMatches && lengthMatches);
233+
}
234+
// malformed definition, fallback to altering
235+
return true;
236+
}
237+
238+
// INT [DEFAULT '0'] style
239+
if (typeUpper.startsWith("INT")) {
240+
boolean isIntFamily = dataType.equalsIgnoreCase("int") || dataType.equalsIgnoreCase("integer")
241+
|| dataType.equalsIgnoreCase("mediumint") || dataType.equalsIgnoreCase("smallint")
242+
|| dataType.equalsIgnoreCase("tinyint") || dataType.equalsIgnoreCase("bigint");
243+
244+
boolean expectDefaultZero = typeUpper.contains("DEFAULT '0'") || typeUpper.contains("DEFAULT 0");
245+
246+
if (!expectDefaultZero) {
247+
// Only care that it's some INT-like type
248+
return !isIntFamily;
249+
} else {
250+
boolean defaultIsZero = defaultVal != null && defaultVal.trim().equals("0");
251+
return !(isIntFamily && defaultIsZero);
252+
}
253+
}
254+
255+
// For any types we don't explicitly handle, be conservative and ALTER.
256+
return true;
257+
}
258+
}
259+
}
260+
161261
public void checkColumn(String column, DataType dataType) {
162262
synchronized (object4) {
163263
if (!ArrayUtils.containsIgnoreCase((ArrayList<String>) getColumns(), column)) {

AdvancedCore/src/main/java/com/bencodez/advancedcore/bungeeapi/globaldata/GlobalMySQL.java

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

Comments
 (0)