Groundwork for running the real clojure.jdbc on a java.sql shim - #9
Merged
Conversation
added 3 commits
August 11, 2026 14:44
First step towards running the real clojure.jdbc on jolt instead of reimplementing its API here. jdbc.constants maps clojure.jdbc's keyword options onto java.sql static fields, and those were the only thing stopping its namespaces from compiling: with ResultSet, Connection and Statement registered through __register-class-statics!, every clojure.jdbc namespace loads under jolt and jdbc.core/prepared-statement resolves from the dependency. The values match the JVM's, since callers pass these through. :serializable has to reach setTransactionIsolation as 8 either way. Nothing requires this namespace yet, so it is inert and the existing suite is unaffected. Wiring it to db.sqlite and db.pg, and routing connection creation away from DriverManager, comes next.
Groundwork for the java.sql shim, kept separate so it can be reviewed on its own.
A JDBC ResultSet is read by column index, and ResultSetMetaData reports labels by
index, but both drivers returned keyword-keyed maps with column order already
lost. query-raw and all-raw now return {:labels [...] :rows [[v ...]]} and query
and all build their maps from that, so the map-shaped API is unchanged while an
indexed reader has something to work with.
The ? to $N rewriter also moves from jdbc.core into db.pg, which is where it
belongs: it is a postgres concern, and db.pg/run now applies it so every caller
gets it rather than each one remembering to. That also keeps it available once
jdbc.core goes away, since the shim needs it. Its lexer table moved with it and
still runs in the sqlite-only build, which is why the test namespace now requires
db.pg directly. Loading db.pg has never needed libpq present, only calling it does.
The object surface clojure.jdbc drives, over db.sqlite and db.pg: Connection, Statement, PreparedStatement, ResultSet, ResultSetMetaData, DatabaseMetaData and Savepoint, as host tagged-tables with their methods registered through __register-class-methods!. __register-class! reports the java.sql class names so that clojure.jdbc's protocols, which are extended to java.sql.Connection and friends, dispatch on these values at all, and __register-instance-check! answers instance? for them. db.jdbc is the entry point. It fixes load order, since clojure.jdbc resolves the java.sql constants when its namespaces compile, and then re-extends IConnection so a dbspec builds a connection over the native drivers instead of reaching for DriverManager, which has nothing to load here. Extending after jdbc.impl is what makes ours win. Two things worth naming. Generated keys are RETURNING underneath, because neither driver has a JDBC generated-keys channel: :all or true asks for the whole row, which is what postgres' own driver gives, and a sequence of names asks for those columns. An empty ResultSet when nothing was requested is deliberate, since that is what makes insert! fall back to the update count the way it does on a driver without generated keys. And a ResultSet here is a cursor over rows the driver has already materialised, so a fetch that streams on the JVM is eager on this shim. Real clojure.jdbc already runs execute!, fetch and fetch-one on sqlite through this. Nothing in the repo requires it yet, so the existing suite is untouched.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Towards running the published clojure.jdbc on jolt instead of reimplementing its API here. This is the groundwork only. It does not switch anything over yet, and everything in it is inert or internal, so the existing suite is untouched at 91 checks.
The three commits are separable on purpose.
The java.sql constants.
jdbc.constantsmaps clojure.jdbc's keyword options ontojava.sqlstatic fields, and those were the only thing stopping its namespaces from compiling. WithResultSet,ConnectionandStatementregistered, every clojure.jdbc namespace loads under jolt. The values match the JVM's, since callers pass them straight through::serializablehas to reachsetTransactionIsolationas 8 either way.Ordered row access, and the
?rewriter moves todb.pg. A JDBCResultSetis read by column index andResultSetMetaDatareports labels by index, but both drivers returned keyword maps with column order already lost.query-rawandall-rawnow return{:labels [...] :rows [[v ...]]}, andquery/allbuild their maps from that, so the map-shaped API is unchanged. The?to$Nrewriter also moves intodb.pg, which is where it belongs, anddb.pg/runapplies it so every caller gets it rather than each one remembering to. Its lexer table moved with it and still runs in the sqlite-only build.The shim itself.
Connection,Statement,PreparedStatement,ResultSet,ResultSetMetaData,DatabaseMetaDataandSavepointoverdb.sqliteanddb.pg, as host tagged-tables.db.jdbcis the entry point: it fixes load order, since clojure.jdbc resolves the constants at compile time, and re-extendsIConnectionso a dbspec builds a connection over the native drivers instead of reaching forDriverManager, which has nothing to load here.Two design points worth naming. Generated keys are
RETURNINGunderneath, because neither driver has a JDBC generated-keys channel::all/trueasks for the whole row, which is what postgres' own driver gives, and a sequence of names asks for those columns. An empty ResultSet when nothing was requested is deliberate, since that is what makesinsert!fall back to the update count the way it does on a driver without generated keys. And a ResultSet here is a cursor over rows the driver has already materialised, so afetchthat streams on the JVM is eager on this shim.What already works
With jolt-lang/jolt#585 (merged), the published clojure.jdbc runs unmodified against native sqlite through this shim:
Generated keys, transactions, nested savepoints via
SAVEPOINT, and column order preserved inas-rows?.What is deliberately not here
clj/jdbc/core.cljstill ships, and while it does it shadows the dependency on the classpath, sodb.jdbcresolves to our copy rather than to clojure.jdbc. Its docstring says so. Removing it is the switchover, and that belongs with the test-suite migration rather than buried here.deps.ednalso does not declare clojure.jdbc orio.github.jolt-lang/timeyet; the latter is needed becausejdbc.util/lower-casecalls(Locale/US).The switchover additionally needs a jolt release carrying jolt-lang/jolt#585, since CI installs jolt from its latest release and the shim depends on all three fixes in it.