From 3d14c31bc6481885d2ce166b2abade44ab3c10dc Mon Sep 17 00:00:00 2001 From: Magnus Persson <1431685+magnusp@users.noreply.github.com> Date: Sat, 31 Oct 2026 00:10:41 +0100 Subject: [PATCH] test(jdbc): add Flyway integration smoke tests --- libsql-jdbc/pom.xml | 53 ++++ .../libsql/jdbc/flyway/FlywayKnownGapsIT.java | 116 +++++++++ .../libsql/jdbc/flyway/FlywayMigrationIT.java | 176 +++++++++++++ .../libsql/jdbc/flyway/FlywaySqldSupport.java | 231 ++++++++++++++++++ .../flyway/broken/V1__create_probe.sql | 3 + .../flyway/broken/V2__fail_midway.sql | 5 + .../flyway/migration/R__author_summary.sql | 5 + .../flyway/migration/V1__create_author.sql | 8 + .../flyway/migration/V2__create_book.sql | 8 + .../flyway/migration/V3__seed_data.sql | 8 + pom.xml | 1 + 11 files changed, 614 insertions(+) create mode 100644 libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/flyway/FlywayKnownGapsIT.java create mode 100644 libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/flyway/FlywayMigrationIT.java create mode 100644 libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/flyway/FlywaySqldSupport.java create mode 100644 libsql-jdbc/src/test/resources/flyway/broken/V1__create_probe.sql create mode 100644 libsql-jdbc/src/test/resources/flyway/broken/V2__fail_midway.sql create mode 100644 libsql-jdbc/src/test/resources/flyway/migration/R__author_summary.sql create mode 100644 libsql-jdbc/src/test/resources/flyway/migration/V1__create_author.sql create mode 100644 libsql-jdbc/src/test/resources/flyway/migration/V2__create_book.sql create mode 100644 libsql-jdbc/src/test/resources/flyway/migration/V3__seed_data.sql diff --git a/libsql-jdbc/pom.xml b/libsql-jdbc/pom.xml index da3d350..be40515 100644 --- a/libsql-jdbc/pom.xml +++ b/libsql-jdbc/pom.xml @@ -13,6 +13,10 @@ libSQL JDBC Driver JDBC 4.3 driver for libSQL / Turso + + ${project.build.directory}/docker-config + + io.github.magnusp.libsql @@ -28,10 +32,59 @@ assertj-core test + + org.flywaydb + flyway-core + ${flyway.version} + test + + + org.testcontainers + testcontainers-junit-jupiter + test + + + org.slf4j + slf4j-simple + 2.0.20 + test + + + org.apache.maven.plugins + maven-resources-plugin + 3.5.0 + + + copy-docker-config + pre-integration-test + + copy-resources + + + ${docker.config.dir} + + + ${project.basedir}/../libsql-conformance/src/test/docker-config + false + + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + ${docker.config.dir} + + + org.apache.maven.plugins maven-shade-plugin diff --git a/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/flyway/FlywayKnownGapsIT.java b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/flyway/FlywayKnownGapsIT.java new file mode 100644 index 0000000..b645c48 --- /dev/null +++ b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/flyway/FlywayKnownGapsIT.java @@ -0,0 +1,116 @@ +package io.github.magnusp.libsql.jdbc.flyway; + +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.FlywayException; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * Pins the Flyway-facing gaps the smoke test found. + * + *

