Liking cljdoc? Tell your friends :D

psql-clj

Clojars Project cljdoc test

PostgreSQL helpers for Clojure: environment- and .pgpass-aware connection specs, HikariCP pooling, and next.jdbc type coercion for JSON/JSONB, arrays, inet, and PostGIS geometry.

Stack

Clojure PostgreSQL PostGIS next.jdbc HikariCP Leiningen

Installation

Leiningen / Boot:

[net.clojars.savya/psql-clj "1.0.0"]

deps.edn:

net.clojars.savya/psql-clj {:mvn/version "1.0.0"}

Connecting

spec and pool read PGHOST, PGPORT, PGUSER, PGDATABASE and PGPASSWORD from the environment and fall back to ~/.pgpass for the password. Function arguments override anything from the environment.

(require '[psql.core :as pg]
         '[next.jdbc :as jdbc])

;; A plain db-spec, suitable for any next.jdbc call.
(def db (pg/spec))
(jdbc/execute! db ["SELECT 1 AS one"])

;; A HikariCP-pooled datasource.
(def pooled (pg/pool :host "db1.example.com"
                     :user "myaccount"
                     :dbname "anotherdb"
                     :password "foobar"
                     :hikari {:read-only true}))
(jdbc/execute! pooled ["SELECT 'hello from db'"])
(pg/close! pooled)

Delay creation so connection parameters are not resolved (and the pool is not opened) at load time:

(def db (delay (pg/pool)))
(jdbc/execute! @db ["SELECT 1"])

spec resolves its map as follows:

  1. :dbtype defaults to "postgresql"; the current OS username seeds :user and :dbname (as psql does).
  2. PGHOST / PGPORT / PGUSER / PGDATABASE override :host / :port / :user / :dbname.
  3. Explicit spec arguments override everything above.
  4. The password is taken from an explicit :password, then PGPASSWORD, then a ~/.pgpass match.

Automatic type conversion

Native Clojure maps, vectors and sequences are accepted as parameters; the target SQL type reported by PostgreSQL decides the conversion.

(jdbc/execute! db ["SELECT ?::int[]  AS arr" [1 2 3 4]])
;; => [{:arr [1 2 3 4]}]
(jdbc/execute! db ["SELECT ?::json   AS obj" {"foo" "bar"}])
;; => [{:obj {"foo" "bar"}}]
(jdbc/execute! db ["SELECT ?::timestamptz AS epoch" 1])
;; => [{:epoch #inst "1970-01-01T00:00:00.001-00:00"}]
  • Mapsjson/jsonb columns accept any map; geometry columns accept GeoJSON-like maps. Extend with (defmethod psql.types/map->parameter :mytype [m _] ...).
  • Vectors — array columns (int[], text[], ...) accept vectors; inet accepts [192 168 1 11]. Extend with (defmethod psql.types/vec->parameter :mytype [v _] ...).
  • Other seqables (lists, lazy seqs) are treated like vectors.
  • Numbers bound to timestamp/timestamptz become java.sql.Timestamp. Extend with (defmethod psql.types/num->parameter :mytype [n _] ...).

On the way out, json/jsonb parse to Clojure data, arrays become vectors, and PostGIS geometry is returned as GeoJSON.

PostGIS geometry

psql.spatial builds net.postgis.jdbc.geometry.* objects. They can be used directly as query parameters and are read back as GeoJSON maps.

(require '[psql.spatial :as st])

(st/point 1 2)                           ;=> POINT(1 2)
(st/point [1 2])                         ;=> POINT(1 2)
(st/multi-point [[1 2] [3 4]])           ;=> MULTIPOINT(1 2,3 4)
(st/line-string [[1 2] [3 4]])           ;=> LINESTRING(1 2,3 4)
(st/polygon [[[1 2] [3 4] [5 6] [1 2]]]) ;=> POLYGON((1 2,3 4,5 6,1 2))

(jdbc/execute! db ["INSERT INTO shapes (geom) VALUES (?)" (st/point [1 2])])
(jdbc/execute-one! db ["SELECT geom FROM shapes LIMIT 1"])
;; => {:shapes/geom {:type :Point :coordinates [1.0 2.0]}}

PostgreSQL geometric types

psql.core constructs the built-in org.postgresql.geometric.* types:

(pg/point 1 2)              ;=> (1.0,2.0)
(pg/box [1 2] [3 4])        ;=> (1.0,2.0),(3.0,4.0)
(pg/circle [25 30] 5)       ;=> <(25.0,30.0),5.0>
(pg/line (pg/point 1 2) (pg/point 3 4))
(pg/lseg [1 2] [10 20])
(pg/path [[1 2] [10 20] [50 100]] true)
(pg/polygon [[1 2] [3 4] [5 6]])

Development

lein check          # compile every namespace (0 reflection warnings)
lein test           # unit tests (no database needed)
lein test :integration   # round-trips against a live PostgreSQL+PostGIS
lein all test       # unit tests across Clojure 1.10 / 1.11 / 1.12

The integration suite reads the standard PG* variables. A quick local database:

docker run -d -e POSTGRES_PASSWORD=postgres -e POSTGRES_DB=psql_clj_test \
  -p 5432:5432 postgis/postgis:16-3.4
PGHOST=localhost PGUSER=postgres PGPASSWORD=postgres PGDATABASE=psql_clj_test \
  lein test :integration

License

Copyright © 2014, Remod Oy. All rights reserved.

Maintenance fork (2026) by Savyasachi. Original project: https://github.com/remodoy/clj-postgresql.

Distributed under the BSD 2-Clause License. See LICENSE.

Can you improve this documentation? These fine people already did:
Juho Juopperi, Savyasachi, Søren Sjørup, Phil Jackson, Wulf, TKffTK, Prof Gra & Dan
Edit on GitHub

cljdoc builds & hosts documentation for Clojure/Script libraries

Keyboard shortcuts
Ctrl+kJump to recent docs
Move to previous article
Move to next article
Ctrl+/Jump to the search field
× close