Skip to content

rqlite/rqlite-jdbc

Repository files navigation

rqlite JDBC Driver

This is a minimal (~130KB, zero dependencies), Type 4 JDBC driver for rqlite, a lightweight, distributed relational database built on top of SQLite.

This driver enables Java applications to interact with rqlite over HTTP, supporting standard JDBC operations like queries, updates, and batch processing in a clustered environment.

Features

  • JDBC Compliance: Supports core JDBC APIs, including Connection, Statement, PreparedStatement, and ResultSet.
  • Atomic Transactions: Executes multiple statements atomically using rqlite’s transaction=true mode via batch operations.
  • Clustered Environment Support: Configurable options for read consistency, write queuing, and timeouts to handle rqlite’s distributed nature.
  • Schema and Metadata Access: Query table metadata, primary keys, foreign keys, and indexes (see L4DriverTest).

Getting Started

Prerequisites

  • Java 11 or higher
  • rqlite server running (e.g., http://localhost:4001)

Install from Maven Central

io.rqlite:rqlite-jdbc:[version]

The driver version corresponds to the last known rqlite release the driver was tested against, followed by a build version of the driver itself.

Basic Usage

Connect to an rqlite instance and execute queries using standard JDBC APIs.

import java.sql.*;

var url = "jdbc:rqlite:http://localhost:4001";

try (Connection conn = DriverManager.getConnection(url)) {
    var stmt = conn.createStatement();
    stmt.execute("CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY, name TEXT, age INTEGER)");

    var ps = conn.prepareStatement("INSERT INTO users (name, age) VALUES (?, ?)");
    ps.setString(1, "Alice");
    ps.setInt(2, 30);
    ps.executeUpdate();

    var rs = stmt.executeQuery("SELECT * FROM users");
    while (rs.next()) {
        System.out.println("ID: " + rs.getInt("id") + ", Name: " + rs.getString("name") + ", Age: " + rs.getInt("age"));
    }
}

Batch Processing for Transactions

rqlite executes statements atomically with transaction=true. Use batch operations for multi-statement transactions.

Using Statement:

Statement stmt = conn.createStatement();
stmt.addBatch("INSERT INTO users (name, age) VALUES ('Fiona', 25)");
stmt.addBatch("INSERT INTO users (name, age) VALUES ('Sinead', 28)");
int[] updateCounts = stmt.executeBatch(); // Executes atomically

Using PreparedStatement:

PreparedStatement ps = conn.prepareStatement("INSERT INTO users (name, age) VALUES (?, ?)");
ps.setString(1, "Fiona");
ps.setInt(2, 25);
ps.addBatch();
ps.setString(1, "Sinead");
ps.setInt(2, 28);
ps.addBatch();
int[] updateCounts = ps.executeBatch(); // Executes atomically

See L4PsTest for advanced examples with various data types, streams, and LOBs.

Configuration Options

Customize the driver’s behavior via JDBC URL parameters, see L4Options. Below are the available options, their defaults, and their purposes.

These options come from rqlite's Developer Guide

Property Key Type Default Value Description
baseUrl String null The base URL of the RQLite server (e.g., http://localhost:4001).
user String null Username for RQLite server authentication.
password String null Password for RQLite server authentication.
cacert String null Path to the CA certificate for SSL/TLS connections.
insecure boolean false If true, disables SSL/TLS verification (not recommended for production).
timeoutSec long 5 Timeout for HTTP requests in seconds.
queue boolean false If true, enables queuing of requests on the RQLite server.
wait boolean true If true, waits for the request to be processed by the RQLite leader.
level L4Level L4Level.linearizable Consistency level for queries (none, weak, strong, linearizable).
linearizableTimeoutSec long 5 Timeout for linearizable consistency queries in seconds.
freshnessSec long 5 Maximum age of data for freshness-based queries in seconds.
freshnessStrict boolean false If true, enforces strict freshness for queries.

Example JDBC URL:

String url = "jdbc:rqlite:http://localhost:4001?timeoutSec=5&level=strong&freshnessSec=1";

Caveats

Memory Usage

Result sets are held in memory (mapped from rqlite’s JSON responses to JDBC ResultSet). Write queries that return small datasets to avoid memory issues.

Catalog Support

Only the main SQLite database is reported as a catalog to JDBC.

Transaction Limitations

The driver offers deferred transaction support on Connection instances due to rqlite's Transaction support conventions, which deviate from the JDBC standard.

To execute multiple SQL statements as a transaction, you have two options.

Batch statements

Populate a JDBC batch using Statement or PreparedStatement, which will get sent with transaction=true to the underlying rqlite HTTP request.

Synthetic/deferred transactions

Call setAutoCommit(false) on a Connection, and run insert, update or delete statements.

The execution of these statements will get deferred until you call commit().

This will send all statements to the database as a single batch, appending transaction=true to the underlying rqlite HTTP request.

This implies that you won't be able to inspect ResultSets, metadata or row counts after executing each statement. A dummy resultset is provided only for compatibility with JDBC semantics.

The only guarantee is that if commit() succeeds, then all deferred statements were accepted by the database.

Lastly, make sure that all statements get executed through the same connection where the transaction was initiated.

Isolation Level

Only TRANSACTION_SERIALIZABLE is supported, with linearizable read consistency by default. Setting level=weak or level=none may introduce read inconsistencies.

Type Mapping

User-defined SQL types (UDTs) are not supported. getTypeMap and setTypeMap are implemented for compliance but have no effect.

Date/Time Handling

The driver normalizes all java.sql.Date, Time, and Timestamp values to UTC before storage in rqlite, ensuring consistent round-trip behavior regardless of the JVM's default timezone.

This is necessary because rqlite normalizes date/time outputs for typed columns (DATE, DATETIME, TIMESTAMP) to ISO 8601 format with a 'Z' suffix (indicating UTC), which could otherwise cause shifts in the retrieved instant if local timezones are involved.

Setters (setDate, setTime, setTimestamp)

The input value's instant (milliseconds since UTC epoch) is converted to UTC components and stored as a string (e.g., '2025-08-23 13:00:00' for timestamps, without 'Z'). The optional Calendar parameter is ignored, as forcing UTC normalization preserves the original instant across environments.

If timezone-specific adjustments are needed, perform them in your application before creating the Date/Time/Timestamp objects.

Getters (getDate, getTime, getTimestamp)

Values are parsed assuming UTC (via Instant.parse for ISO strings with 'Z'). The returned objects hold accurate UTC milliseconds internally.

However, due to legacy java.sql types:

  • toString() on Date/Time/Timestamp formats components in the JVM's default timezone, which may show shifted values (e.g., a UTC midnight might display as the previous day in EST). This is a display artifact—the underlying instant is unchanged.
  • To ensure consistent display or interpretation, provide a Calendar set to UTC (e.g., Calendar.getInstance(TimeZone.getTimeZone("UTC"))) in getters.

Recommendations:

  • Store all dates/times in UTC to avoid complexity.
  • Test in multiple JVM timezones (e.g., via -Duser.timezone=UTC) to verify behavior.
  • If preserving original timezones is critical, store offsets or timezone names in separate columns, as rqlite/SQLite does not natively support timezone-aware types.

This approach deviates slightly from standard JDBC for timezone-agnostic databases but prioritizes reliability with rqlite's output quirks.

For examples, see L4PsTest.

Contributing

Contributions are welcome! Please submit issues or pull requests to this GitHub repository.

Requires Gradle 8.1 or later.

Create a file with the following content at ~/.gsOrgConfig.json:

{
  "orgConfigUrl": "https://raw.githubusercontent.com/rqlite/rqlite-jdbc/refs/heads/org-config/org-config.json"
}

Then run:

gradle clean build

About

Type 4 JDBC driver for rqlite

Topics

Resources

License

Stars

Watchers

Forks

Contributors 3

  •  
  •  
  •  

Languages