Apply more than one migration if suitable.

This commit is contained in:
akwizgran
2018-02-01 10:14:34 +00:00
parent 9889f86f69
commit 3dbc3cef56
4 changed files with 83 additions and 35 deletions

View File

@@ -311,7 +311,7 @@ abstract class JdbcDatabase implements Database<Connection> {
if (!checkSchemaVersion(txn)) throw new DbException(); if (!checkSchemaVersion(txn)) throw new DbException();
} else { } else {
createTables(txn); createTables(txn);
storeSchemaVersion(txn); storeSchemaVersion(txn, CODE_SCHEMA_VERSION);
} }
createIndexes(txn); createIndexes(txn);
commitTransaction(txn); commitTransaction(txn);
@@ -327,31 +327,31 @@ abstract class JdbcDatabase implements Database<Connection> {
if (dataSchemaVersion == -1) return false; if (dataSchemaVersion == -1) return false;
if (dataSchemaVersion == CODE_SCHEMA_VERSION) return true; if (dataSchemaVersion == CODE_SCHEMA_VERSION) return true;
if (CODE_SCHEMA_VERSION < dataSchemaVersion) return false; if (CODE_SCHEMA_VERSION < dataSchemaVersion) return false;
// Do we have a suitable migration? // Apply any suitable migrations in order
for (Migration<Connection> m : getMigrations()) { for (Migration<Connection> m : getMigrations()) {
int start = m.getStartVersion(), end = m.getEndVersion(); int start = m.getStartVersion(), end = m.getEndVersion();
if (start == dataSchemaVersion && end == CODE_SCHEMA_VERSION) { if (start == dataSchemaVersion) {
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Migrating from schema " + start + " to " + end); LOG.info("Migrating from schema " + start + " to " + end);
// Apply the migration // Apply the migration
m.migrate(txn); m.migrate(txn);
// Store the new schema version // Store the new schema version
storeSchemaVersion(txn); storeSchemaVersion(txn, end);
return true; dataSchemaVersion = end;
} }
} }
// No suitable migration return dataSchemaVersion == CODE_SCHEMA_VERSION;
return false;
} }
// Package access for testing // Package access for testing
Collection<Migration<Connection>> getMigrations() { List<Migration<Connection>> getMigrations() {
return Collections.emptyList(); return Collections.emptyList();
} }
private void storeSchemaVersion(Connection txn) throws DbException { private void storeSchemaVersion(Connection txn, int version)
throws DbException {
Settings s = new Settings(); Settings s = new Settings();
s.putInt(SCHEMA_VERSION_KEY, CODE_SCHEMA_VERSION); s.putInt(SCHEMA_VERSION_KEY, version);
mergeSettings(txn, s, DB_SETTINGS_NAMESPACE); mergeSettings(txn, s, DB_SETTINGS_NAMESPACE);
} }

View File

