Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 6 additions & 10 deletions clj/db/jdbc_shim.clj
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
implemented; anything else is deliberately absent so a gap shows up as a missing
method rather than as a silently wrong answer."
(:require [clojure.string :as str]
[db.sqlite :as sqlite]))
[db.sqlite :as sqlite]
[db.pg :as pg]))

;; --- java.sql constants ------------------------------------------------------
;; jdbc.constants maps its keyword options onto these, so they have to read as the
Expand Down Expand Up @@ -75,10 +76,6 @@
(defmacro ^:private sql-try [& body]
`(try ~@body (catch Exception e# (as-sql-error e#))))

;; db.pg is required lazily, only for a postgres connection, so a sqlite-only app
;; never needs libpq. Resolve its fns at runtime for the same reason jdbc.core did.
(defn- pgfn [n] (deref (resolve (symbol "db.pg" n))))

;; --- driver-facing operations ------------------------------------------------
(defn- vendor [conn] (tget conn :vendor))
(defn- handle [conn] (tget conn :handle))
Expand All @@ -89,7 +86,7 @@
(sql-try
(case (vendor conn)
:sqlite (sqlite/query-raw (handle conn) sql params)
:postgresql ((pgfn "all-raw") (handle conn) sql params))))
:postgresql (pg/all-raw (handle conn) sql params))))

(defn- run-update
"Execute `sql` and return the number of rows it affected."
Expand All @@ -98,7 +95,7 @@
(case (vendor conn)
:sqlite (do (sqlite/query-raw (handle conn) sql params)
(sqlite/changes (handle conn)))
:postgresql ((pgfn "exec") (handle conn) sql params))))
:postgresql (pg/exec (handle conn) sql params))))

;; --- java.sql.ResultSetMetaData ----------------------------------------------
(defn- make-rsmeta [labels]
Expand Down Expand Up @@ -380,9 +377,8 @@
(make-connection :sqlite h (fn [] (sqlite/close h)))))

(defn- pg-connection [uri]
(require '[db.pg])
(let [h ((pgfn "connect") uri)]
(make-connection :postgresql h (fn [] ((pgfn "close") h)))))
(let [h (pg/connect uri)]
(make-connection :postgresql h (fn [] (pg/close h)))))

(defn- pg-uri [{:keys [subname host port user password dbname] :as spec}]
(let [;; subname is JDBC's //host:port/db
Expand Down
11 changes: 7 additions & 4 deletions clj/db/pg.clj
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
(ns db.pg
"PostgreSQL driver for jolt, binding the system libpq through jolt.ffi. Exposes
the surface jdbc.core needs: connect / close / exec / all (rows as keyword-keyed
maps, numeric columns coerced to jolt numbers). Loaded lazily by jdbc.core, so a
sqlite-only app never needs libpq present."
maps, numeric columns coerced to jolt numbers). Required statically by its
callers; a sqlite-only app still never needs libpq present, because loading this
namespace only declares the bindings and libpq is not touched until one is
called."
(:require [jolt.ffi :as ffi]
[clojure.string :as str]))

;; libpq is declared in deps.edn (:jolt/native, :optional) and loaded by jolt at
;; startup when present; jdbc.core only requires this namespace for a postgres
;; connection, so a sqlite-only app never needs libpq.
;; startup when present. Its absence is only a problem for a program that actually
;; opens a postgres connection: ffi/defcfn resolves a symbol on first call, so this
;; namespace loads either way.

(ffi/defcfn PQconnectdb "PQconnectdb" [:string] :pointer)
(ffi/defcfn PQstatus "PQstatus" [:pointer] :int)
Expand Down
35 changes: 14 additions & 21 deletions clj/jdbc/core.clj
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@
(jdbc/insert! conn :p {:name \"ada\"})
(jdbc/fetch conn [\"select * from p where name = ?\" \"ada\"]))"
(:require [clojure.string :as str]
[db.sqlite :as sqlite]))
[db.sqlite :as sqlite]
[db.pg :as pg]))

;;; dbspec

Expand Down Expand Up @@ -48,10 +49,6 @@

;;; connection

;; the postgres driver is reached through pgfn, defined with the other pg helpers
;; below; connection calls it for the :postgresql branch.
(declare pgfn)

(defn connection
"Open a connection. spec is a uri string (\"sqlite:path\", a bare sqlite
path, or \"postgres://user:pass@host:port/db\") or a dbspec map with
Expand All @@ -69,15 +66,15 @@
:rollback (atom false)
:close (fn [] (sqlite/close h))})
"postgresql"
;; db.pg (and libpq) load lazily — only when a postgres connection is made,
;; so a sqlite-only app never needs libpq present.
(do (require '[db.pg])
(let [h ((pgfn "connect") (pg-uri spec))]
{:vendor :postgresql
:handle h
:depth (atom 0)
:rollback (atom false)
:close (fn [] ((pgfn "close") h))})))))
;; db.pg is required statically so an AOT build has a dependency edge to the
;; postgres implementation. A sqlite-only app still does not need libpq: the
;; FFI bindings resolve its symbols on first call, not on load.
(let [h (pg/connect (pg-uri spec))]
{:vendor :postgresql
:handle h
:depth (atom 0)
:rollback (atom false)
:close (fn [] (pg/close h))}))))

;;; queries

Expand All @@ -90,12 +87,8 @@
(defn- sqlite-eval [conn sql params]
(sqlite/query (:handle conn) sql params))

;; db.pg is required lazily (only for a postgres connection), so resolve its fns
;; at runtime — a compile-time db.pg/foo reference would be read as a host class.
(defn- pgfn [n] (deref (resolve (symbol "db.pg" n))))

(defn- pg-eval [conn sql params]
((pgfn "exec") (:handle conn) sql params))
(pg/exec (:handle conn) sql params))

(defn fetch
"Run a query (string or sqlvec), return a vector of keyword-keyed row maps."
Expand All @@ -104,7 +97,7 @@
(let [[sql params] (sqlvec q)
rows (case (:vendor conn)
:sqlite (sqlite-eval conn sql params)
:postgresql ((pgfn "all") (:handle conn) sql params))]
:postgresql (pg/all (:handle conn) sql params))]
(if-let [n (:max-rows opts)] (vec (take n rows)) rows))))

(defn fetch-one
Expand All @@ -128,7 +121,7 @@
[conn]
(case (:vendor conn)
:sqlite (sqlite/last-insert-rowid (:handle conn))
:postgresql (:id (first ((pgfn "all") (:handle conn) "select lastval() as id" [])))))
:postgresql (:id (first (pg/all (:handle conn) "select lastval() as id" [])))))

;;; insert! / update! / delete! — the clojure.jdbc convenience surface

Expand Down
Loading