import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
/**
* Drops every table and view of one database so Flyway can rebuild the schema from the migrations.
*
*
Run through {@code scripts/reset-test-db.ps1}; this program exists because the checkout has no
* MySQL client and no Docker, while the JDBC driver is already required by the application.
*/
public final class ResetTestDatabase {
public static void main(String[] args) throws Exception {
if (args.length < 3) {
System.err.println("usage: ResetTestDatabase ");
System.exit(2);
}
String url = args[0];
String user = args[1];
String password = args[2];
String database = databaseName(url);
if (database.isEmpty()) {
System.err.println("The JDBC URL must name the database to reset: " + url);
System.exit(2);
}
try (Connection connection = DriverManager.getConnection(url, user, password)) {
List views = objects(connection, database, "VIEW");
List tables = objects(connection, database, "BASE TABLE");
dropAll(connection, views, tables);
System.out.println("reset " + database + ": dropped " + views.size() + " views and " + tables.size() + " tables");
if (!tables.isEmpty()) {
System.out.println(" dropped: " + summary(tables));
}
System.out.println("The next test run recreates the schema from db/migration.");
return;
} catch (SQLException exception) {
if (!unknownDatabase(exception)) throw exception;
}
try (Connection connection = DriverManager.getConnection(serverUrl(url), user, password);
Statement statement = connection.createStatement()) {
statement.executeUpdate("create database if not exists `" + database + "` default charset utf8mb4");
}
System.out.println("created empty database " + database + " (it did not exist yet)");
}
private static List objects(Connection connection, String database, String type) throws SQLException {
List names = new ArrayList<>();
try (var statement = connection.prepareStatement("""
select table_name from information_schema.tables
where table_schema = ? and table_type = ?
order by table_name
""")) {
statement.setString(1, database);
statement.setString(2, type);
try (ResultSet result = statement.executeQuery()) {
while (result.next()) names.add(result.getString(1));
}
}
return names;
}
private static void dropAll(Connection connection, List views, List tables) throws SQLException {
try (Statement statement = connection.createStatement()) {
statement.execute("set foreign_key_checks = 0");
try {
for (String view : views) statement.executeUpdate("drop view if exists `" + view + "`");
for (String table : tables) statement.executeUpdate("drop table if exists `" + table + "`");
} finally {
statement.execute("set foreign_key_checks = 1");
}
}
}
/** The database name of a JDBC URL such as jdbc:mysql://host:port/db?params. */
static String databaseName(String url) {
int pathStart = pathStart(url);
if (pathStart < 0) return "";
String tail = url.substring(pathStart + 1);
int query = tail.indexOf('?');
return (query < 0 ? tail : tail.substring(0, query)).trim();
}
/** The same URL without its database, used to create a database that does not exist yet. */
static String serverUrl(String url) {
int pathStart = pathStart(url);
if (pathStart < 0) return url;
int query = url.indexOf('?', pathStart);
return url.substring(0, pathStart + 1) + (query < 0 ? "" : url.substring(query));
}
/**
* Index of the slash that separates the host from the database path. Splitting on the last slash
* would break on URLs that carry a path-like parameter, for example serverTimezone=Asia/Shanghai.
*/
private static int pathStart(String url) {
int schemeEnd = url.indexOf("//");
return schemeEnd < 0 ? -1 : url.indexOf('/', schemeEnd + 2);
}
private static boolean unknownDatabase(SQLException exception) {
for (SQLException current = exception; current != null; current = current.getNextException()) {
if (current.getErrorCode() == 1049) return true;
String message = current.getMessage();
if (message != null && message.toLowerCase().contains("unknown database")) return true;
}
return false;
}
private static String summary(List names) {
if (names.size() <= 12) return String.join(", ", names);
return String.join(", ", names.subList(0, 12)) + ", ... (" + (names.size() - 12) + " more)";
}
}