v0.1.0-alpha.1
Published to Maven Central as
com.gizmodata:quack-jdbc:0.1.0-alpha.1 and as a GitHub Release with
both versioned and un-versioned jars at
https://github.com/gizmodata/quack-jdbc/releases/tag/v0.1.0-alpha.1.
First public alpha release. The Quack protocol itself is in beta until
DuckDB v2.0 ships in September 2026 — this driver is pinned to
duckdb/duckdb-quack@daae4826
(2026-05-10). Don't use this in production; do use it to evaluate
DBeaver / DataGrip / Spark / direct JDBC against a remote DuckDB.
All [Unreleased] entries below shipped in this release.
Added — Release distribution
- GitHub Release jars are now attached to every tagged release by
CI, in addition to the versioned artifact going to Maven Central.
Each release gets:quack-jdbc-<version>.jar— versioned filenamequack-jdbc.jar— un-versioned filename (always the latest), so
DBeaver / DataGrip / curl users can fetch a stable URL:
https://github.com/gizmodata/quack-jdbc/releases/latest/download/quack-jdbc.jar- Matching
*-sources.jarand*-javadoc.jarin both forms SHA256SUMSfor every uploaded asset
- README badges added at the top: Maven Central version, latest
GitHub release, direct download link to the latest un-versioned jar,
link to the GitHub repo, MIT license.
Added — APPEND_REQUEST encoder + appendChunk bulk-load API
VectorCodec.encodeDataChunkWrapper/encodeDataChunk/
encodeVector/encodeFlatVectorBodyare now implemented for
the common scalar physical types: BOOLEAN, all integer family
(signed and unsigned, including HUGEINT / UHUGEINT), FLOAT, DOUBLE,
DECIMAL (width 1-38), VARCHAR / CHAR / BLOB / BIT / GEOMETRY,
DATE, TIME / TIME_NS, TIMESTAMP variants
(sec/ms/us/ns/TZ), INTERVAL, UUID. STRUCT / LIST / MAP / ARRAY
encoding still throws — opens an issue if you need them.QuackSession.appendChunk(schema, table, chunk)sends an
APPEND_REQUESTto the server with a fully-encoded DataChunk.
This is the bulk-load fast-path: it sends column-oriented binary
data directly, bypassing per-row INSERT parsing. Typical workloads
see an order-of-magnitude speed-up over
PreparedStatement.executeBatch().- 5 round-trip unit tests in
VectorCodecRoundTripTestexercise
encode → decode against an in-memory buffer (no server needed) for
primitive vectors, nullable columns, and a mixed scalar payload. - 5 integration tests in
AppendIntegrationTestexercise the
full APPEND_REQUEST flow against a live DuckDB+Quack server —
CREATE TABLE → build chunk in code →appendChunk(...)→ SELECT
to verify — covering INTEGER+VARCHAR, nullable BIGINT, all-scalar
mix (BOOLEAN/INT/BIGINT/DOUBLE/DECIMAL/DATE/TIMESTAMP/VARCHAR),
5000-row bulk, and BLOB round-trip.
Total suite: 75 tests, all green.
Changed — Bitset validity + typed CONSTANT/DICTIONARY/SEQUENCE paths
- Bitset-packed validity.
DecodedVector's {@code boolean[] validity}
is replaced with a {@code long[]} bitmap that holds one bit per row.
Wire format hasn't changed (still a byte-aligned bitmap), but the
driver-side memory footprint is now 8× smaller for the validity mask
(1 bit/row instead of 1 byte/row from a {@code boolean[]}). The
validity reader produces {@code long[]} directly via a packed
little-endian read. Validityhelper with bit-test, all-valid initializer, byte
round-trip, and set-valid/set-null helpers — covered by 7 new unit
tests pinning bit ordering and round-trips.- CONSTANT vectors now broadcast a primitive value into the right
typed primitive vector (e.g.,SELECT 42::INTEGER FROM range(1000)→
IntVec(int[])) instead of falling back toObjectVec. - DICTIONARY vectors preserve the dictionary's storage type when
projecting through the selection vector — typed in → typed out. - SEQUENCE vectors for INTEGER and BIGINT logical types
materialize directly intoIntVec/LongVec(the common
SELECT i FROM range(...)case). - 2 new integration tests in
StreamingIntegrationTestpin the typed
SEQUENCE and CONSTANT paths (total: 65 tests).
Changed — Streaming cursor + typed primitive vectors (memory rewrite)
- Streaming cursor.
QuackSession.prepare(sql)is replaced by
QuackSession.cursor(sql), which returns aQuackSession.Cursor
that holds at most one server batch in memory at a time. The
initial PREPARE_RESPONSE is parsed eagerly; subsequent
FETCH_REQUESTs fire only when the local buffer drains. Peak driver
memory for a million-row SELECT now grows with the server batch
size (default ~12 chunks ≈ a few hundred KB), not with the total
result-set row count. - Typed primitive vectors.
DecodedVectorbecomes a sealed
interface with primitive-array records (BoolVec,ByteVec,
ShortVec,IntVec,LongVec,FloatVec,DoubleVec) plus an
ObjectVecfallback. Fixed-width primitive logical types
(BOOLEAN, TINYINT..BIGINT and unsigned siblings, FLOAT, DOUBLE)
now decode directly intoint[]/long[]/double[]/etc. instead
ofList<Object>of boxed wrappers — roughly 4-8× smaller in
memory for those columns, matching the bytes-on-the-wire footprint.
Logical types whose materialized Java form is not a primitive
(DECIMAL, DATE, TIME / TIME_NS / TIME_TZ, TIMESTAMP variants, UUID,
INTERVAL, HUGEINT/UHUGEINT, ENUM, VARCHAR, BLOB, STRUCT, LIST,
ARRAY, MAP) stay inObjectVec. ResultSetgetters route through the new typed accessors
(DecodedVector.getInt,getLong,getDouble, etc.) so reading
a primitive column no longer triggers anInteger.valueOf
per row.QuackConnection.session()is nowpublicso advanced callers
can open aCursordirectly and drain chunks without going through
the JDBCResultSetsurface.- 4 new integration tests in
StreamingIntegrationTestpin the
lazy-fetch behavior and the typed-vector layout (total: 56 tests
passing).
Added — JDBC coverage parity with DuckDB's own driver
Closed the eight method-coverage gaps surfaced by a side-by-side audit
against org.duckdb.DuckDB* (full audit at /tmp/jdbc-coverage-audit.md
during development). All eight are required by at least one of
DBeaver / IntelliJ DataGrip / dbt / Spark JDBC / HikariCP.
PreparedStatement.getMetaData()— returns
{@link java.sql.ResultSetMetaData} for the prepared query by running
SELECT * FROM (<sql>) LIMIT 0with NULL-filled placeholders.
Returns {@code null} for non-SELECT statements per JDBC contract.
Required byspark.read.jdbc(...)for schema inference.PreparedStatement.getParameterMetaData()— returns a
QuackParameterMetaDatareporting the?-marker count (counted
outside single-quoted strings and double-quoted identifiers). Used
by Hibernate / Spring JDBC / DataGrip parameter inspectors.Statement.addBatch(String)/clearBatch()/executeBatch()
andPreparedStatement.addBatch()/executeBatch()— executed
as a sequential loop (no native batch protocol exists in Quack today).
ThrowsBatchUpdateExceptionon individual failures with the partial
counts array. Required bydbt seedand Sparkdf.write.jdbc(...).ResultSet.getArray(...)— wraps the decoded {@code List}
in a {@code QuackArray} (java.sql.Array) carrying the element's
logical type forgetBaseType/getBaseTypeName. Required by
DBeaver's value editor for LIST / ARRAY columns.ResultSet.getBlob(...)— wraps the decoded {@code byte[]} in a
{@code QuackBlob} (java.sql.Blob). Required by DBeaver's BLOB value
editor.Connection.createArrayOf/createStruct— return opaque
QuackArray/QuackStructwrappers usable insetObject(_, Array)
/setObject(_, Struct). Used by dbt IN-list macros and adapter
frameworks.Connection.setCatalog/setSchema— now emit
USE "catalog"."schema"instead of silently storing a field. DBeaver
catalog-navigator switching actually changes context server-side.Connection.isValid(int)— now actually runsSELECT 1to detect
dead server-side connections instead of only checking the local
closedflag. Required by HikariCP / pgbouncer-style pool
health-check semantics.Connection.setTypeMap(Map)— silently acceptsnulland empty
maps (the call HikariCP makes during eviction), throws only for
non-empty mappings.Statement.cancel()— degraded fromSQLFeatureNotSupportedException
to a best-effort no-op so DBeaver / DataGrip query-timeout buttons
don't crash the UI. Real protocol cancel will follow when Quack
surfaces it.QuackHttpTransportnow iterates every address returned by
InetAddress.getAllByName(host)instead of relying on JDK
HttpClient's first-address behavior. Hosts likelocalhostthat
resolve to both127.0.0.1and::1now succeed against a server
bound to either family — previously aConnectExceptionon the first
address (IPv4 by default on macOS) aborted the whole request even
though an IPv6 listener was reachable.- Error messages no longer say
Quack HTTP request failed: nullwhen
the cause has no message; the exception class name is used as a
fallback. The exhausted-addresses error names every address that was
tried, including the underlying failure detail. - First cut of the JDBC driver for DuckDB's Quack remote protocol.
BinaryReader/BinaryWriterfor DuckDB's BinarySerializer wire format
(little-endian uint16 field ids, ULEB128/SLEB128, fixed-width primitives,
length-prefixed strings/blobs/lists, nested objects terminated by
FIELD_END = 0xFFFF).- Logical type model and codec covering BOOLEAN, integer family
(TINYINT…HUGEINT including unsigned), FLOAT/DOUBLE, DECIMAL, VARCHAR/CHAR,
BLOB/BIT/GEOMETRY, DATE, TIME / TIME_NS / TIME_TZ, TIMESTAMP variants
(SEC / MS / default µs / NS / TZ), INTERVAL, UUID, ENUM, STRUCT, LIST,
MAP, ARRAY, plus allExtraTypeInfovariants. DataChunkdecoder supporting FLAT, CONSTANT, DICTIONARY,
and SEQUENCE vector encodings with validity bitmaps. FSST is not
yet supported.- Quack protocol message records and
MessageCodecforCONNECTION_*,
PREPARE_*,FETCH_*,APPEND_REQUEST,SUCCESS_RESPONSE,
DISCONNECT_MESSAGE, andERROR_RESPONSE. QuackHttpTransportoverjava.net.http.HttpClient(JDK 17+).- JDBC URL parser accepting
jdbc:quack://host[:port][/database][?token=…&tls=…]. QuackDriver(auto-registered viaMETA-INF/services),QuackConnection,
QuackStatement,QuackPreparedStatement(client-side?interpolation),
QuackResultSet,QuackResultSetMetaData.QuackDatabaseMetaDatamodeled directly on DuckDB's own JDBC driver so
DBeaver and other tools that introspect viagetTables/getColumns/
getPrimaryKeys/getImportedKeys/getExportedKeys/getIndexInfo/
getTypeInfo/getFunctionssee the same shape they would from a
native DuckDB connection.- JUnit 5 integration suite that spawns a real
duckdb -unsignedprocess,
installs the Quack extension fromcore_nightly, callsquack_serveon
a random local port, and exercises the driver end-to-end (connect,
CRUD, multi-chunk fetch, scalar type round-trips, DatabaseMetaData,
bad-token auth, concurrent connections). - Unit test coverage for the BinarySerializer round-trip, URI parsing,
and message encode/decode. - DuckDB CLI: 1.5.2+ (tested with 1.5.2)
- Quack extension:
duckdb/duckdb-quack@daae4826f57986fbb6cc2116316f89c673814b23
(2026-05-10, currentmain— no release tags exist yet at the time of
writing; will be retargeted as the protocol stabilizes for DuckDB 2.0
in September 2026) - The Quack protocol is beta; breaking changes are expected before DuckDB 2.0.
PreparedStatementparameter binding uses client-side literal
substitution — the protocol'sPREPARE_REQUESTdoes not (yet) carry
bind parameters.APPEND_REQUEST(vector encoding) is decoder-complete but not yet
encoder-complete; the driver does not yet expose the append fast-path.- FSST-compressed vectors and the TIME WITH TIME ZONE wall-clock decode
are not yet supported. - Nested types (STRUCT/LIST/MAP/ARRAY) decode to plain Java collections;
fulljava.sql.Array/java.sql.Structwrapping is on the roadmap.
Plus 15 new integration tests exercising every fix end-to-end against
a live DuckDB+Quack server (52 tests total, all green).