Добавить версионирование схемы БД и заблокировать прод-очистку БД
This commit is contained in:
parent
e764a713c4
commit
c8fa4a01a1
@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.2
|
client.version=1.2.3
|
||||||
server.version=1.2.2
|
server.version=1.2.3
|
||||||
|
|||||||
29
build.gradle
29
build.gradle
@ -191,25 +191,20 @@ tasks.register('deployServer', JavaExec) {
|
|||||||
dependsOn testClasses
|
dependsOn testClasses
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('deployServerWithBackupCleanAndTests', JavaExec) {
|
tasks.register('deployServerWithBackupCleanAndTests') {
|
||||||
group = "!!deployment"
|
group = "!!deployment"
|
||||||
description = "DANGER: deploy + backup data + clean data + restart + run IT tests (с обязательным подтверждением)"
|
description = "BLOCKED: удаление БД на проде запрещено, используйте только миграции"
|
||||||
|
|
||||||
classpath = sourceSets.test.runtimeClasspath
|
doLast {
|
||||||
mainClass = "test.it.IT_DeployBackupCleanAndRunRemoteMain"
|
def msg = """
|
||||||
|
[BLOCKED] Удаление базы данных на продакшен-сервере отключено.
|
||||||
dependsOn shadowJar
|
Причина: в базе уже есть пользовательские сообщения.
|
||||||
systemProperty "it.remoteHost", System.getProperty("it.remoteHost", "194.87.0.247")
|
Дальше используйте только миграции схемы БД.
|
||||||
systemProperty "it.remoteUser", System.getProperty("it.remoteUser", "user")
|
Задача остановлена намеренно.
|
||||||
systemProperty "it.remoteDir", System.getProperty("it.remoteDir", "/home/user/docker/shine-server")
|
""".stripIndent().trim()
|
||||||
systemProperty "it.remoteDataDir", System.getProperty("it.remoteDataDir", "/home/user/docker/shine-server/data")
|
println msg
|
||||||
systemProperty "it.remoteBackupDir", System.getProperty("it.remoteBackupDir", "/home/user/docker/shine-server/backup")
|
throw new GradleException(msg)
|
||||||
systemProperty "it.service", System.getProperty("it.service", "shine-server")
|
}
|
||||||
systemProperty "it.localJar", System.getProperty("it.localJar", "build/libs/shine-server.jar")
|
|
||||||
systemProperty "it.wsUri", System.getProperty("it.wsUri", "wss://shineup.me/ws")
|
|
||||||
|
|
||||||
standardInput = System.in
|
|
||||||
dependsOn testClasses
|
|
||||||
}
|
}
|
||||||
|
|
||||||
tasks.register('deployServerNoCleanNoTests', JavaExec) {
|
tasks.register('deployServerNoCleanNoTests', JavaExec) {
|
||||||
|
|||||||
@ -24,6 +24,9 @@ import java.sql.Statement;
|
|||||||
*/
|
*/
|
||||||
public final class DatabaseInitializer {
|
public final class DatabaseInitializer {
|
||||||
|
|
||||||
|
public static final String DB_SCHEMA_VERSION_TABLE = "db_schema_version";
|
||||||
|
public static final int SCHEMA_VERSION_1 = 1;
|
||||||
|
|
||||||
private DatabaseInitializer() {}
|
private DatabaseInitializer() {}
|
||||||
|
|
||||||
/* ===================== TEXT (msg_type=1) ===================== */
|
/* ===================== TEXT (msg_type=1) ===================== */
|
||||||
@ -101,7 +104,15 @@ public final class DatabaseInitializer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void ensureSchemaV1Structure(String jdbcUrl) throws SQLException {
|
||||||
|
createSchema(jdbcUrl, false);
|
||||||
|
}
|
||||||
|
|
||||||
private static void createSchema(String jdbcUrl) throws SQLException {
|
private static void createSchema(String jdbcUrl) throws SQLException {
|
||||||
|
createSchema(jdbcUrl, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void createSchema(String jdbcUrl, boolean initializeVersionRow) throws SQLException {
|
||||||
try {
|
try {
|
||||||
Class.forName("org.sqlite.JDBC");
|
Class.forName("org.sqlite.JDBC");
|
||||||
} catch (ClassNotFoundException e) {
|
} catch (ClassNotFoundException e) {
|
||||||
@ -586,6 +597,24 @@ public final class DatabaseInitializer {
|
|||||||
ON signed_message_session_delivery (session_id, delivered);
|
ON signed_message_session_delivery (session_id, delivered);
|
||||||
""");
|
""");
|
||||||
|
|
||||||
|
st.executeUpdate("""
|
||||||
|
CREATE TABLE IF NOT EXISTS db_schema_version (
|
||||||
|
id INTEGER NOT NULL PRIMARY KEY CHECK (id = 1),
|
||||||
|
schema_version INTEGER NOT NULL,
|
||||||
|
updated_at_ms INTEGER NOT NULL
|
||||||
|
);
|
||||||
|
""");
|
||||||
|
|
||||||
|
if (initializeVersionRow) {
|
||||||
|
st.executeUpdate("""
|
||||||
|
INSERT INTO db_schema_version (id, schema_version, updated_at_ms)
|
||||||
|
VALUES (1, 1, CAST(strftime('%s','now') AS INTEGER) * 1000)
|
||||||
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
|
schema_version = excluded.schema_version,
|
||||||
|
updated_at_ms = excluded.updated_at_ms;
|
||||||
|
""");
|
||||||
|
}
|
||||||
|
|
||||||
DatabaseTriggersInstaller.createAllTriggers(st);
|
DatabaseTriggersInstaller.createAllTriggers(st);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -14,6 +14,7 @@ import java.sql.Statement;
|
|||||||
public final class SqliteDbController {
|
public final class SqliteDbController {
|
||||||
|
|
||||||
private static volatile SqliteDbController instance;
|
private static volatile SqliteDbController instance;
|
||||||
|
private static final int LATEST_SCHEMA_VERSION = DatabaseInitializer.SCHEMA_VERSION_1;
|
||||||
|
|
||||||
private final String jdbcUrl;
|
private final String jdbcUrl;
|
||||||
|
|
||||||
@ -38,7 +39,7 @@ public final class SqliteDbController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
this.jdbcUrl = "jdbc:sqlite:" + dbPath;
|
this.jdbcUrl = "jdbc:sqlite:" + dbPath;
|
||||||
ensureSchemaUpgrades();
|
ensureSchemaMigrations();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static SqliteDbController getInstance() {
|
public static SqliteDbController getInstance() {
|
||||||
@ -70,42 +71,91 @@ public final class SqliteDbController {
|
|||||||
// no-op
|
// no-op
|
||||||
}
|
}
|
||||||
|
|
||||||
private void ensureSchemaUpgrades() {
|
private void ensureSchemaMigrations() {
|
||||||
|
int currentVersion = getCurrentSchemaVersion();
|
||||||
|
|
||||||
|
while (currentVersion < LATEST_SCHEMA_VERSION) {
|
||||||
|
int nextVersion = currentVersion + 1;
|
||||||
|
applyMigration(nextVersion);
|
||||||
|
currentVersion = nextVersion;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyMigration(int targetVersion) {
|
||||||
|
switch (targetVersion) {
|
||||||
|
case 1 -> migrateToV1();
|
||||||
|
default -> throw new RuntimeException("Unknown DB migration target version: " + targetVersion);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void migrateToV1() {
|
||||||
|
try {
|
||||||
|
DatabaseInitializer.ensureSchemaV1Structure(jdbcUrl);
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException("DB migration to v1 failed (base schema)", e);
|
||||||
|
}
|
||||||
|
|
||||||
try (Connection c = DriverManager.getConnection(jdbcUrl);
|
try (Connection c = DriverManager.getConnection(jdbcUrl);
|
||||||
Statement st = c.createStatement()) {
|
Statement st = c.createStatement()) {
|
||||||
|
|
||||||
c.setAutoCommit(false);
|
c.setAutoCommit(false);
|
||||||
try {
|
try {
|
||||||
st.execute("PRAGMA foreign_keys = OFF");
|
st.execute("PRAGMA foreign_keys = OFF");
|
||||||
|
|
||||||
ensureReactionsStateTable(st);
|
if (tableExists(c, "connections_state") && needsConnectionsStateUpgrade(c)) {
|
||||||
ensureMessageViewsStateTable(st);
|
|
||||||
|
|
||||||
if (!tableExists(c, "connections_state")) {
|
|
||||||
createConnectionsStateTable(st);
|
|
||||||
} else if (needsConnectionsStateUpgrade(c)) {
|
|
||||||
rebuildConnectionsStateTable(st);
|
rebuildConnectionsStateTable(st);
|
||||||
}
|
}
|
||||||
ensureChannelNamesStateTable(st);
|
|
||||||
ensureChannelNamesDescriptionColumn(c, st);
|
|
||||||
ensureConnectionsIndexes(st);
|
|
||||||
ensureReactionsIndexes(st);
|
|
||||||
ensureMessageViewsIndexes(st);
|
|
||||||
ensureChannelNamesIndexes(st);
|
|
||||||
ensureSignedMessageReceiptUniq(c, st);
|
|
||||||
|
|
||||||
|
ensureChannelNamesDescriptionColumn(c, st);
|
||||||
|
ensureSignedMessageReceiptUniq(c, st);
|
||||||
DatabaseTriggersInstaller.createAllTriggers(st);
|
DatabaseTriggersInstaller.createAllTriggers(st);
|
||||||
|
setSchemaVersion(c, 1);
|
||||||
|
|
||||||
st.execute("PRAGMA foreign_keys = ON");
|
st.execute("PRAGMA foreign_keys = ON");
|
||||||
c.commit();
|
c.commit();
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
try { c.rollback(); } catch (Exception ignored) {}
|
try { c.rollback(); } catch (Exception ignored) {}
|
||||||
throw new RuntimeException("DB schema upgrade failed", e);
|
throw new RuntimeException("DB migration to v1 failed", e);
|
||||||
} finally {
|
} finally {
|
||||||
try { c.setAutoCommit(true); } catch (Exception ignored) {}
|
try { c.setAutoCommit(true); } catch (Exception ignored) {}
|
||||||
}
|
}
|
||||||
} catch (SQLException e) {
|
} catch (SQLException e) {
|
||||||
throw new RuntimeException("DB schema upgrade failed", e);
|
throw new RuntimeException("DB migration to v1 failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getCurrentSchemaVersion() {
|
||||||
|
try (Connection c = DriverManager.getConnection(jdbcUrl)) {
|
||||||
|
if (!tableExists(c, DatabaseInitializer.DB_SCHEMA_VERSION_TABLE)) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (var ps = c.prepareStatement("""
|
||||||
|
SELECT schema_version
|
||||||
|
FROM db_schema_version
|
||||||
|
WHERE id = 1
|
||||||
|
LIMIT 1
|
||||||
|
""");
|
||||||
|
ResultSet rs = ps.executeQuery()) {
|
||||||
|
if (rs.next()) {
|
||||||
|
return rs.getInt(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0;
|
||||||
|
} catch (SQLException e) {
|
||||||
|
throw new RuntimeException("Cannot read DB schema version", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void setSchemaVersion(Connection c, int version) throws SQLException {
|
||||||
|
try (var ps = c.prepareStatement("""
|
||||||
|
INSERT INTO db_schema_version (id, schema_version, updated_at_ms)
|
||||||
|
VALUES (1, ?, CAST(strftime('%s','now') AS INTEGER) * 1000)
|
||||||
|
ON CONFLICT(id) DO UPDATE SET
|
||||||
|
schema_version = excluded.schema_version,
|
||||||
|
updated_at_ms = excluded.updated_at_ms
|
||||||
|
""")) {
|
||||||
|
ps.setInt(1, version);
|
||||||
|
ps.executeUpdate();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user