diff --git a/src/main/java/fr/xephi/authme/datasource/MySQL.java b/src/main/java/fr/xephi/authme/datasource/MySQL.java
index 06a5422b..24b28777 100644
--- a/src/main/java/fr/xephi/authme/datasource/MySQL.java
+++ b/src/main/java/fr/xephi/authme/datasource/MySQL.java
@@ -20,7 +20,6 @@ import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
-import java.sql.Types;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
@@ -193,17 +192,19 @@ 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;");
+ } else {
+ MySqlMigrater.migrateLastIpColumn(st, md, tableName, col);
}
if (isColumnMissing(md, col.LAST_LOGIN)) {
st.executeUpdate("ALTER TABLE " + tableName
+ " ADD COLUMN " + col.LAST_LOGIN + " BIGINT;");
} else {
- migrateLastLoginColumn(st, md);
+ MySqlMigrater.migrateLastLoginColumn(st, md, tableName, col);
}
if (isColumnMissing(md, col.REGISTRATION_DATE)) {
- addRegistrationDateColumn(st);
+ MySqlMigrater.addRegistrationDateColumn(st, tableName, col);
}
if (isColumnMissing(md, col.REGISTRATION_IP)) {
@@ -730,103 +731,4 @@ public class MySQL implements DataSource {
.locPitch(row.getFloat(col.LASTLOC_PITCH))
.build();
}
-
- /**
- * Checks if the last login column has a type that needs to be migrated.
- *
- * @param st Statement object to the database
- * @param metaData lastlogin column meta data
- */
- private void migrateLastLoginColumn(Statement st, DatabaseMetaData metaData) throws SQLException {
- final int columnType;
- try (ResultSet rs = metaData.getColumns(null, null, tableName, col.LAST_LOGIN)) {
- if (!rs.next()) {
- ConsoleLogger.warning("Could not get LAST_LOGIN meta data. This should never happen!");
- return;
- }
- columnType = rs.getInt("DATA_TYPE");
- }
-
- if (columnType == Types.TIMESTAMP) {
- migrateLastLoginColumnFromTimestamp(st);
- } else if (columnType == Types.INTEGER) {
- migrateLastLoginColumnFromInt(st);
- }
- }
-
- /**
- * Performs conversion of lastlogin column from timestamp type to bigint.
- *
- * @param st Statement object to the database
- * @see #477
- */
- private void migrateLastLoginColumnFromTimestamp(Statement st) throws SQLException {
- ConsoleLogger.info("Migrating lastlogin column from timestamp to bigint");
- final String lastLoginOld = col.LAST_LOGIN + "_old";
-
- // Rename lastlogin to lastlogin_old
- String sql = String.format("ALTER TABLE %s CHANGE COLUMN %s %s BIGINT",
- tableName, col.LAST_LOGIN, lastLoginOld);
- st.execute(sql);
-
- // Create lastlogin column
- sql = String.format("ALTER TABLE %s ADD COLUMN %s "
- + "BIGINT NOT NULL DEFAULT 0 AFTER %s",
- tableName, col.LAST_LOGIN, col.LAST_IP);
- st.execute(sql);
-
- // Set values of lastlogin based on lastlogin_old
- sql = String.format("UPDATE %s SET %s = UNIX_TIMESTAMP(%s) * 1000",
- tableName, col.LAST_LOGIN, lastLoginOld);
- st.execute(sql);
-
- // Drop lastlogin_old
- sql = String.format("ALTER TABLE %s DROP COLUMN %s",
- tableName, lastLoginOld);
- st.execute(sql);
- ConsoleLogger.info("Finished migration of lastlogin (timestamp to bigint)");
- }
-
- /**
- * Performs conversion of lastlogin column from int to bigint.
- *
- * @param st Statement object to the database
- * @see
- * #887: Migrate lastlogin column from int32 to bigint
- */
- private void migrateLastLoginColumnFromInt(Statement st) throws SQLException {
- // Change from int to bigint
- ConsoleLogger.info("Migrating lastlogin column from int to bigint");
- String sql = String.format("ALTER TABLE %s MODIFY %s BIGINT;", tableName, col.LAST_LOGIN);
- st.execute(sql);
-
- // Migrate timestamps in seconds format to milliseconds format if they are plausible
- int rangeStart = 1262304000; // timestamp for 2010-01-01
- int rangeEnd = 1514678400; // timestamp for 2017-12-31
- sql = String.format("UPDATE %s SET %s = %s * 1000 WHERE %s > %d AND %s < %d;",
- tableName, col.LAST_LOGIN, col.LAST_LOGIN, col.LAST_LOGIN, rangeStart, col.LAST_LOGIN, rangeEnd);
- int changedRows = st.executeUpdate(sql);
-
- ConsoleLogger.warning("You may have entries with invalid timestamps. Please check your data "
- + "before purging. " + changedRows + " rows were migrated from seconds to milliseconds.");
- }
-
- /**
- * Creates the column for registration date and sets all entries to the current timestamp.
- * We do so in order to avoid issues with purging, where entries with 0 / NULL might get
- * purged immediately on startup otherwise.
- *
- * @param st Statement object to the database
- */
- private void addRegistrationDateColumn(Statement st) throws SQLException {
- st.executeUpdate("ALTER TABLE " + tableName
- + " ADD COLUMN " + col.REGISTRATION_DATE + " BIGINT NOT NULL;");
-
- // Use the timestamp from Java to avoid timezone issues in case JVM and database are out of sync
- long currentTimestamp = System.currentTimeMillis();
- int updatedRows = st.executeUpdate(String.format("UPDATE %s SET %s = %d;",
- tableName, col.REGISTRATION_DATE, currentTimestamp));
- ConsoleLogger.info("Created column '" + col.REGISTRATION_DATE + "' and set the current timestamp, "
- + currentTimestamp + ", to all " + updatedRows + " rows");
- }
}
diff --git a/src/main/java/fr/xephi/authme/datasource/MySqlMigrater.java b/src/main/java/fr/xephi/authme/datasource/MySqlMigrater.java
new file mode 100644
index 00000000..944691ee
--- /dev/null
+++ b/src/main/java/fr/xephi/authme/datasource/MySqlMigrater.java
@@ -0,0 +1,151 @@
+package fr.xephi.authme.datasource;
+
+import fr.xephi.authme.ConsoleLogger;
+
+import java.sql.DatabaseMetaData;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.sql.Statement;
+import java.sql.Types;
+
+/**
+ * Performs migrations on the MySQL data source if necessary.
+ */
+final class MySqlMigrater {
+
+ private MySqlMigrater() {
+ }
+
+ /**
+ * Changes the last IP column to be nullable if it has a NOT NULL constraint without a default value.
+ * Background: Before 5.2, the last IP column was initialized to be {@code NOT NULL} without a default.
+ * With the introduction of a registration IP column we no longer want to set the last IP column on registration.
+ *
+ * @param st Statement object to the database
+ * @param metaData column metadata for the table
+ * @param tableName the MySQL table's name
+ * @param col the column names configuration
+ */
+ static void migrateLastIpColumn(Statement st, DatabaseMetaData metaData,
+ String tableName, Columns col) throws SQLException {
+ final boolean isNotNullWithoutDefault = SqlDataSourceUtils.isNotNullColumn(metaData, tableName, col.LAST_IP)
+ && SqlDataSourceUtils.getColumnDefaultValue(metaData, tableName, col.LAST_IP) == null;
+
+ if (isNotNullWithoutDefault) {
+ String sql = String.format("ALTER TABLE %s MODIFY %s VARCHAR(40) CHARACTER SET ascii COLLATE ascii_bin",
+ tableName, col.LAST_IP);
+ st.execute(sql);
+ ConsoleLogger.info("Changed last login column to allow NULL values. Please verify the registration feature "
+ + "if you are hooking into a forum.");
+ }
+ }
+
+ /**
+ * Checks if the last login column has a type that needs to be migrated.
+ *
+ * @param st Statement object to the database
+ * @param metaData column metadata for the table
+ * @param tableName the MySQL table's name
+ * @param col the column names configuration
+ */
+ static void migrateLastLoginColumn(Statement st, DatabaseMetaData metaData,
+ String tableName, Columns col) throws SQLException {
+ final int columnType;
+ try (ResultSet rs = metaData.getColumns(null, null, tableName, col.LAST_LOGIN)) {
+ if (!rs.next()) {
+ ConsoleLogger.warning("Could not get LAST_LOGIN meta data. This should never happen!");
+ return;
+ }
+ columnType = rs.getInt("DATA_TYPE");
+ }
+
+ if (columnType == Types.TIMESTAMP) {
+ migrateLastLoginColumnFromTimestamp(st, tableName, col);
+ } else if (columnType == Types.INTEGER) {
+ migrateLastLoginColumnFromInt(st, tableName, col);
+ }
+ }
+
+ /**
+ * Performs conversion of lastlogin column from timestamp type to bigint.
+ *
+ * @param st Statement object to the database
+ * @param tableName the table name
+ * @param col the column names configuration
+ * @see #477
+ */
+ private static void migrateLastLoginColumnFromTimestamp(Statement st, String tableName,
+ Columns col) throws SQLException {
+ ConsoleLogger.info("Migrating lastlogin column from timestamp to bigint");
+ final String lastLoginOld = col.LAST_LOGIN + "_old";
+
+ // Rename lastlogin to lastlogin_old
+ String sql = String.format("ALTER TABLE %s CHANGE COLUMN %s %s BIGINT",
+ tableName, col.LAST_LOGIN, lastLoginOld);
+ st.execute(sql);
+
+ // Create lastlogin column
+ sql = String.format("ALTER TABLE %s ADD COLUMN %s "
+ + "BIGINT NOT NULL DEFAULT 0 AFTER %s",
+ tableName, col.LAST_LOGIN, col.LAST_IP);
+ st.execute(sql);
+
+ // Set values of lastlogin based on lastlogin_old
+ sql = String.format("UPDATE %s SET %s = UNIX_TIMESTAMP(%s) * 1000",
+ tableName, col.LAST_LOGIN, lastLoginOld);
+ st.execute(sql);
+
+ // Drop lastlogin_old
+ sql = String.format("ALTER TABLE %s DROP COLUMN %s",
+ tableName, lastLoginOld);
+ st.execute(sql);
+ ConsoleLogger.info("Finished migration of lastlogin (timestamp to bigint)");
+ }
+
+ /**
+ * Performs conversion of lastlogin column from int to bigint.
+ *
+ * @param st Statement object to the database
+ * @param tableName the table name
+ * @param col the column names configuration
+ * @see
+ * #887: Migrate lastlogin column from int32 to bigint
+ */
+ private static void migrateLastLoginColumnFromInt(Statement st, String tableName, Columns col) throws SQLException {
+ // Change from int to bigint
+ ConsoleLogger.info("Migrating lastlogin column from int to bigint");
+ String sql = String.format("ALTER TABLE %s MODIFY %s BIGINT;", tableName, col.LAST_LOGIN);
+ st.execute(sql);
+
+ // Migrate timestamps in seconds format to milliseconds format if they are plausible
+ int rangeStart = 1262304000; // timestamp for 2010-01-01
+ int rangeEnd = 1514678400; // timestamp for 2017-12-31
+ sql = String.format("UPDATE %s SET %s = %s * 1000 WHERE %s > %d AND %s < %d;",
+ tableName, col.LAST_LOGIN, col.LAST_LOGIN, col.LAST_LOGIN, rangeStart, col.LAST_LOGIN, rangeEnd);
+ int changedRows = st.executeUpdate(sql);
+
+ ConsoleLogger.warning("You may have entries with invalid timestamps. Please check your data "
+ + "before purging. " + changedRows + " rows were migrated from seconds to milliseconds.");
+ }
+
+ /**
+ * Creates the column for registration date and sets all entries to the current timestamp.
+ * We do so in order to avoid issues with purging, where entries with 0 / NULL might get
+ * purged immediately on startup otherwise.
+ *
+ * @param st Statement object to the database
+ * @param tableName the table name
+ * @param col the column names configuration
+ */
+ static void addRegistrationDateColumn(Statement st, String tableName, Columns col) throws SQLException {
+ st.executeUpdate("ALTER TABLE " + tableName
+ + " ADD COLUMN " + col.REGISTRATION_DATE + " BIGINT NOT NULL DEFAULT 0;");
+
+ // Use the timestamp from Java to avoid timezone issues in case JVM and database are out of sync
+ long currentTimestamp = System.currentTimeMillis();
+ int updatedRows = st.executeUpdate(String.format("UPDATE %s SET %s = %d;",
+ tableName, col.REGISTRATION_DATE, currentTimestamp));
+ ConsoleLogger.info("Created column '" + col.REGISTRATION_DATE + "' and set the current timestamp, "
+ + currentTimestamp + ", to all " + updatedRows + " rows");
+ }
+}
diff --git a/src/main/java/fr/xephi/authme/datasource/SqLiteMigrater.java b/src/main/java/fr/xephi/authme/datasource/SqLiteMigrater.java
index b0da1da1..6f8f2a37 100644
--- a/src/main/java/fr/xephi/authme/datasource/SqLiteMigrater.java
+++ b/src/main/java/fr/xephi/authme/datasource/SqLiteMigrater.java
@@ -1,12 +1,10 @@
package fr.xephi.authme.datasource;
import fr.xephi.authme.ConsoleLogger;
-import fr.xephi.authme.initialization.DataFolder;
import fr.xephi.authme.settings.Settings;
import fr.xephi.authme.settings.properties.DatabaseSettings;
import fr.xephi.authme.util.FileUtils;
-import javax.inject.Inject;
import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
@@ -24,15 +22,12 @@ import java.util.Date;
*/
class SqLiteMigrater {
- @DataFolder
private final File dataFolder;
-
private final String databaseName;
private final String tableName;
private final Columns col;
- @Inject
- SqLiteMigrater(Settings settings, @DataFolder File dataFolder) {
+ SqLiteMigrater(Settings settings, File dataFolder) {
this.dataFolder = dataFolder;
this.databaseName = settings.getProperty(DatabaseSettings.MYSQL_DATABASE);
this.tableName = settings.getProperty(DatabaseSettings.MYSQL_TABLE);