forked from Archives/Athou_commafeed
add H2 migration tool
This commit is contained in:
@@ -405,9 +405,13 @@
|
||||
<dependency>
|
||||
<groupId>com.h2database</groupId>
|
||||
<artifactId>h2</artifactId>
|
||||
<!-- stay on 2.1 because 2.2 file format changed to version '3' -->
|
||||
<version>2.1.214</version><!--$NO-MVN-MAN-VER$ -->
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.manticore-projects.tools</groupId>
|
||||
<artifactId>h2migrationtool</artifactId>
|
||||
<version>1.4</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.mysql</groupId>
|
||||
<artifactId>mysql-connector-j</artifactId>
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package com.commafeed;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.time.Instant;
|
||||
import java.util.EnumSet;
|
||||
import java.util.Set;
|
||||
@@ -24,8 +26,9 @@ import com.commafeed.backend.model.FeedSubscription;
|
||||
import com.commafeed.backend.model.User;
|
||||
import com.commafeed.backend.model.UserRole;
|
||||
import com.commafeed.backend.model.UserSettings;
|
||||
import com.commafeed.backend.service.DatabaseStartupService;
|
||||
import com.commafeed.backend.service.UserService;
|
||||
import com.commafeed.backend.service.db.DatabaseStartupService;
|
||||
import com.commafeed.backend.service.db.H2MigrationService;
|
||||
import com.commafeed.backend.task.ScheduledTask;
|
||||
import com.commafeed.frontend.auth.PasswordConstraintValidator;
|
||||
import com.commafeed.frontend.auth.SecurityCheckFactoryProvider;
|
||||
@@ -58,6 +61,7 @@ import io.dropwizard.configuration.DefaultConfigurationFactoryFactory;
|
||||
import io.dropwizard.configuration.EnvironmentVariableSubstitutor;
|
||||
import io.dropwizard.configuration.SubstitutingSourceProvider;
|
||||
import io.dropwizard.core.Application;
|
||||
import io.dropwizard.core.ConfiguredBundle;
|
||||
import io.dropwizard.core.setup.Bootstrap;
|
||||
import io.dropwizard.core.setup.Environment;
|
||||
import io.dropwizard.db.DataSourceFactory;
|
||||
@@ -93,6 +97,30 @@ public class CommaFeedApplication extends Application<CommaFeedConfiguration> {
|
||||
configureEnvironmentSubstitutor(bootstrap);
|
||||
configureObjectMapper(bootstrap.getObjectMapper());
|
||||
|
||||
// run h2 migration as the first bundle because we need to migrate before hibernate is initialized
|
||||
bootstrap.addBundle(new ConfiguredBundle<CommaFeedConfiguration>() {
|
||||
@Override
|
||||
public void run(CommaFeedConfiguration config, Environment environment) throws Exception {
|
||||
DataSourceFactory dataSourceFactory = config.getDataSourceFactory();
|
||||
String url = dataSourceFactory.getUrl();
|
||||
if (isFileBasedH2(url)) {
|
||||
Path path = getFilePath(url);
|
||||
String user = dataSourceFactory.getUser();
|
||||
String password = dataSourceFactory.getPassword();
|
||||
new H2MigrationService().migrateIfNeeded(path, user, password);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isFileBasedH2(String url) {
|
||||
return url.startsWith("jdbc:h2:") && !url.startsWith("jdbc:h2:mem:");
|
||||
}
|
||||
|
||||
private Path getFilePath(String url) {
|
||||
String name = url.substring("jdbc:h2:".length()).split(";")[0];
|
||||
return Paths.get(name + ".mv.db");
|
||||
}
|
||||
});
|
||||
|
||||
bootstrap.addBundle(hibernateBundle = new HibernateBundle<>(AbstractModel.class, Feed.class, FeedCategory.class, FeedEntry.class,
|
||||
FeedEntryContent.class, FeedEntryStatus.class, FeedEntryTag.class, FeedSubscription.class, User.class, UserRole.class,
|
||||
UserSettings.class) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.commafeed.backend.service;
|
||||
package com.commafeed.backend.service.db;
|
||||
|
||||
import java.time.Instant;
|
||||
import java.util.List;
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.commafeed.backend.service;
|
||||
package com.commafeed.backend.service.db;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
@@ -9,6 +9,7 @@ import org.hibernate.SessionFactory;
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.dao.UnitOfWork;
|
||||
import com.commafeed.backend.dao.UserDAO;
|
||||
import com.commafeed.backend.service.UserService;
|
||||
|
||||
import io.dropwizard.lifecycle.Managed;
|
||||
import jakarta.inject.Inject;
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.commafeed.backend.service.db;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import com.manticore.h2.H2MigrationTool;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
@Slf4j
|
||||
public class H2MigrationService {
|
||||
|
||||
public void migrateIfNeeded(Path path, String user, String password) {
|
||||
int format;
|
||||
try {
|
||||
format = getH2FileFormat(path);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException("could not detect H2 format", e);
|
||||
}
|
||||
|
||||
if (format == 2) {
|
||||
try {
|
||||
migrate(path, user, password, "2.1.214", "2.2.224");
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("could not migrate H2 to format 3", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getH2FileFormat(Path path) throws IOException {
|
||||
try (BufferedReader reader = Files.newBufferedReader(path)) {
|
||||
String headers = reader.readLine();
|
||||
|
||||
return Stream.of(headers.split(","))
|
||||
.filter(h -> h.startsWith("format:"))
|
||||
.map(h -> h.split(":")[1])
|
||||
.map(Integer::parseInt)
|
||||
.findFirst()
|
||||
.orElseThrow(() -> new RuntimeException("could not find format in H2 file headers"));
|
||||
}
|
||||
}
|
||||
|
||||
private void migrate(Path path, String user, String password, String fromVersion, String toVersion) throws Exception {
|
||||
log.info("migrating H2 database at {} from format {} to format {}", path, fromVersion, toVersion);
|
||||
|
||||
Path scriptPath = path.resolveSibling("script-" + System.currentTimeMillis() + ".sql");
|
||||
Path newVersionPath = path.resolveSibling(path.getFileName() + "." + getPatchVersion(toVersion) + ".mv.db");
|
||||
Path oldVersionBackupPath = path.resolveSibling(path.getFileName() + "." + getPatchVersion(fromVersion) + ".backup");
|
||||
|
||||
H2MigrationTool.readDriverRecords();
|
||||
new H2MigrationTool().migrate(fromVersion, toVersion, path.toAbsolutePath().toString(), user, password,
|
||||
scriptPath.toAbsolutePath().toString(), "", "", false, false, "");
|
||||
if (!Files.exists(newVersionPath)) {
|
||||
throw new RuntimeException("H2 migration failed, new version file not found");
|
||||
}
|
||||
|
||||
Files.move(path, oldVersionBackupPath);
|
||||
Files.move(newVersionPath, path);
|
||||
Files.delete(oldVersionBackupPath);
|
||||
Files.delete(scriptPath);
|
||||
|
||||
log.info("migrated H2 database from format {} to format {}", fromVersion, toVersion);
|
||||
}
|
||||
|
||||
private String getPatchVersion(String version) {
|
||||
return StringUtils.substringAfterLast(version, ".");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -3,7 +3,7 @@ package com.commafeed.backend.task;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.service.DatabaseCleaningService;
|
||||
import com.commafeed.backend.service.db.DatabaseCleaningService;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
@@ -5,7 +5,7 @@ import java.time.Instant;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.service.DatabaseCleaningService;
|
||||
import com.commafeed.backend.service.db.DatabaseCleaningService;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
@@ -4,7 +4,7 @@ import java.time.Instant;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.commafeed.CommaFeedConfiguration;
|
||||
import com.commafeed.backend.service.DatabaseCleaningService;
|
||||
import com.commafeed.backend.service.db.DatabaseCleaningService;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.commafeed.backend.task;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.commafeed.backend.service.DatabaseCleaningService;
|
||||
import com.commafeed.backend.service.db.DatabaseCleaningService;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
@@ -2,7 +2,7 @@ package com.commafeed.backend.task;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.commafeed.backend.service.DatabaseCleaningService;
|
||||
import com.commafeed.backend.service.db.DatabaseCleaningService;
|
||||
|
||||
import jakarta.inject.Inject;
|
||||
import jakarta.inject.Singleton;
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
package com.commafeed.backend.service.db;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.Objects;
|
||||
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
|
||||
class H2MigrationServiceTest {
|
||||
|
||||
@TempDir
|
||||
private Path root;
|
||||
|
||||
@Test
|
||||
void testMigrateIfNeeded() throws IOException {
|
||||
Path path = root.resolve("database.mv.db");
|
||||
Files.copy(Objects.requireNonNull(getClass().getResourceAsStream("/h2-migration/database-v2.1.214.mv.db")), path);
|
||||
|
||||
H2MigrationService service = new H2MigrationService();
|
||||
Assertions.assertEquals(2, service.getH2FileFormat(path));
|
||||
|
||||
service.migrateIfNeeded(path, "sa", "sa");
|
||||
Assertions.assertEquals(3, service.getH2FileFormat(path));
|
||||
}
|
||||
|
||||
}
|
||||
Binary file not shown.
Reference in New Issue
Block a user