migrations work

This commit is contained in:
Maxime Duchene-Savard 2025-09-20 23:35:30 -04:00
parent 2fa76d84df
commit 9ab8902dd6
10 changed files with 189 additions and 34 deletions

17
.idea/dataSources.xml generated Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourceManagerImpl" format="xml" multifile-model="true">
<data-source source="LOCAL" name="bolts@localhost" uuid="0c3816cc-be27-438f-91ac-28ac37a4af01">
<driver-ref>mariadb</driver-ref>
<synchronize>true</synchronize>
<jdbc-driver>org.mariadb.jdbc.Driver</jdbc-driver>
<jdbc-url>jdbc:mariadb://localhost:3306/bolts</jdbc-url>
<jdbc-additional-properties>
<property name="com.intellij.clouds.kubernetes.db.host.port" />
<property name="com.intellij.clouds.kubernetes.db.enabled" value="false" />
<property name="com.intellij.clouds.kubernetes.db.container.port" />
</jdbc-additional-properties>
<working-dir>$ProjectFileDir$</working-dir>
</data-source>
</component>
</project>

6
.idea/data_source_mapping.xml generated Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="DataSourcePerFileMappings">
<file url="file://$APPLICATION_CONFIG_DIR$/consoles/db/0c3816cc-be27-438f-91ac-28ac37a4af01/console.sql" value="0c3816cc-be27-438f-91ac-28ac37a4af01" />
</component>
</project>

4
.idea/kotlinc.xml generated
View File

@ -7,8 +7,8 @@
<option name="jvmTarget" value="1.8" />
</component>
<component name="KotlinCommonCompilerArguments">
<option name="apiVersion" value="2.1" />
<option name="languageVersion" value="2.1" />
<option name="apiVersion" value="2.2" />
<option name="languageVersion" value="2.2" />
</component>
<component name="KotlinJpsPluginSettings">
<option name="version" value="2.2.0" />

15
pom.xml
View File

@ -134,15 +134,18 @@
<version>2.0.16</version>
</dependency>
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-scripting-jvm</artifactId>
<version>${kotlin.version}</version>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.20.0</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-scripting-common</artifactId>
<version>${kotlin.version}</version>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.18.0</version>
</dependency>
<!-- TEST -->

View File

