Statically link postgres calls for tree shaking - #10
Conversation
Replace jdbc.core runtime require/resolve dispatch with a static db.pg alias. This gives Jolt a sound AOT dependency edge while optional libpq symbols remain lazily resolved.
Adopted from #10 by sundbp, extended to the java.sql shim and resolved against main, which had moved underneath it. jdbc.core reached the postgres driver through (resolve (symbol "db.pg" n)) behind a runtime require. A reachable call to resolve stops jolt from building a sound whole-program call graph, so jolt build --tree-shake skipped shaking entirely for any program that loaded jdbc.core, sqlite-only ones included. A runtime require is also the wrong edge for an AOT binary, which has no source roots to load db.pg from later. The reason that indirection existed was that a compile-time db.pg/foo reference used to be read as a host class. That is no longer so, and the namespace alias works, so both callers now require db.pg statically and call pg/connect, pg/close, pg/exec, pg/all and pg/all-raw directly. db.jdbc-shim had grown its own copy of the same pgfn trick, which matters more than the jdbc.core one: jdbc.core is on its way out when the switchover lands, while the shim is what stays. Fixing only the file in the PR would have carried the defect into the new architecture. A sqlite-only app still does not need libpq. Loading db.pg only declares the bindings; ffi/defcfn resolves a symbol on first call. Checked against a project whose deps.edn points :jolt/native at a libpq that does not exist: it builds, runs and returns rows. Verified the actual claim rather than the diff. Against main, jolt build --tree-shake reports "tree-shake skipped (reachable code resolves vars at runtime)". With this, it reports "tree-shake kept 336 of 764 defs (core 237/659)" and the resulting binary runs. Co-authored-by: Yogthos <yogthos@gmail.com> Co-authored-by: sundbp <sundbp@users.noreply.github.com>
|
Adopted in #11, now merged as 9955782. Thanks for this, and for the diagnosis — the tree-shake reasoning was the useful part and it was right. Two things meant landing it as a separate commit rather than merging this one directly. main had moved underneath it: The more substantive one is that I also checked the two claims directly rather than the diff, since both are build-time properties no unit test covers. Before, on main, You are credited as co-author on the commit. Closing this in favour of #11. |
Problem
jdbc.coredynamically loaded the PostgreSQL driver and resolved its functions by name:A reachable call to clojure.core/resolve prevents Jolt from constructing a sound whole-program call graph. As a result, jolt build --tree-shake skipped tree shaking for any application that included jdbc.core, including SQLite-only applications.
Runtime require is also unsuitable as the only dependency edge for an AOT binary: Jolt assembles the transitive static require graph at build time, and the resulting executable has no source roots from which to load db.pg later.
Change
This gives Jolt a static dependency edge to the PostgreSQL implementation and allows tree shaking to proceed.
SQLite applications still do not require libpq at runtime. The native library remains optional, and Jolt's FFI bindings resolve libpq symbols lazily when a PostgreSQL operation is first invoked.
Why there are no test changes
The public JDBC API and its runtime behavior are unchanged; this is a build-time reachability and linking change.
The existing suite already exercises:
A new unit test would either duplicate those behavioral tests or assert implementation details such as the absence of resolve. The relevant regression can only be demonstrated meaningfully by running an actual tree-shaken build, so verification uses jolt build --tree-shake rather than a source-text assertion.
Verification