Skip to content

Latest commit

 

History

History
283 lines (206 loc) · 9.54 KB

README.md

File metadata and controls

283 lines (206 loc) · 9.54 KB

libsql-java

CI & Integration Tests License: MIT Java LTS

A pure-Java JDBC driver, client library, and protocol codec for libSQL and sqld databases over the Hrana protocol.


Features

  • Standard JDBC Driver: Drop-in java.sql compliance (jdbc:libsql:...) for applications, connection pools (HikariCP), and ORMs.
  • Pure Java: No JNI, C-bindings, or native dependencies required.
  • Hrana 3 Protocol Support: High-performance binary and JSON protocol codecs for libSQL/sqld.
  • Lightweight & Fast: Built for modern Java (LTS).
  • Batteries Included: Multi-module architecture providing fine-grained dependencies.

Modules

Module Description
libsql-hrana-codec Core codec implementation for the Hrana 3 protocol
libsql-client High-level client API for libSQL queries and streaming
libsql-jdbc Standard JDBC 4.3 driver (jdbc:libsql:...)
libsql-itests Integration tests against sqld running in Testcontainers
libsql-conformance Selected xerial/sqlite-jdbc tests running against libSQL
libsql-perf Driver-side codec and JDBC benchmark used for the PR performance comment

Getting Started

Maven Dependency

Artifacts are hosted on GitHub Packages. Add the repository and configure credentials as described in Publishing.

<repositories>
    <repository>
        <id>github</id>
        <url>https://maven.pkg.github.com/magnusp/libsql-java</url>
    </repository>
</repositories>
<dependency>
    <groupId>io.github.magnusp.libsql</groupId>
    <artifactId>libsql-jdbc</artifactId>
    <version>0.2.0</version>
</dependency>

Prerelease builds from main use 0.2.0-rc.<shortsha>. No -SNAPSHOT versions are published.

JDBC Usage Example

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

public class Example {
    public static void main(String[] args) throws Exception {
        String url = "jdbc:libsql:http://localhost:8080";

        try (Connection conn = DriverManager.getConnection(url);
             PreparedStatement stmt = conn.prepareStatement("SELECT 1 AS col")) {

            try (ResultSet rs = stmt.executeQuery()) {
                if (rs.next()) {
                    System.out.println("Result: " + rs.getInt("col"));
                }
            }
        }
    }
}

Development & Building

Prerequisites

  • Java: Java LTS
  • Tooling: Managed via mise and Maven Wrapper (./mvnw)
  • Docker: For running integration tests against sqld

Commands

# Compile and run unit tests
mise exec -- ./mvnw clean test

# Run full integration test suite (requires Docker)
mise exec -- ./mvnw clean verify

# Format code with Spotless (Google Java Format)
mise exec -- ./mvnw spotless:apply

JDBC 4.3 Appendix B checks

Run the Appendix B unit suite.

./mvnw -q -pl libsql-jdbc -Dtest='Jdbc4AppendixB*' -Dsurefire.failIfNoSpecifiedTests=false test

Run the full libsql-jdbc unit suite.

./mvnw -q -pl libsql-jdbc test

Run the Appendix B server suite. The install step puts the current driver in the local repository. Under the nono sandbox, the Docker config path is required.

./mvnw -q -DskipTests install -pl libsql-hrana-codec,libsql-client,libsql-jdbc
DOCKER_CONFIG="$PWD/libsql-conformance/src/test/docker-config" ./mvnw -pl libsql-itests -Dit.test=Jdbc4AppendixBTypeMappingIT -Dtest=NoSuchTest -Dsurefire.failIfNoSpecifiedTests=false verify

Run the xerial conformance suite.

./mvnw -pl libsql-conformance -am verify

Run the full gate.

DOCKER_CONFIG="$PWD/libsql-conformance/src/test/docker-config" ./mvnw clean verify

The compliance profile is in docs/jdbc-appendix-b-compliance.md.

Integrations

Liquibase

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.

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:

API with an explicit driver class

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:sqlite:http://localhost:8080",
        null,                          // username
        null,                          // password
        LibsqlDriver.class.getName(),  // driver
        null,                          // database class
        null,                          // driver properties file
        null,                          // property provider class
        new ClassLoaderResourceAccessor());

Liquibase Maven plugin or liquibase.properties

url=jdbc:sqlite: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:

spring.datasource.url=jdbc:sqlite:http://localhost:8080
spring.datasource.driver-class-name=io.github.magnusp.libsql.jdbc.LibsqlDriver

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.

Flyway

Flyway selects its database plugin from the URL before it opens a connection. Its SQLite plugin claims jdbc:sqlite:, jdbc:sqldroid:, and the p6spy prefixes, so a jdbc:libsql: URL is rejected with:

No Flyway database plugin found to handle jdbc:libsql:********

The jdbc:sqlite: alias is claimed, but its plugin resolves the driver to org.sqlite.JDBC. Programmatic Flyway.configure().dataSource(url, ...) builds a DriverDataSource eagerly with a null driver class, so a later driver(...) call is ignored.

Use one of these configurations:

DataSource with either URL form (recommended)

jdbc:libsql: works when Flyway receives a DataSource, because the plugin lookup has already happened by the time the connection is opened:

Flyway flyway = Flyway.configure()
        .dataSource(dataSource) // any pool or DriverManager-backed DataSource
        .locations("classpath:db/migration")
        .load();
flyway.migrate();

Spring Boot configuration, using the application DataSource that Flyway receives automatically:

spring.datasource.url=jdbc:libsql:http://localhost:8080
spring.datasource.driver-class-name=io.github.magnusp.libsql.jdbc.LibsqlDriver

URL configuration with an explicit driver

URL-based configuration must use the jdbc:sqlite: alias and name the driver explicitly. This mirrors the Flyway Maven plugin and flyway.properties path:

ClassicConfiguration configuration = new ClassicConfiguration();
configuration.setUrl("jdbc:sqlite:http://localhost:8080");
configuration.setDriver("io.github.magnusp.libsql.jdbc.LibsqlDriver");
Flyway flyway = new Flyway(configuration);
# flyway.properties
url=jdbc:sqlite:http://localhost:8080
driver=io.github.magnusp.libsql.jdbc.LibsqlDriver

jdbc:libsql: cannot be used with URL-based configuration at all, even with an explicit driver, because the plugin lookup fails first.

Auto-commit statements on one JDBC connection share a Hrana stream, so a connection-init statement that sets session state such as PRAGMA foreign_keys applies to the migrations that follow on that connection.

Flyway wraps clean() in a transaction, and SQLite ignores PRAGMA foreign_keys inside a transaction. Run the pragma when the DataSource hands over the connection, before Flyway begins that transaction:

# HikariCP / Spring Boot
spring.datasource.hikari.connection-init-sql=PRAGMA foreign_keys = OFF

For a programmatic DataSource, run the same statement in getConnection() before returning the connection. Do not use an AFTER_CONNECT callback for this: Flyway has already disabled auto-commit by then, so the driver starts a transaction before the pragma and SQLite ignores it.

The driver splits Flyway's PRAGMA foreign_keys = OFF; DROP TABLE ...; PRAGMA foreign_keys = ON scripts and runs every statement on the connection's stream (see #258). sqld expires an idle stream after about ten seconds and session state resets with it, so configure state that must outlive an idle period in the pool's connection initialization.

Contributing

We welcome contributions! Please see CONTRIBUTING.md for contribution guidelines and development workflow, and CODE_OF_CONDUCT.md for community standards.

Security

Please report security issues according to our Security Policy.

License

This project is licensed under the MIT License.