https://github.com/luminus-framework/conman
a helper library for managing database connections
https://github.com/luminus-framework/conman
Last synced: 6 months ago
JSON representation
a helper library for managing database connections
- Host: GitHub
- URL: https://github.com/luminus-framework/conman
- Owner: luminus-framework
- License: epl-1.0
- Created: 2015-08-01T18:35:34.000Z (almost 11 years ago)
- Default Branch: master
- Last Pushed: 2023-08-31T10:57:07.000Z (almost 3 years ago)
- Last Synced: 2025-10-03T00:58:20.240Z (9 months ago)
- Language: Clojure
- Size: 118 KB
- Stars: 124
- Watchers: 8
- Forks: 26
- Open Issues: 3
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE
Awesome Lists containing this project
README
# conman

Luminus database connection management and SQL query generation library
The library provides pooled connections using the [HikariCP](https://github.com/brettwooldridge/HikariCP) library.
The queries are generated using [HugSQL](https://github.com/layerware/hugsql) and wrapped with
connection aware functions.
## Usage
[](http://clojars.org/conman)
Conman relies on a dynamic variable to manage the connection. The dynamic variable allows the connection to be
rebound contextually for transactions. When working with multiple databases, a separate var is required
to track each database connection.
### Defining Queries
SQL statements should be populated in files that are accessible on the resource path.
For example, we could create a file `resources/sql/queries.sql` with
the following content:
``` sql
-- :name create-user! :! :n
-- :doc creates a new user record
INSERT INTO users
(id, first_name, last_name, email, pass)
VALUES (:id, :first_name, :last_name, :email, :pass)
-- :name get-user :? :1
-- :doc retrieve a user given the id.
SELECT * FROM users
WHERE id = :id
-- :name get-all-users :? :*
-- :doc retrieve all users.
SELECT * FROM users
```
See the official [HugSQL docs](http://www.hugsql.org/) for further examples.
### Using `bind-connection`
The queries are bound to the connection using the `bind-connection` macro. This macro
accepts the connection var followed by one or more strings representing SQL query files.
The lifecycle of the connection is expected to be managed using a library such as [mount](https://github.com/tolitius/mount). The full list of options that can be passed to `pool-spec` can be found [here](https://github.com/tomekw/hikari-cp#configuration-options).
```clojure
(ns myapp.db
(:require [mount.core :refer [defstate]]
[conman.core :as conman]))
(def pool-spec
{:jdbc-url "jdbc:postgresql://localhost/myapp?user=user&password=pass"})
(defstate ^:dynamic *db*
:start (conman/connect! pool-spec)
:stop (conman/disconnect! *db*))
(conman/bind-connection *db* "sql/queries.sql")
```
The `bind-connection` generates `create-user!` and `get-user` functions
in the current namespace. These functions can be called in four different ways:
```clojure
;; when called with no argument then the HugSQL generated function
;; will be called with an empty parameter map and the connection specified in the *db* var
(get-all-users)
;; when a parameter map is passed as the argument, then the map and the connection specified
;; in the *db* var will be passed to the HugSQL generated function
(create-user! {:id "foo" :first_name "Bob" :last_name "Bobberton" :email nil :pass nil})
;; an explicit connection and a parameter map can be
;; passed to the function
(get-user some-other-conn {:id "foo"})
;; finally, an explicit connection and a parameter map, options, and optional command options
;; can be passed to the function
(get-user some-other-conn {:id "foo"} opts)
(get-user some-other-conn {:id "foo"} opts cmd-opt1 cmd-opt2)
```
### Using `bind-connection-map`
Alternatively, you may wish to use `bind-connection-map` to define the queries. This function will return
a map containing `:snips` and `:fns` keys that point to maps of snippets and queries respectively.
The `snip` and `query` helper functions are provided for accessing the connection map returned by the `bind-connection-map`
function.
```clojure
;; create a connection map given the connection instance and one or more query files:
(def queries (bind-connection-map conn "queries.sql"))
;; a HugSQL options map can be passed as the second argument:
(def queries (bind-connection-map conn {:quoting :ansi} "queries.sql"))
;; run a query
(query
queries
:add-fruit!
{:name "apple"
:appearance "red"
:cost 1
:grade 1})
;; run a query specifying the connection explicitly
(query
conn
queries
:add-fruit!
{:name "apple"
:appearance "red"
:cost 1
:grade 1})
;; run a query with a snippet
(query
conn
queries
:get-fruit-by
{:by-appearance
(snip queries :by-appearance {:appearance "red"})})
```
If you use `:cljc` [mode of mount](https://github.com/tolitius/mount/blob/master/doc/clojurescript.md#clojure-and-clojurescript-mode),
where you would need to explicitly `deref` each state, you'll need to use `conman/bind-connection-deref` instead of
`conman/bind-connection`. Both `conman/bind-connection-deref` and `conman/with-transaction` macros still accept undereferenced state,
while in every other case it has to be dereferenced:
```clojure
(mount/in-cljc-mode)
(conman/bind-connection-deref *db* "sql/queries.sql")
(conman/with-transaction [*db*]
(sql/db-set-rollback-only! @*db*)
(add-memo! {:id 123 :text "Hello"})
(sql/query @*db* ["SELECT * FROM memos;"]))
```
Next, the `connect!` function should be called to initialize the database connection.
The function accepts a map with the database specification.
```clojure
(def pool-spec
{:jdbc-url "jdbc:postgresql://localhost/myapp?user=user&password=pass"})
(connect! pool-spec)
```
For the complete list of configuration options refer to the official [hikari-cp](https://github.com/tomekw/hikari-cp) library documentation. Conman supports the following additional options:
* `:datasource`
* `:datasource-classname`
The connection can be terminated by running the `disconnect!` function:
```clojure
(disconnect! conn)
```
A connection can be reset using the `reconnect!` function:
```clojure
(reconnect! conn pool-spec)
```
When using a dynamic connection, it's possible to use the `with-transaction`
macro to rebind it to the transaction connection. The SQL query functions
generated by the `bind-connection` macro will automatically use the transaction
connection in that case:
```clojure
(with-transaction [conn {:rollback-only true}]
(create-user!
{:id "foo"
:first_name "Sam"
:last_name "Smith"
:email "sam.smith@example.com"})
(get-user {:id "foo"}))
```
The isolation level and readonly status of the transaction may be specified using the `:isolation`
and `:read-only?` keys respectively:
```clojure
(with-transaction
[conn {:isolation :serializable}]
(= java.sql.Connection/TRANSACTION_SERIALIZABLE
(.getTransactionIsolation (sql/db-connection conn))))
(with-transaction
[conn {:isolation :read-uncommitted}]
(= java.sql.Connection/TRANSACTION_READ_UNCOMMITTED
(.getTransactionIsolation (sql/db-connection conn))))
```
## License
Copyright © 2015 Dmitri Sotnikov and Carousel Apps Ltd.
Distributed under the Eclipse Public License either version 1.0 or (at
your option) any later version.