These assertions describe current behavior, not desired behavior. If a test + * starts failing, the driver improved and the workaround in the assertion message + * can be removed. + */ +class FlywayKnownGapsIT extends FlywaySqldSupport { + + private static Flyway flyway() { + return Flyway.configure() + .dataSource(dataSource()) + .locations("classpath:flyway/migration") + .cleanDisabled(false) + .load(); + } + + @BeforeEach + void emptyDatabase() throws Exception { + resetDatabase(); + } + + @Test + void libsqlUrlIsNotRecognizedByFlyway() { + assertThatThrownBy(() -> Flyway.configure() + .dataSource(jdbcUrl(), "", "") + .load() + .migrate()) + .as("Flyway 13's SQLite plugin claims only jdbc:sqlite:, jdbc:sqldroid: and" + + " p6spy prefixes. Use the jdbc:sqlite: alias or pass a DataSource.") + .isInstanceOf(FlywayException.class) + .hasMessageContaining("No Flyway database plugin found to handle") + .hasMessageContaining("jdbc:libsql:"); + } + + @Test + void sqliteAliasUrlCannotUseTheLibsqlDriver() { + assertThatThrownBy(() -> Flyway.configure() + .driver("io.github.magnusp.libsql.jdbc.LibsqlDriver") + .dataSource("jdbc:sqlite:" + httpUrl(), "", "") + .load() + .migrate()) + .as("Flyway 13.12 builds its URL-based DriverDataSource eagerly and passes a" + + " null driver class, so driver(...) is ignored for this path. Flyway" + + " falls back to org.sqlite.JDBC, which is not on the classpath." + + " Pass a DataSource instead.") + .isInstanceOf(FlywayException.class) + .hasMessageContaining("No JDBC driver found to handle"); + } + + @Test + void productNameStartsWithSqliteSoProductDetectionWorks() throws SQLException { + try (Connection connection = connect()) { + assertThat(connection.getMetaData().getDatabaseProductName()) + .as("Flyway matches SQLite with startsWith(\"SQLite\"), so the suffix is safe") + .startsWith("SQLite"); + assertThat(connection.getMetaData().getDatabaseMajorVersion()).isNotZero(); + } + } + + @Test + void cleanIsBlockedUntilMultiStatementSupportLands() throws Exception { + Flyway flyway = flyway(); + flyway.migrate(); + + assertThat(scalarInt("PRAGMA foreign_keys")) + .as("sqld enables foreign keys on a fresh connection, unlike vanilla SQLite") + .isEqualTo(1); + + // The decision is to support clean(); see #258. This pin flips when + // multi-statement PRAGMA/DROP scripts are supported. + assertThatThrownBy(flyway::clean) + .as("Flyway sees foreign keys enabled and sends" + + " 'PRAGMA foreign_keys = OFF; DROP TABLE ...; PRAGMA foreign_keys = ON'" + + " as one statement. sqld rejects that string, so clean cannot finish." + + " Supporting clean() is tracked in #258.") + .isInstanceOf(FlywayException.class) + .hasMessageContaining("Unable to drop") + .hasStackTraceContaining("SQL string contains more than one statement"); + + assertThat(userTables()) + .as("clean stopped at the first table") + .contains("author"); + } + + @Test + void autoCommitStatementsDoNotShareSessionState() throws SQLException { + try (Connection connection = connect(); Statement statement = connection.createStatement()) { + statement.execute("PRAGMA foreign_keys = OFF"); + + try (ResultSet resultSet = statement.executeQuery("PRAGMA foreign_keys")) { + assertThat(resultSet.next()).isTrue(); + assertThat(resultSet.getInt(1)) + .as("each auto-commit statement runs as a one-shot Hrana stream, so" + + " connection-scoped state set by PRAGMA is gone by the next" + + " statement. Wrap related statements in a transaction.") + .isEqualTo(1); + } + } + } +} diff --git a/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/flyway/FlywayMigrationIT.java b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/flyway/FlywayMigrationIT.java new file mode 100644 index 0000000..b05f594 --- /dev/null +++ b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/flyway/FlywayMigrationIT.java @@ -0,0 +1,176 @@ +package io.github.magnusp.libsql.jdbc.flyway; + +import org.flywaydb.core.Flyway; +import org.flywaydb.core.api.Location; +import org.flywaydb.core.api.MigrationInfo; +import org.flywaydb.core.api.configuration.ClassicConfiguration; +import org.flywaydb.core.api.output.MigrateResult; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.sql.Connection; +import java.sql.ResultSet; +import java.sql.Statement; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +/** + * End-to-end Flyway probes. Every migration below travels through the libSQL JDBC + * driver to a real sqld container. + * + *

Flyway owns the schema, so these tests cover the driver paths Flyway depends + * on: multi-statement migration scripts, DDL inside a transaction, the schema + * history table and its timestamp column, baseline, and repeatable migrations. + * + *

Two Flyway-facing limitations are pinned in {@link FlywayKnownGapsIT}. + */ +class FlywayMigrationIT extends FlywaySqldSupport { + + private static Flyway flyway(String... locations) { + return Flyway.configure() + .dataSource(dataSource()) + .locations(locations.length == 0 ? new String[] {"classpath:flyway/migration"} : locations) + .cleanDisabled(false) + .load(); + } + + @BeforeEach + void emptyDatabase() throws Exception { + resetDatabase(); + } + + @Test + void migratesThroughADataSource() throws Exception { + Flyway flyway = flyway(); + + MigrateResult result = flyway.migrate(); + + assertThat(result.migrationsExecuted) + .as("V1, V2, V3 and the repeatable view") + .isEqualTo(4); + assertThat(userTables()).contains("author", "book", "flyway_schema_history"); + + assertThat(scalarInt("SELECT count(*) FROM author")).isEqualTo(2); + assertThat(scalarInt("SELECT count(*) FROM book")).isEqualTo(2); + assertThat(scalarInt("SELECT book_count FROM author_summary WHERE name = 'Ursula K. Le Guin'")) + .isEqualTo(1); + + assertThat(flyway.info().applied()) + .extracting(info -> info.getVersion() == null ? "repeatable" : info.getVersion().toString()) + .containsExactly("1", "2", "3", "repeatable"); + assertThat(flyway.info().current().getState().isApplied()).isTrue(); + assertThat(flyway.info().pending()).isEmpty(); + + flyway.validate(); + } + + @Test + void secondMigrateIsANoOp() { + Flyway flyway = flyway(); + flyway.migrate(); + + assertThat(flyway.migrate().migrationsExecuted).isZero(); + } + + @Test + void schemaHistoryIsReadableThroughTheDriver() throws Exception { + flyway().migrate(); + + try (Connection connection = connect(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery( + "SELECT installed_rank, version, description, type, success, installed_on" + + " FROM flyway_schema_history ORDER BY installed_rank")) { + assertThat(resultSet.next()).isTrue(); + assertThat(resultSet.getInt("installed_rank")).isEqualTo(1); + assertThat(resultSet.getString("version")).isEqualTo("1"); + assertThat(resultSet.getString("description")).isEqualTo("create author"); + assertThat(resultSet.getString("type")).isEqualTo("SQL"); + assertThat(resultSet.getBoolean("success")).isTrue(); + assertThat(resultSet.getTimestamp("installed_on")) + .as("installed_on is TEXT with a strftime() default") + .isNotNull(); + assertThat(resultSet.wasNull()).isFalse(); + } + } + + @Test + void migrateWithASqliteAliasUrlAndADataSource() throws Exception { + // jdbc:sqlite: is the only prefix Flyway's SQLite plugin accepts. Passing a + // DataSource still avoids its URL-based driver pick, which hardcodes + // org.sqlite.JDBC. This proves the alias itself works through the driver. + Flyway flyway = Flyway.configure() + .dataSource(new FlywaySqldSupport.DriverManagerDataSource( + "jdbc:sqlite:" + httpUrl())) + .locations("classpath:flyway/migration") + .load(); + + MigrateResult result = flyway.migrate(); + + assertThat(result.migrationsExecuted).isEqualTo(4); + assertThat(scalarInt("SELECT count(*) FROM author")).isEqualTo(2); + } + + @Test + void migrateWithUrlConfigurationAndExplicitDriver() throws Exception { + // This mirrors the Maven plugin / flyway.properties path: url(...) plus an + // explicit driver is honored, because unlike dataSource(url) it does not + // build the DriverDataSource eagerly with a null driver class. The URL + // still has to use the jdbc:sqlite: alias that Flyway's plugin claims. + ClassicConfiguration configuration = new ClassicConfiguration(); + configuration.setUrl("jdbc:sqlite:" + httpUrl()); + configuration.setDriver("io.github.magnusp.libsql.jdbc.LibsqlDriver"); + configuration.setLocations(new Location("classpath:flyway/migration")); + Flyway flyway = new Flyway(configuration); + + assertThat(flyway.migrate().migrationsExecuted).isEqualTo(4); + assertThat(scalarInt("SELECT count(*) FROM author")).isEqualTo(2); + } + + @Test + void failedMigrationRollsBackItsDdl() throws Exception { + Flyway flyway = flyway("classpath:flyway/broken"); + + assertThatThrownBy(flyway::migrate) + .as("V2 inserts into a table that does not exist") + .hasStackTraceContaining("table_that_does_not_exist"); + + assertThat(tableExists("migration_probe")) + .as("V1 committed") + .isTrue(); + assertThat(tableExists("partial_table")) + .as("V2's CREATE TABLE must roll back with the failed insert") + .isFalse(); + + assertThat(flyway.info().applied()) + .extracting(info -> info.getVersion().toString()) + .containsExactly("1"); + } + + @Test + void baselineAnExistingSchemaThenMigrate() throws Exception { + try (Connection connection = connect(); Statement statement = connection.createStatement()) { + statement.execute("CREATE TABLE author (" + + " id INTEGER PRIMARY KEY AUTOINCREMENT," + + " name TEXT NOT NULL," + + " country TEXT)"); + statement.execute("CREATE INDEX author_name_idx ON author (name)"); + } + + Flyway flyway = flyway(); + flyway.baseline(); + + MigrateResult result = flyway.migrate(); + + assertThat(result.migrationsExecuted) + .as("V1 is skipped by the baseline; V2, V3 and the repeatable view run") + .isEqualTo(3); + assertThat(userTables()).contains("author", "book", "flyway_schema_history"); + assertThat(scalarInt("SELECT count(*) FROM author")).isEqualTo(2); + + MigrationInfo baseline = flyway.info().applied()[0]; + assertThat(baseline.getVersion().toString()).isEqualTo("1"); + assertThat(baseline.getType().name()).isEqualTo("BASELINE"); + } +} diff --git a/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/flyway/FlywaySqldSupport.java b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/flyway/FlywaySqldSupport.java new file mode 100644 index 0000000..b55b9b9 --- /dev/null +++ b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/flyway/FlywaySqldSupport.java @@ -0,0 +1,231 @@ +package io.github.magnusp.libsql.jdbc.flyway; + +import org.testcontainers.containers.GenericContainer; +import org.testcontainers.containers.wait.strategy.Wait; +import org.testcontainers.junit.jupiter.Container; +import org.testcontainers.junit.jupiter.Testcontainers; +import org.testcontainers.utility.DockerImageName; + +import javax.sql.DataSource; +import java.io.PrintWriter; +import java.sql.Connection; +import java.sql.DriverManager; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.SQLFeatureNotSupportedException; +import java.sql.Statement; +import java.time.Duration; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.logging.Logger; + +/** + * A throwaway sqld container for the Flyway integration tests. + * + *

Flyway owns the schema here, so unlike the jOOQ support class there is no + * out-of-band bootstrap. The reset helpers exist only to isolate tests: every + * test starts from an empty database. + */ +@Testcontainers +abstract class FlywaySqldSupport { + + private static final int PORT = 8080; + private static final String LIBSQL_IMAGE = requireLibsqlImage(); + + @Container + static final GenericContainer LIBSQL = new GenericContainer<>(DockerImageName.parse(LIBSQL_IMAGE)) + .withExposedPorts(PORT) + .withEnv("SQLD_NODE", "primary") + .withEnv("SQLD_DISABLE_INTELLIGENT_THROTTLING", "true") + .withEnv("SQLD_CONNECTION_CREATION_TIMEOUT_SEC", "30") + .waitingFor(Wait.forHttp("/v2/pipeline") + .forPort(PORT) + .forStatusCode(405) + .withStartupTimeout(Duration.ofSeconds(60))); + + static String jdbcUrl() { + return "jdbc:libsql:http://" + LIBSQL.getHost() + ":" + LIBSQL.getMappedPort(PORT); + } + + static String httpUrl() { + return "http://" + LIBSQL.getHost() + ":" + LIBSQL.getMappedPort(PORT); + } + + static DataSource dataSource() { + return new DriverManagerDataSource(jdbcUrl()); + } + + static Connection connect() throws SQLException { + return DriverManager.getConnection(jdbcUrl()); + } + + static void resetDatabase() throws SQLException { + try (Connection connection = connect(); Statement statement = connection.createStatement()) { + for (String view : names(statement, "view")) { + statement.execute("DROP VIEW IF EXISTS \"" + view + "\""); + } + // A PRAGMA does not survive into the next auto-commit statement, so the + // drop order has to respect the foreign-key graph instead of disabling + // checks. + for (String table : dropOrder(statement, names(statement, "table"))) { + statement.execute("DROP TABLE IF EXISTS \"" + table + "\""); + } + } + } + + static List userTables() throws SQLException { + List tables = new ArrayList<>(); + try (Connection connection = connect(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery( + "SELECT name FROM sqlite_master WHERE type = 'table'" + + " AND name NOT LIKE 'sqlite_%' ORDER BY name")) { + while (resultSet.next()) { + tables.add(resultSet.getString(1)); + } + } + return tables; + } + + static boolean tableExists(String name) throws SQLException { + try (Connection connection = connect(); + var statement = connection.prepareStatement( + "SELECT count(*) FROM sqlite_master WHERE type = 'table' AND name = ?")) { + statement.setString(1, name); + try (ResultSet resultSet = statement.executeQuery()) { + resultSet.next(); + return resultSet.getInt(1) > 0; + } + } + } + + static int scalarInt(String sql) throws SQLException { + try (Connection connection = connect(); + Statement statement = connection.createStatement(); + ResultSet resultSet = statement.executeQuery(sql)) { + resultSet.next(); + return resultSet.getInt(1); + } + } + + private static List names(Statement statement, String type) throws SQLException { + List names = new ArrayList<>(); + try (ResultSet resultSet = statement.executeQuery( + "SELECT name FROM sqlite_master WHERE type = '" + type + "'" + + " AND name NOT LIKE 'sqlite_%' ORDER BY name")) { + while (resultSet.next()) { + names.add(resultSet.getString(1)); + } + } + return names; + } + + /** Orders children before the parents they reference. */ + private static List dropOrder(Statement statement, List tables) throws SQLException { + var parents = new LinkedHashMap>(); + var childCount = new LinkedHashMap(); + for (String table : tables) { + List references = new ArrayList<>(); + try (ResultSet resultSet = statement.executeQuery("PRAGMA foreign_key_list(\"" + table + "\")")) { + while (resultSet.next()) { + String parent = resultSet.getString("table"); + if (tables.contains(parent) && !references.contains(parent)) { + references.add(parent); + } + } + } + parents.put(table, references); + childCount.put(table, 0); + } + for (List references : parents.values()) { + for (String parent : references) { + childCount.merge(parent, 1, Integer::sum); + } + } + + List order = new ArrayList<>(); + var remaining = new LinkedHashSet<>(tables); + while (!remaining.isEmpty()) { + String leaf = remaining.stream() + .filter(table -> childCount.get(table) == 0) + .findFirst() + .orElseGet(() -> remaining.iterator().next()); + order.add(leaf); + remaining.remove(leaf); + for (String parent : parents.get(leaf)) { + childCount.merge(parent, -1, Integer::sum); + } + } + return order; + } + + private static String requireLibsqlImage() { + String image = System.getProperty("libsql.image"); + if (image == null || image.isBlank()) { + throw new IllegalStateException( + "libsql.image is not set. Run the integration tests with Maven so the" + + " pinned sqld image from the root pom is used."); + } + return image; + } + + /** + * The smallest DataSource over DriverManager. Flyway's URL-based configuration + * picks the driver from the URL before it connects, which fails for libSQL; a + * DataSource is how an application with a pool would use the driver. + */ + static final class DriverManagerDataSource implements DataSource { + + private final String url; + + DriverManagerDataSource(String url) { + this.url = url; + } + + @Override + public Connection getConnection() throws SQLException { + return DriverManager.getConnection(url); + } + + @Override + public Connection getConnection(String username, String password) throws SQLException { + return DriverManager.getConnection(url, username, password); + } + + @Override + public PrintWriter getLogWriter() { + return null; + } + + @Override + public void setLogWriter(PrintWriter out) {} + + @Override + public void setLoginTimeout(int seconds) {} + + @Override + public int getLoginTimeout() { + return 0; + } + + @Override + public Logger getParentLogger() throws SQLFeatureNotSupportedException { + throw new SQLFeatureNotSupportedException("no parent logger"); + } + + @Override + public T unwrap(Class iface) throws SQLException { + if (iface.isInstance(this)) { + return iface.cast(this); + } + throw new SQLException("cannot unwrap to " + iface.getName()); + } + + @Override + public boolean isWrapperFor(Class iface) { + return iface.isInstance(this); + } + } +} diff --git a/libsql-jdbc/src/test/resources/flyway/broken/V1__create_probe.sql b/libsql-jdbc/src/test/resources/flyway/broken/V1__create_probe.sql new file mode 100644 index 0000000..f8b7c74 --- /dev/null +++ b/libsql-jdbc/src/test/resources/flyway/broken/V1__create_probe.sql @@ -0,0 +1,3 @@ +CREATE TABLE migration_probe ( + id INTEGER PRIMARY KEY +); diff --git a/libsql-jdbc/src/test/resources/flyway/broken/V2__fail_midway.sql b/libsql-jdbc/src/test/resources/flyway/broken/V2__fail_midway.sql new file mode 100644 index 0000000..f65da0d --- /dev/null +++ b/libsql-jdbc/src/test/resources/flyway/broken/V2__fail_midway.sql @@ -0,0 +1,5 @@ +CREATE TABLE partial_table ( + id INTEGER PRIMARY KEY +); + +INSERT INTO table_that_does_not_exist (id) VALUES (1); diff --git a/libsql-jdbc/src/test/resources/flyway/migration/R__author_summary.sql b/libsql-jdbc/src/test/resources/flyway/migration/R__author_summary.sql new file mode 100644 index 0000000..4ffe25b --- /dev/null +++ b/libsql-jdbc/src/test/resources/flyway/migration/R__author_summary.sql @@ -0,0 +1,5 @@ +CREATE VIEW author_summary AS +SELECT a.id, a.name, COUNT(b.id) AS book_count +FROM author a +LEFT JOIN book b ON b.author_id = a.id +GROUP BY a.id, a.name; diff --git a/libsql-jdbc/src/test/resources/flyway/migration/V1__create_author.sql b/libsql-jdbc/src/test/resources/flyway/migration/V1__create_author.sql new file mode 100644 index 0000000..e24dffc --- /dev/null +++ b/libsql-jdbc/src/test/resources/flyway/migration/V1__create_author.sql @@ -0,0 +1,8 @@ +-- A migration file with more than one statement: Flyway splits on semicolons. +CREATE TABLE author ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + name TEXT NOT NULL, + country TEXT +); + +CREATE INDEX author_name_idx ON author (name); diff --git a/libsql-jdbc/src/test/resources/flyway/migration/V2__create_book.sql b/libsql-jdbc/src/test/resources/flyway/migration/V2__create_book.sql new file mode 100644 index 0000000..eda6fd2 --- /dev/null +++ b/libsql-jdbc/src/test/resources/flyway/migration/V2__create_book.sql @@ -0,0 +1,8 @@ +CREATE TABLE book ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + author_id INTEGER NOT NULL REFERENCES author (id), + title TEXT NOT NULL, + price REAL +); + +CREATE INDEX book_author_id_idx ON book (author_id); diff --git a/libsql-jdbc/src/test/resources/flyway/migration/V3__seed_data.sql b/libsql-jdbc/src/test/resources/flyway/migration/V3__seed_data.sql new file mode 100644 index 0000000..b8c484e --- /dev/null +++ b/libsql-jdbc/src/test/resources/flyway/migration/V3__seed_data.sql @@ -0,0 +1,8 @@ +INSERT INTO author (name, country) VALUES ('Ursula K. Le Guin', 'US'); +INSERT INTO author (name, country) VALUES ('Octavia E. Butler', 'US'); + +INSERT INTO book (author_id, title, price) +SELECT id, 'A Wizard of Earthsea', 9.99 FROM author WHERE name = 'Ursula K. Le Guin'; + +INSERT INTO book (author_id, title, price) +SELECT id, 'Kindred', 12.50 FROM author WHERE name = 'Octavia E. Butler'; diff --git a/pom.xml b/pom.xml index 80933e7..63b8da1 100644 --- a/pom.xml +++ b/pom.xml @@ -61,6 +61,7 @@ 3.27.7 2.0.5 7.1.0 + 13.12.0 4.36.2 5.7-beta1