DB
Drivers & Protocol

JDBC driver

Build and use the experimental Java 17 NYXDB JDBC driver, including URLs, typed v2 results, metadata, limitations, retries, and version compatibility.

The experimental JDBC 4 driver lives in drivers/jdbc. It supports forward-only one-shot results, append-shaped streaming row results, statements and prepared statements, a DataSource, catalog metadata, and negotiated wire v2 typed results for one-shot queries.

The driver is not published to a public Maven repository and does not provide a GA compatibility contract. Build it from the same engine revision you deploy. Append-shaped streaming and one-shot wire v2 are functional but experimental; maintained snapshot/delta streams are not yet a JDBC surface. This revision also has no JDBC property or handshake for operator OP_AUTH, so queries receive authentication required from a server launched with --require-auth. Do not disable that gate on an untrusted listener merely to accommodate the driver.

Build and install locally

The driver requires Java 17 and Maven.

git clone https://github.com/NYXL-io/db.git
cd db/drivers/jdbc
mvn -q test
mvn -q install

mvn install adds the current snapshot to your local Maven repository:

<dependency>
  <groupId>io.nyxdb</groupId>
  <artifactId>nyxdb-jdbc</artifactId>
  <version>0.1.0-SNAPSHOT</version>
</dependency>

For a direct classpath, use target/nyxdb-jdbc-0.1.0-SNAPSHOT.jar. The JAR registers io.nyxdb.jdbc.NyxDriver through Java's service-provider mechanism.

Connection URLs

Preferred URL:

jdbc:nyxdb://<host>:<port>/<database>?socketTimeoutMillis=<milliseconds>

Examples:

jdbc:nyxdb://127.0.0.1:7510/default
jdbc:nyxdb://127.0.0.1:7777/default?socketTimeoutMillis=5000

jdbc:nyx:// is accepted as a legacy prefix. Omitted values default to host 127.0.0.1, port 7510, and a 30-second socket timeout. The database path is carried by the JDBC surface; verify namespace behavior against the current server before depending on it.

Execute a query

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

try (Connection connection = DriverManager.getConnection(
        "jdbc:nyxdb://127.0.0.1:7777/default?socketTimeoutMillis=5000");
     PreparedStatement statement = connection.prepareStatement(
        "SELECT market, amount FROM trades WHERE amount > ? ORDER BY market")) {
  statement.setLong(1, 1000);
  try (ResultSet rows = statement.executeQuery()) {
    while (rows.next()) {
      System.out.printf("%s %s%n", rows.getString("market"), rows.getObject("amount"));
    }
  }
}

Prepared parameters are rendered through the driver's current SQL-literal layer. This is not a server-side multi-statement transaction boundary.

Typed results and metadata

The transport negotiates lazily on the first executeV2 path used by Statement.execute/executeQuery, trying handshake schema 2, then schema 1, then plain TSV on fresh sockets. After that setup it reuses one persistent query connection. A schema-2 path uses OP_QUERY_META and receives one strict typed-result envelope containing exact elapsed time, numeric engine query identity, cache state, server identity, and one nested columnar frame. A v2 result preserves the full type descriptor:

  • integer and floating-point widths;
  • decimal precision, scale, and exact mantissa;
  • datetime64 precision and timezone;
  • nullability and low-cardinality dictionaries; and
  • vector(N) element type and dimension.

ResultSetMetaData maps those descriptors to Java SQL types, getBigDecimal preserves declared scale, temporal values retain their instant, and vectors are returned as primitive arrays. If negotiation is explicitly unsupported, the driver can continue in v1 TSV mode, where metadata and value fidelity are more limited.

At the transport layer, NyxQueryResult.metadata() retains elapsedUs, queryId, cache state, and server for both the schema-2 typed path and its text fallback. Java stores the unsigned 64-bit fields in long; use an unsigned rendering/conversion when a value is above Long.MAX_VALUE. The standard Statement/ResultSet facade currently consumes this carrier but does not expose an engine-query-ID or wire-cancel API, so applications must not claim that Statement.cancel() targets OP_CANCEL_REQUEST.

For the schema-2 executeV2/Statement.execute path, unsupported or no-row typed shapes—including a write acknowledgement reached through that path—fall back to #meta plus TSV from the original captured execution. Encoding fallback never re-executes the statement. executeUpdate/executeLargeUpdate use the plain OP_QUERY transport and therefore receive a text acknowledgement without the metadata carrier. Both behaviors are distinct from the driver's transport retry below, which may deliberately start one new execution for a conservatively classified read-only statement after connection loss.

