From 23f71e266da858ef42d5174a93bf93d406041b4a Mon Sep 17 00:00:00 2001 From: Magnus Persson <1431685+magnusp@users.noreply.github.com> Date: Sat, 31 Oct 2026 01:36:12 +0100 Subject: [PATCH 1/3] docs: document the Liquibase bootstrap for jdbc:libsql URLs Liquibase resolves a driver from the URL before it connects, and its SQLite implementation claims only jdbc:sqlite:. Document the three supported configurations and pin the explicit-driver DatabaseFactory path. Closes #262. --- README.md | 49 +++++++++++++++++++ .../libsql/jdbc/liquibase/LiquibaseIT.java | 22 +++++++++ 2 files changed, 71 insertions(+) diff --git a/README.md b/README.md index 33ee6d2..d342afc 100644 --- a/README.md +++ b/README.md @@ -133,6 +133,55 @@ DOCKER_CONFIG="$PWD/libsql-conformance/src/test/docker-config" ./mvnw clean veri The compliance profile is in [`docs/jdbc-appendix-b-compliance.md`](docs/jdbc-appendix-b-compliance.md). +## Integrations + +### Liquibase + +Liquibase resolves a JDBC driver class from the URL before it opens a connection. Its +SQLite implementation claims only the `jdbc:sqlite:` prefix, so a `jdbc:libsql:` URL +needs an explicit driver class or a `DataSource`. + +The driver reports the database product name as exactly `SQLite`, so Liquibase selects +its SQLite dialect through the normal `DatabaseFactory` path once a connection is open. + +Use one of these configurations: + +**API with an explicit driver class** + +```java +import io.github.magnusp.libsql.jdbc.LibsqlDriver; +import liquibase.database.Database; +import liquibase.database.DatabaseFactory; +import liquibase.resource.ClassLoaderResourceAccessor; + +Database database = DatabaseFactory.getInstance().openDatabase( + "jdbc:libsql:http://localhost:8080", + null, + null, + LibsqlDriver.class.getName(), + new ClassLoaderResourceAccessor()); +``` + +**Liquibase Maven plugin or `liquibase.properties`** + +```properties +url=jdbc:libsql:http://localhost:8080 +driver=io.github.magnusp.libsql.jdbc.LibsqlDriver +``` + +**Spring Boot or a connection pool** + +Pass a `DataSource` and set the driver class explicitly: + +```properties +spring.datasource.url=jdbc:libsql:http://localhost:8080 +spring.datasource.driver-class-name=io.github.magnusp.libsql.jdbc.LibsqlDriver +``` + +The `jdbc:libsql:` prefix is the supported form. Liquibase never asks `DriverManager` +which driver accepts a URL, so leaving the driver unset fails before the connection is +made. + ## Contributing We welcome contributions! Please see [CONTRIBUTING.md](CONTRIBUTING.md) for contribution guidelines and development workflow, and [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) for community standards. diff --git a/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseIT.java b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseIT.java index f50d60b..9bb42be 100644 --- a/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseIT.java +++ b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseIT.java @@ -1,5 +1,6 @@ package io.github.magnusp.libsql.jdbc.liquibase; +import io.github.magnusp.libsql.jdbc.LibsqlDriver; import liquibase.Contexts; import liquibase.LabelExpression; import liquibase.Liquibase; @@ -161,6 +162,27 @@ void databaseFactoryResolvesTheSqliteImplementation() throws Exception { } } + /** + * The supported bootstrapping path for a {@code jdbc:libsql:} URL: name the + * driver class explicitly, which {@link DatabaseFactory#openDatabase} + * accepts before it resolves the SQLite implementation. + */ + @Test + void databaseFactoryOpensALibsqlUrlWithAnExplicitDriver() throws Exception { + Database database = DatabaseFactory.getInstance() + .openDatabase( + jdbcUrl(), + null, + null, + LibsqlDriver.class.getName(), + new ClassLoaderResourceAccessor()); + try { + assertThat(database).isInstanceOf(SQLiteDatabase.class); + } finally { + database.close(); + } + } + /** * Pins the second bootstrapping gap: Liquibase resolves a JDBC driver class * from the URL before it opens a connection, and its SQLite implementation From 127ed44ff749510c2b57e3f4e94950ba2fe95920 Mon Sep 17 00:00:00 2001 From: Magnus Persson <1431685+magnusp@users.noreply.github.com> Date: Sat, 31 Oct 2026 01:58:03 +0100 Subject: [PATCH 2/3] test(liquibase): use the explicit-driver openDatabase overload The five-argument DatabaseFactory.openDatabase overload treats its fourth argument as a property-provider class, not a driver, so the probe failed with a ClassCastException. Use the eight-argument overload that names the driver explicitly, and correct the README snippet. --- README.md | 9 ++++++--- .../magnusp/libsql/jdbc/liquibase/LiquibaseIT.java | 5 +++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index d342afc..1b90c17 100644 --- a/README.md +++ b/README.md @@ -156,9 +156,12 @@ import liquibase.resource.ClassLoaderResourceAccessor; Database database = DatabaseFactory.getInstance().openDatabase( "jdbc:libsql:http://localhost:8080", - null, - null, - LibsqlDriver.class.getName(), + null, // username + null, // password + LibsqlDriver.class.getName(), // driver + null, // database class + null, // driver properties file + null, // property provider class new ClassLoaderResourceAccessor()); ``` diff --git a/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseIT.java b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseIT.java index 9bb42be..3a8804c 100644 --- a/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseIT.java +++ b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseIT.java @@ -169,12 +169,17 @@ void databaseFactoryResolvesTheSqliteImplementation() throws Exception { */ @Test void databaseFactoryOpensALibsqlUrlWithAnExplicitDriver() throws Exception { + // The five-argument openDatabase overload names a property-provider class, not a + // driver. The eight-argument overload takes the driver explicitly. Database database = DatabaseFactory.getInstance() .openDatabase( jdbcUrl(), null, null, LibsqlDriver.class.getName(), + null, + null, + null, new ClassLoaderResourceAccessor()); try { assertThat(database).isInstanceOf(SQLiteDatabase.class); From 2411fc9422a1e146a532212c2264cfd3711f105d Mon Sep 17 00:00:00 2001 From: Magnus Persson <1431685+magnusp@users.noreply.github.com> Date: Sat, 31 Oct 2026 02:39:05 +0100 Subject: [PATCH 3/3] docs(liquibase): use the jdbc:sqlite prefix Liquibase's SQLite implementation claims jdbc:sqlite: and selects its dialect from the database product name, which is now exactly SQLite. The driver maps the alias to sqld, so document and pin that form with the explicit LibsqlDriver class that Liquibase still needs. --- README.md | 23 ++++++++++--------- .../libsql/jdbc/liquibase/LiquibaseIT.java | 10 ++++---- .../jdbc/liquibase/LiquibaseSqldSupport.java | 5 ++++ 3 files changed, 22 insertions(+), 16 deletions(-) diff --git a/README.md b/README.md index 1b90c17..01c42fd 100644 --- a/README.md +++ b/README.md @@ -137,12 +137,13 @@ The compliance profile is in [`docs/jdbc-appendix-b-compliance.md`](docs/jdbc-ap ### Liquibase -Liquibase resolves a JDBC driver class from the URL before it opens a connection. Its -SQLite implementation claims only the `jdbc:sqlite:` prefix, so a `jdbc:libsql:` URL -needs an explicit driver class or a `DataSource`. +Liquibase's SQLite implementation claims the `jdbc:sqlite:` prefix and selects its +dialect from the database product name. The driver maps `jdbc:sqlite:http://...` to +sqld and reports the product name as exactly `SQLite`, so the two work together. -The driver reports the database product name as exactly `SQLite`, so Liquibase selects -its SQLite dialect through the normal `DatabaseFactory` path once a connection is open. +Liquibase still resolves a JDBC driver class from the URL before it opens a connection. +It maps `jdbc:sqlite:` to `org.sqlite.JDBC`, which is not on the classpath, so name +`LibsqlDriver` explicitly or pass a `DataSource`. Use one of these configurations: @@ -155,7 +156,7 @@ import liquibase.database.DatabaseFactory; import liquibase.resource.ClassLoaderResourceAccessor; Database database = DatabaseFactory.getInstance().openDatabase( - "jdbc:libsql:http://localhost:8080", + "jdbc:sqlite:http://localhost:8080", null, // username null, // password LibsqlDriver.class.getName(), // driver @@ -168,7 +169,7 @@ Database database = DatabaseFactory.getInstance().openDatabase( **Liquibase Maven plugin or `liquibase.properties`** ```properties -url=jdbc:libsql:http://localhost:8080 +url=jdbc:sqlite:http://localhost:8080 driver=io.github.magnusp.libsql.jdbc.LibsqlDriver ``` @@ -177,13 +178,13 @@ driver=io.github.magnusp.libsql.jdbc.LibsqlDriver Pass a `DataSource` and set the driver class explicitly: ```properties -spring.datasource.url=jdbc:libsql:http://localhost:8080 +spring.datasource.url=jdbc:sqlite:http://localhost:8080 spring.datasource.driver-class-name=io.github.magnusp.libsql.jdbc.LibsqlDriver ``` -The `jdbc:libsql:` prefix is the supported form. Liquibase never asks `DriverManager` -which driver accepts a URL, so leaving the driver unset fails before the connection is -made. +Use the `jdbc:sqlite:` prefix so Liquibase's SQLite implementation claims the URL. +`jdbc:libsql:` also reaches the driver, but Liquibase cannot resolve a driver from it +and still needs the explicit driver class. ## Contributing diff --git a/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseIT.java b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseIT.java index 3a8804c..37d2f1d 100644 --- a/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseIT.java +++ b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseIT.java @@ -163,17 +163,17 @@ void databaseFactoryResolvesTheSqliteImplementation() throws Exception { } /** - * The supported bootstrapping path for a {@code jdbc:libsql:} URL: name the - * driver class explicitly, which {@link DatabaseFactory#openDatabase} - * accepts before it resolves the SQLite implementation. + * The supported bootstrapping path: use the {@code jdbc:sqlite:} prefix that + * Liquibase's SQLite implementation claims and name the driver class + * explicitly. The driver maps that prefix to sqld. */ @Test - void databaseFactoryOpensALibsqlUrlWithAnExplicitDriver() throws Exception { + void databaseFactoryOpensASqliteAliasUrlWithAnExplicitDriver() throws Exception { // The five-argument openDatabase overload names a property-provider class, not a // driver. The eight-argument overload takes the driver explicitly. Database database = DatabaseFactory.getInstance() .openDatabase( - jdbcUrl(), + sqliteJdbcUrl(), null, null, LibsqlDriver.class.getName(), diff --git a/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseSqldSupport.java b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseSqldSupport.java index 3f606ac..98d6128 100644 --- a/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseSqldSupport.java +++ b/libsql-jdbc/src/test/java/io/github/magnusp/libsql/jdbc/liquibase/LiquibaseSqldSupport.java @@ -37,6 +37,11 @@ static String jdbcUrl() { return "jdbc:libsql:http://" + LIBSQL.getHost() + ":" + LIBSQL.getMappedPort(PORT); } + /** The prefix Liquibase's SQLite implementation claims; the driver maps it to sqld. */ + static String sqliteJdbcUrl() { + return "jdbc:sqlite:http://" + LIBSQL.getHost() + ":" + LIBSQL.getMappedPort(PORT); + } + private static String requireLibsqlImage() { String image = System.getProperty("libsql.image"); if (image == null || image.isBlank()) {