#792 #814 Implement SQLite migration, allow last IP to be nullable in MySQL

- Old SQLite setups have the last IP column as NOT NULL but without a default value. With the new concept (where we don't set a last IP on player registration) it fails.
  - Create an /authme debug child that allows to migrate SQLite (tricky because SQLite does not support dropping or modifying columns)
  - Allow last IP column to be NOT NULL in MySQL as well (extend MySQL /authme debug child)
- Add TODO comments with follow-up issue to extend our commands with new registration IP field
This commit is contained in:
ljacqu
2017-10-21 10:24:40 +02:00
parent b5ea48085c
commit 1651a61063
29 changed files with 688 additions and 130 deletions
@@ -192,7 +192,7 @@ public class MySQL implements DataSource {
if (isColumnMissing(md, col.LAST_IP)) {
st.executeUpdate("ALTER TABLE " + tableName
+ " ADD COLUMN " + col.LAST_IP + " VARCHAR(40) CHARACTER SET ascii COLLATE ascii_bin NOT NULL;");
+ " ADD COLUMN " + col.LAST_IP + " VARCHAR(40) CHARACTER SET ascii COLLATE ascii_bin;");
}
if (isColumnMissing(md, col.LAST_LOGIN)) {
@@ -97,7 +97,7 @@ public class SQLite implements DataSource {
if (isColumnMissing(md, col.LAST_IP)) {
st.executeUpdate("ALTER TABLE " + tableName
+ " ADD COLUMN " + col.LAST_IP + " VARCHAR(40) NOT NULL DEFAULT '';");
+ " ADD COLUMN " + col.LAST_IP + " VARCHAR(40);");
}
if (isColumnMissing(md, col.LAST_LOGIN)) {
@@ -152,10 +152,30 @@ public class SQLite implements DataSource {
st.executeUpdate("ALTER TABLE " + tableName
+ " ADD COLUMN " + col.HAS_SESSION + " INT NOT NULL DEFAULT '0';");
}
if (isMigrationRequired(md)) {
ConsoleLogger.warning("READ ME! Your SQLite database is outdated and cannot save new players.");
ConsoleLogger.warning("Run /authme debug migratesqlite after making a backup");
}
}
ConsoleLogger.info("SQLite Setup finished");
}
/**
* Returns whether the database needs to be migrated.
* <p>
* Background: Before commit 22911a0 (July 2016), new SQLite databases initialized the last IP column to be NOT NULL
* without a default value. Allowing the last IP to be null (#792) is therefore not compatible.
*
* @param metaData the database meta data
* @return true if a migration is necessary, false otherwise
* @throws SQLException .
*/
public boolean isMigrationRequired(DatabaseMetaData metaData) throws SQLException {
return SqlDataSourceUtils.isNotNullColumn(metaData, tableName, col.LAST_IP)
&& SqlDataSourceUtils.getColumnDefaultValue(metaData, tableName, col.LAST_IP) == null;
}
private boolean isColumnMissing(DatabaseMetaData metaData, String columnName) throws SQLException {
try (ResultSet rs = metaData.getColumns(null, null, tableName, columnName)) {
return !rs.next();
@@ -2,13 +2,14 @@ package fr.xephi.authme.datasource;
import fr.xephi.authme.ConsoleLogger;
import java.sql.DatabaseMetaData;
import java.sql.ResultSet;
import java.sql.SQLException;
/**
* Utilities for SQL data sources.
*/
final class SqlDataSourceUtils {
public final class SqlDataSourceUtils {
private SqlDataSourceUtils() {
}
@@ -18,7 +19,7 @@ final class SqlDataSourceUtils {
*
* @param e the exception to log
*/
static void logSqlException(SQLException e) {
public static void logSqlException(SQLException e) {
ConsoleLogger.logException("Error during SQL operation:", e);
}
@@ -31,8 +32,55 @@ final class SqlDataSourceUtils {
* @return the value (which may be null)
* @throws SQLException :)
*/
static Long getNullableLong(ResultSet rs, String columnName) throws SQLException {
public static Long getNullableLong(ResultSet rs, String columnName) throws SQLException {
long longValue = rs.getLong(columnName);
return rs.wasNull() ? null : longValue;
}
/**
* Returns whether the given column has a NOT NULL constraint.
*
* @param metaData the database meta data
* @param tableName the name of the table in which the column is
* @param columnName the name of the column to check
* @return true if the column is NOT NULL, false otherwise
* @throws SQLException :)
*/
public static boolean isNotNullColumn(DatabaseMetaData metaData, String tableName,
String columnName) throws SQLException {
try (ResultSet rs = metaData.getColumns(null, null, tableName, columnName)) {
if (!rs.next()) {
throw new IllegalStateException("Did not find meta data for column '"
+ columnName + "' while checking for not-null constraint");
}
int nullableCode = rs.getInt("NULLABLE");
if (nullableCode == DatabaseMetaData.columnNoNulls) {
return true;
} else if (nullableCode == DatabaseMetaData.columnNullableUnknown) {
ConsoleLogger.warning("Unknown nullable status for column '" + columnName + "'");
}
}
return false;
}
/**
* Returns the default value of a column (as per its SQL definition).
*
* @param metaData the database meta data
* @param tableName the name of the table in which the column is
* @param columnName the name of the column to check
* @return the default value of the column (may be null)
* @throws SQLException :)
*/
public static Object getColumnDefaultValue(DatabaseMetaData metaData, String tableName,
String columnName) throws SQLException {
try (ResultSet rs = metaData.getColumns(null, null, tableName, columnName)) {
if (!rs.next()) {
throw new IllegalStateException("Did not find meta data for column '"
+ columnName + "' while checking its default value");
}
return rs.getObject("COLUMN_DEF");
}
}
}