Database
The gungnir.database
namespace is responsible for managing the database connection. By default it uses the HikariCP connection pool.
Establishing a connection
Use the gungnir.database/make-datasource!
function to establish a database connection. Generally you’d want your system state manager to manage this. E.g. Integrant, Component, Mount.
This function accepts any of the following arguments:
JDBC_DATABASE_URL
- The standard format used to create a database connection in Java.DATABASE_URL
- The universal format used to create a database connection. Used by services such as Heroku or Render.HikariCP
configuration - A HikariCP configuration map. Optionally can also be given as a second argument, where the first argument is either aJDBC_DATABASE_URL
orDATABASE_URL
.
(gungnir.database/make-datasource!
"jdbc:postgresql://localhost:5432/postgres?user=postgres&password=postgres")
(gungnir.database/make-datasource!
"postgres://postgres:postgres@localhost:5432/postgres")
(gungnir.database/make-datasource!
{:adapter "postgresql"
:username "postgres"
:password "postgres"
:database-name "postgres"
:server-name "localhost"
:port-number 5432})
Once established, the datasource will be stored in gungnir.database/*datasource*
. If you’re using the Gungnir query API you won’t have to access this yourself.
Setting a custom connection pool
A custom connection pool can also be set using the gungnir.database/set-datasource!
function.
Exception handling
When JDBC runs into an error it will raise an exception. This isn’t something that we want. Instead we would like to add errors to our changeset. This can be done using the gungnir.database/exception->map
multimethod which takes a SQLException and matches by the Postgres error. It should then return a map with a field key, and a keyword representing the error.
In this example we catch the "23505"
Postgres error.
This does a few things:
- Parse the
SQLException
and find the related sql key - Convert the sql key to a valid model record key
- Apply the
gungnir.model/format-error
multimethod in case of custom error messages - Return
{record-key [error-message]}
(which by default will be{record-key [:duplicate-key]}
)
(defmethod exception->map "23505" [^SQLException e]
(let [error (.getMessage e)
sql-key (remove-quotes (re-find #"\".*\"" error))
record-key (sql-key->keyword sql-key)]
{record-key [(gungnir.model/format-error record-key :duplicate-key)]}))
In the event that an SQLException does not have an exception->map
implementation, it will return {:unknown [sql-exception-code]}
. The next step is to open an issue or create a pull request to Gungnir. You can implement the exception->map
clause yourself as well.
Multiple datasources
By default Gungnir has a global datasource. If you need to access multiple datasources we can use the gungnir.factory
namespace. A local datasource will be bound to a specific namespace that you create. Inside of this namespace we will call gungnir.factory/make-datasource-map!
and bind the resulting functions that are returned.
(ns my.datasource
(:require
[gungnir.factory]))
(def datasource-opts
{:adapter "postgresql"
:username "postgres"
:password "postgres"
:database-name "postgres"
:server-name "localhost"
:port-number 5432})
(let [datasource-map (gungnir.factory/make-datasource-map! datasource-opts)]
(def close (:close!-fn datasource-map))
(def datasource (:datasource datasource-map))
(def find! (:find!-fn datasource-map))
(def find-by! (:find-by!-fn datasource-map))
(def all! (:all!-fn datasource-map))
(def delete! (:delete!-fn datasource-map))
(def save! (:save!-fn datasource-map)))
Now when you call my.datasource/find!
which will query it’s local datasource instead of the global datasource with q/find!
.
Logging
Gungnir uses clojure/tools.logging to log errors and debug information. You can plug in your own logging backend, whether that be timbre, slf4j, log4j, apache commons logging, etc.
In some error cases Gungnir might print out SQL or HoneySQL forms. These are always logged under the gungnir.sql
ns at :debug
level. To enable or disable these, see your logging backend configuration documentation.