On a schema-1 binary connection the driver chooses bare OP_QUERY to preserve typed rows, so server timing and numeric query identity are unavailable on that fallback. The protocol defines LZ4, but this JDBC revision requests binary results plus typed metadata and does not enable LZ4.

The metadata surface can enumerate catalogs, schemas, tables, and columns for tools such as DataGrip. It is pre-GA and may grow append-only fields.

Stream append-shaped rows

The driver recognizes the canonical statement-prefix form and returns a long-lived, forward-only ResultSet for append-shaped streams:

try (Connection connection = DriverManager.getConnection(
        "jdbc:nyxdb://127.0.0.1:7777/default");
     var statement = connection.createStatement();
     ResultSet stream = statement.executeQuery(
        "STREAM SELECT id, market, amount FROM trades")) {
  while (stream.next()) {
    System.out.printf("%d %s %d%n",
        stream.getLong("id"),
        stream.getString("market"),
        stream.getLong("amount"));
  }
}

The stream socket uses v1 text row frames; ResultSet.next() blocks until the next initial or live row arrives. The owning Connection tracks stream sockets while they are opening and after they become active. Closing the result set, its statement, or the connection closes the dedicated socket; Connection.abort() uses the same teardown path. Connection shutdown also releases a thread blocked in ResultSet.next() with a SQLException. Resource closure is idempotent, but applications should still use try-with-resources so ownership remains explicit.

socketTimeoutMillis bounds connection and initial stream setup. After the stream header arrives, the driver disables the socket read timeout: an idle ResultSet.next() can wait indefinitely for a row or terminal condition. An application that needs its own deadline must close the result set, statement, or connection from another control path.

EOF, socket failure, server shutdown, source drop, and a terminal engine error end that result set. The driver does not claim cursor continuity on a replacement connection. If the application resubscribes, treat the initial row replay as a new replacement boundary before accepting live rows.

The current JDBC stream reader accepts append-row frames. Do not use it for a keyed, aggregate, or UNION stream: those result shapes use snapshot and positional-delta statuses that the driver does not yet maintain. Use nyxsql, the web console, or a protocol client that implements those statuses.

Transaction and result limitations

  • DatabaseMetaData.supportsTransactions() is false and the reported isolation level is TRANSACTION_NONE.
  • The engine does not provide multi-statement BEGIN/COMMIT/rollback or an MVCC snapshot-isolation contract.
  • Result sets are forward-only and read-only.
  • Batch updates, stored procedures, generated keys, and many optional JDBC methods are not implemented.
  • Streaming result sets are experimental, blocking, forward-only, v1-text only, and not resumable across connections. They currently support append-shaped row streams, not maintained snapshot/delta results.

Failure and retry behavior

Connection and protocol failures surface as SQLException. Use socketTimeoutMillis plus an application-level overall deadline for bounded operations; the live-row wait exception is described above. The transport makes at most one reconnect/retry, and only for one conservatively classified read-only statement: SELECT, SHOW, DESCRIBE/DESC, or EXPLAIN with a recognized statement target. Classification is case-insensitive, skips leading SQL trivia, requires complete keyword boundaries, and rejects multiple statements.

  • Retry a connection attempt with bounded backoff and jitter.
  • A failed read may be repeated only when the caller accepts a fresh result.
  • DDL, DML, topology commands, unknown statements, and malformed statements are never replayed by the driver.
  • A transport failure after one of those requests may have reached the server. The driver throws SQLTransientConnectionException with SQLSTATE 08007 and the stable message: “NYXDB statement outcome is ambiguous; the driver did not retry it. Reconcile server state before retrying.”
  • A refusal while opening the socket remains a plain “Failed to connect to NYXDB …” connection failure; no request was sent, so it is not labeled as an ambiguous statement outcome.
  • Do not retry deterministic SQLException diagnostics as transport outages.
  • Close failed connections and result sets; do not reuse a connection after a framing or negotiation error.

Upgrade checklist

  1. Pin the engine and JDBC driver source SHAs.
  2. Run mvn test, including golden wire-v2 frames.
  3. Exercise v1 fallback and v2 typed results against the target server.
  4. Verify decimal, temporal, nullable, and vector columns used by the application.
  5. Test connection refusal, timeout, mid-response close, ambiguous DML, and append-stream cleanup before promotion.

See Wire protocol for the current experimental format. See Query identity, cancellation, and retries for the distinction between encoding fallback, transport retry, and cooperative cancellation.

On this page