@@ -16,8 +16,9 @@ import org.junit.Test;
import java.io.File; import java.io.File;
import java.sql.Connection; import java.sql.Connection;
import java.util.Collection; import java.util.List;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList; import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList; import static java.util.Collections.singletonList;
import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE; import static org.briarproject.bramble.db.DatabaseConstants.DB_SETTINGS_NAMESPACE;
@@ -33,14 +34,17 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
private final File testDir = TestUtils.getTestDirectory(); private final File testDir = TestUtils.getTestDirectory();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private final Migration<Connection> migration = private final Migration<Connection> migration =
context.mock(Migration.class); context.mock(Migration.class, "migration");
@SuppressWarnings("unchecked")
private final Migration<Connection> migration1 =
context.mock(Migration.class, "migration1");
protected final DatabaseConfig config = protected final DatabaseConfig config =
new TestDatabaseConfig(testDir, 1024 * 1024); new TestDatabaseConfig(testDir, 1024 * 1024);
protected final Clock clock = new SystemClock(); protected final Clock clock = new SystemClock();
abstract Database<Connection> createDatabase( abstract Database<Connection> createDatabase(
Collection<Migration<Connection>> migrations) throws Exception; List<Migration<Connection>> migrations) throws Exception;
@Before @Before
public void setUp() { public void setUp() {
@@ -65,14 +69,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
public void testThrowsExceptionIfDataSchemaVersionIsMissing() public void testThrowsExceptionIfDataSchemaVersionIsMissing()
throws Exception { throws Exception {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(singletonList(migration)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open()); assertFalse(db.open());
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, -1); setDataSchemaVersion(db, -1);
db.close(); db.close();
// Reopen the DB - an exception should be thrown // Reopen the DB - an exception should be thrown
db = createDatabase(singletonList(migration)); db = createDatabase(asList(migration, migration1));
db.open(); db.open();
} }
@@ -80,12 +84,12 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
public void testDoesNotRunMigrationsIfSchemaVersionsMatch() public void testDoesNotRunMigrationsIfSchemaVersionsMatch()
throws Exception { throws Exception {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(singletonList(migration)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open()); assertFalse(db.open());
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close(); db.close();
// Reopen the DB - migrations should not be run // Reopen the DB - migrations should not be run
db = createDatabase(singletonList(migration)); db = createDatabase(asList(migration, migration1));
assertTrue(db.open()); assertTrue(db.open());
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close(); db.close();
@@ -94,14 +98,14 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
@Test(expected = DbException.class) @Test(expected = DbException.class)
public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception { public void testThrowsExceptionIfDataIsNewerThanCode() throws Exception {
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(singletonList(migration)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open()); assertFalse(db.open());
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION + 1); setDataSchemaVersion(db, CODE_SCHEMA_VERSION + 1);
db.close(); db.close();
// Reopen the DB - an exception should be thrown // Reopen the DB - an exception should be thrown
db = createDatabase(singletonList(migration)); db = createDatabase(asList(migration, migration1));
db.open(); db.open();
} }
@@ -126,18 +130,22 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
oneOf(migration).getStartVersion(); oneOf(migration).getStartVersion();
will(returnValue(CODE_SCHEMA_VERSION - 2)); will(returnValue(CODE_SCHEMA_VERSION - 2));
oneOf(migration).getEndVersion(); oneOf(migration).getEndVersion();
will(returnValue(CODE_SCHEMA_VERSION - 1));
oneOf(migration1).getStartVersion();
will(returnValue(CODE_SCHEMA_VERSION - 1));
oneOf(migration1).getEndVersion();
will(returnValue(CODE_SCHEMA_VERSION)); will(returnValue(CODE_SCHEMA_VERSION));
}}); }});
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(singletonList(migration)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open()); assertFalse(db.open());
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db)); assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 1); setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 3);
db.close(); db.close();
// Reopen the DB - an exception should be thrown // Reopen the DB - an exception should be thrown
db = createDatabase(singletonList(migration)); db = createDatabase(asList(migration, migration1));
db.open(); db.open();
} }
@@ -145,22 +153,62 @@ public abstract class DatabaseMigrationTest extends BrambleMockTestCase {
public void testRunsMigrationIfCodeIsNewerThanDataAndSuitableMigration() public void testRunsMigrationIfCodeIsNewerThanDataAndSuitableMigration()
throws Exception { throws Exception {
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// First migration should be run, increasing schema version by 2
oneOf(migration).getStartVersion(); oneOf(migration).getStartVersion();
will(returnValue(CODE_SCHEMA_VERSION - 1)); will(returnValue(CODE_SCHEMA_VERSION - 2));
oneOf(migration).getEndVersion(); oneOf(migration).getEndVersion();
will(returnValue(CODE_SCHEMA_VERSION)); will(returnValue(CODE_SCHEMA_VERSION));
oneOf(migration).migrate(with(any(Connection.class))); oneOf(migration).migrate(with(any(Connection.class)));
// Second migration is not suitable and should be skipped
oneOf(migration1).getStartVersion();
will(returnValue(CODE_SCHEMA_VERSION - 1));
oneOf(migration1).getEndVersion();
will(returnValue(CODE_SCHEMA_VERSION));
}}); }});
// Open the DB for the first time // Open the DB for the first time
Database<Connection> db = createDatabase(singletonList(migration)); Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open()); assertFalse(db.open());
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version // Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 1); setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
db.close(); db.close();
// Reopen the DB - the migration should be run // Reopen the DB - the first migration should be run
db = createDatabase(singletonList(migration)); db = createDatabase(asList(migration, migration1));
assertTrue(db.open()); assertTrue(db.open());
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close();
}
@Test
public void testRunsMigrationsIfCodeIsNewerThanDataAndSuitableMigrations()
throws Exception {
context.checking(new Expectations() {{
// First migration should be run, incrementing schema version
oneOf(migration).getStartVersion();
will(returnValue(CODE_SCHEMA_VERSION - 2));
oneOf(migration).getEndVersion();
will(returnValue(CODE_SCHEMA_VERSION - 1));
oneOf(migration).migrate(with(any(Connection.class)));
// Second migration should be run, incrementing schema version again
oneOf(migration1).getStartVersion();
will(returnValue(CODE_SCHEMA_VERSION - 1));
oneOf(migration1).getEndVersion();
will(returnValue(CODE_SCHEMA_VERSION));
oneOf(migration1).migrate(with(any(Connection.class)));
}});
// Open the DB for the first time
Database<Connection> db = createDatabase(asList(migration, migration1));
assertFalse(db.open());
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
// Override the data schema version
setDataSchemaVersion(db, CODE_SCHEMA_VERSION - 2);
db.close();
// Reopen the DB - both migrations should be run
db = createDatabase(asList(migration, migration1));
assertTrue(db.open());
assertEquals(CODE_SCHEMA_VERSION, getDataSchemaVersion(db));
db.close(); db.close();
} }

View File

@@ -3,17 +3,17 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.sql.Connection; import java.sql.Connection;
import java.util.Collection; import java.util.List;
@NotNullByDefault @NotNullByDefault
public class H2MigrationTest extends DatabaseMigrationTest { public class H2MigrationTest extends DatabaseMigrationTest {
@Override @Override
Database<Connection> createDatabase( Database<Connection> createDatabase(List<Migration<Connection>> migrations)
Collection<Migration<Connection>> migrations) throws Exception { throws Exception {
return new H2Database(config, clock) { return new H2Database(config, clock) {
@Override @Override
Collection<Migration<Connection>> getMigrations() { List<Migration<Connection>> getMigrations() {
return migrations; return migrations;
} }
}; };

View File

@@ -3,17 +3,17 @@ package org.briarproject.bramble.db;
import org.briarproject.bramble.api.nullsafety.NotNullByDefault; import org.briarproject.bramble.api.nullsafety.NotNullByDefault;
import java.sql.Connection; import java.sql.Connection;
import java.util.Collection; import java.util.List;
@NotNullByDefault @NotNullByDefault
public class HyperSqlMigrationTest extends DatabaseMigrationTest { public class HyperSqlMigrationTest extends DatabaseMigrationTest {
@Override @Override
Database<Connection> createDatabase( Database<Connection> createDatabase(List<Migration<Connection>> migrations)
Collection<Migration<Connection>> migrations) throws Exception { throws Exception {
return new HyperSqlDatabase(config, clock) { return new HyperSqlDatabase(config, clock) {
@Override @Override
Collection<Migration<Connection>> getMigrations() { List<Migration<Connection>> getMigrations() {
return migrations; return migrations;
} }
}; };