@ -4,16 +4,25 @@ import io.javalin.Javalin;
import org.apache.commons.dbutils.DbUtils;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.ScalarHandler;
import org.apache.commons.io.FileUtils;
import org.jooq.impl.DSL;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.sql.Connection;
import java.sql.SQLException;
import java.time.LocalDateTime;
import java.util.List;
/** Hello world! */
public class App {
public static void main(String[] args) {
System.setProperty("org.jooq.no-tips", "true");
System.setProperty("org.jooq.no-logo", "true");
Db db =
Db.Builder.create()
.url("jdbc:mariadb://localhost:3306/bolts")
@ -22,7 +31,22 @@ public class App {
.build();
db.init();
Migration.of(db).getPendingBaseMigrations().forEach(f -> System.out.println(f.getName()));
List<File> pendingBaseMigrations = Migration.of(db).getPendingBaseMigrations();
for (File file : pendingBaseMigrations) {
LoggerFactory.getLogger(App.class).info("Running migration: {}", file.getName());
try (Connection connection = db.getConnection()) {
String content = FileUtils.readFileToString(file, StandardCharsets.UTF_8);
db.run(connection, content);
DSL.using(connection)
.insertInto(DSL.table("_migrations"))
.columns(DSL.field("name"), DSL.field("base"), DSL.field("executedAt"))
.values(
file.getName(), true, LocalDateTime.now()) // executedAt is null for base migrations
.execute();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
var app = Javalin.create(cnf -> {});
app.before(

View File

@ -84,13 +84,18 @@ public class Db {
public void run(String query) {
try (Connection connection = getConnection()) {
dsl.execute(query);
connection.commit();
run(connection, query);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public DSLContext dsl() {
return dsl;
public void run(Connection connection, String query) {
try {
DSL.using(connection).execute(query);
connection.commit();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
}

View File

@ -1,20 +1,14 @@
package dev.mduchene;
import org.jooq.Meta;
import org.jooq.impl.DSL;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.Connection;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Arrays;
import java.sql.ResultSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import org.jooq.Meta;
import org.jooq.Record1;
import org.jooq.SelectConditionStep;
import org.jooq.impl.DSL;
public class Migration {
@ -38,14 +32,19 @@ public class Migration {
public boolean tableExists(String tableName) {
// Get the Meta object, which contains all database metadata
Meta meta = db.dsl().meta();
try {
Connection connection = db.getConnection();
Meta meta = DSL.using(connection).meta();
// Use the getTables() method with a filter
return meta.getTables(DSL.name("bolts", tableName)).stream()
.anyMatch(
table ->
table.getName().equalsIgnoreCase(tableName)
&& table.getSchema().getName().equalsIgnoreCase("bolts"));
// Use the getTables() method with a filter
return meta.getTables(DSL.name("bolts", tableName)).stream()
.anyMatch(
table ->
table.getName().equalsIgnoreCase(tableName)
&& table.getSchema().getName().equalsIgnoreCase("bolts"));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public List<String> getRanBaseMigrations() {
@ -53,11 +52,10 @@ public class Migration {
boolean migrationsExists = tableExists("_migrations");
if (!migrationsExists) return List.of();
return db.dsl()
return DSL.using(connection)
.select(DSL.field("name"))
.from(DSL.table("_migrations"))
.where(DSL.field("base").eq(true))
.and(DSL.field("executedAt").isNull())
.fetch(DSL.field("name"), String.class);
} catch (Exception e) {
throw new RuntimeException(e);
@ -66,7 +64,7 @@ public class Migration {
public List<String> getRanMigrations() {
try (Connection connection = db.getConnection()) {
return db.dsl()
return DSL.using(connection)
.select(DSL.field("name"))
.from(DSL.table("_migrations"))
.fetch(DSL.field("name"), String.class);

View File

@ -0,0 +1,6 @@
create table _migrations (
name varchar(255) primary key,
executedAt timestamp,
base int(1),
content longtext
)

View File

@ -0,0 +1,96 @@
package dev.mduchene;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import java.util.Objects;
import static org.junit.jupiter.api.Assertions.*;
public class MigrationTest {
// private Db db;
// private Migration migration;
//
// @BeforeEach
// public void setUp() {
// db = Db.Builder.create()
// .url("jdbc:h2:mem:test;DB_CLOSE_DELAY=-1;MODE=MySQL")
// .user("sa")
// .password("")
// .build();
// db.init();
// migration = Migration.of(db);
// }
//
// @AfterEach
// public void tearDown() throws IOException {
// // Clean up the created migration files
// File migrationsDir = new File("src/main/resources/migrations");
// for (File file : Objects.requireNonNull(migrationsDir.listFiles())) {
// if (!file.getName().equals(".gitkeep")) {
// Files.delete(file.toPath());
// }
// }
// db.run("DROP ALL OBJECTS");
// }
//
//
//
// @Test
// public void testCreateMigration() {
// migration.create("test_migration");
// File migrationsDir = new File("src/main/resources/migrations");
// assertEquals(1, Objects.requireNonNull(migrationsDir.listFiles()).length);
// assertTrue(Objects.requireNonNull(migrationsDir.listFiles())[0].getName().contains("_test_migration.sql"));
// }
//
// @Test
// public void testGetPendingMigrations() throws IOException {
// // Create a dummy migration file
// Path newFile = Paths.get("src/main/resources/migrations/20230101000000_initial.sql");
// Files.createFile(newFile);
//
// List<String> pending = migration.getPending();
// assertEquals(1, pending.size());
// assertEquals("20230101000000_initial.sql", pending.get(0));
//
// // Run the migration
// db.run("INSERT INTO _migrations (name) VALUES ('20230101000000_initial.sql')");
//
// pending = migration.getPending();
// assertEquals(0, pending.size());
// }
//
// @Test
// public void testRunMigrations() throws IOException {
// // Create a dummy migration file with some SQL
// String migrationName = "20230101000001_create_users_table.sql";
// Path newFile = Paths.get("src/main/resources/migrations/" + migrationName);
// Files.write(newFile, "CREATE TABLE users (id INT PRIMARY KEY, name VARCHAR(255));".getBytes());
//
// // Run pending migrations
// List<String> pending = migration.getPending();
// migration.run(pending);
//
// // Check if the table was created
// try {
// db.run("SELECT * FROM users");
// } catch (Exception e) {
// fail("Table 'users' should have been created by the migration.");
// }
//
// // Check if the migration was recorded in the _migrations table
//// List<String> ranMigrations = db.getRanMigrations();
//// assertEquals(1, ranMigrations.size());
//// assertEquals(migrationName, ranMigrations.get(0));
// }
}