Skip to content

Commit 244ec52

Browse files
committed
#12: Add support for UUID and JSON/JSONB
1 parent 098d27d commit 244ec52

File tree

5 files changed

+93
-11
lines changed

5 files changed

+93
-11
lines changed

README.md

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -116,20 +116,29 @@ Channel-returning functions can be composed with `dosql` macro that returns resu
116116
; {:now-promoting [{:id 1, product_id 1002}]}
117117
```
118118

119+
## JSON and JSONB
120+
121+
Using JSON types requires `[cheshire "5.5.0"]` and reading the ns `postgres.async.json`.
122+
123+
```clojure
124+
(require '[postgres.async.json])
125+
126+
(<!! (query! db ["select $1::JSONB" {:hello "world"}]))
127+
; [{:jsonb {:hello "world"}}]
128+
```
129+
119130
## Custom column types
120131

121132
Support for custom types can be added by extending `IPgParameter` protocol and `from-pg-value` multimethod.
122133

123134
```clojure
124-
(require '[cheshire.core :as json])
125-
126135
(extend-protocol IPgParameter
127-
clojure.lang.IPersistentMap
128-
(to-pg-value [value]
129-
(.getBytes (json/generate-string value))))
136+
com.example.MyHStore
137+
(to-pg-value [store]
138+
(.getBytes (str store) "UTF-8")))
130139

131-
(defmethod from-pg-value com.github.pgasync.impl.Oid/JSON [oid value]
132-
(json/parse-string (String. value))
140+
(defmethod from-pg-value com.github.pgasync.impl.Oid/HSTORE [oid ^bytes value]
141+
(my-h-store/parse-string (String. value "UTF-8)))
133142
```
134143
135144
## Dependencies

project.clj

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,8 @@
77
:url "http://github.com/alaisi/postgres.async.git"}
88
:dependencies [[org.clojure/clojure "1.6.0"]
99
[org.clojure/core.async "0.1.346.0-17112a-alpha"]
10-
[com.github.alaisi.pgasync/postgres-async-driver "0.5"]]
10+
[com.github.alaisi.pgasync/postgres-async-driver "0.6"]
11+
[cheshire "5.5.0" :scope "provided"]]
1112
:lein-release {:deploy-via :clojars}
1213
:global-vars {*warn-on-reflection* true}
1314
:target-path "target/%s"

src/postgres/async/json.clj

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
(ns postgres.async.json
2+
(:require [postgres.async :refer [from-pg-value IPgParameter]]
3+
[cheshire.core :as json]
4+
[cheshire.parse])
5+
(:import [java.nio.charset StandardCharsets]))
6+
7+
(defn- json->coll [^bytes value]
8+
(binding [cheshire.parse/*use-bigdecimals?* true]
9+
(json/parse-string (String. value StandardCharsets/UTF_8) true)))
10+
11+
(defn- coll->json [coll]
12+
(.getBytes (json/generate-string coll) StandardCharsets/UTF_8))
13+
14+
(extend-protocol IPgParameter
15+
clojure.lang.IPersistentCollection
16+
(to-pg-value [coll]
17+
(coll->json coll))
18+
clojure.lang.IPersistentMap
19+
(to-pg-value [map]
20+
(coll->json map)))
21+
22+
(defmethod from-pg-value com.github.pgasync.impl.Oid/JSON [oid value]
23+
(json->coll value))
24+
25+
(defmethod from-pg-value com.github.pgasync.impl.Oid/JSONB [oid value]
26+
(json->coll value))
27+

test/postgres/async/json_test.clj

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
(ns postgres.async.json-test
2+
(:require [postgres.async :refer :all]
3+
[postgres.async.json]
4+
[clojure.test :refer :all]
5+
[postgres.async-test :refer [db-fixture wait *db*]]))
6+
7+
(use-fixtures :each db-fixture)
8+
9+
(deftest json-queries
10+
11+
(testing "json is returned as json using keywords"
12+
(let [rs (wait (query! *db*
13+
["select '{\"name\":\"demo\",\"c\":[1,2]}'::JSON as j"]))]
14+
(is (= "demo" (get-in rs [0 :j :name])))
15+
(is (= [1 2] (get-in rs [0 :j :c])))))
16+
17+
(testing "json array parameter is returned as vec"
18+
(let [rs (wait (query! *db* ["select $1::JSON as j" ["a" "b"]]))]
19+
(is (= ["a" "b"] (get-in rs [0 :j])))))
20+
21+
(testing "json parameter is returned as map"
22+
(let [rs (wait (query! *db* ["select $1::JSON as j" {:a "b"}]))]
23+
(is (= {:a "b"} (get-in rs [0 :j]))))))
24+
25+
(deftest jsonb-queries
26+
27+
(testing "jsonb array parameter is returned as vec"
28+
(let [rs (wait (query! *db* ["select $1::JSONB as j" ["a" [1 2]]]))]
29+
(is (= ["a" [1 2]] (get-in rs [0 :j])))))
30+
31+
(testing "jsonb parameter is returned as map"
32+
(let [rs (wait (query! *db* ["select $1::JSONB as j" {:a nil :b true :c "s"}]))]
33+
(is (nil? (get-in rs [0 :j :a])))
34+
(is (= true (get-in rs [0 :j :b])))
35+
(is (= "s" (get-in rs [0 :j :c]))))))
36+

test/postgres/async_test.clj

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
[clojure.core.async :refer [<!! go]]
44
[postgres.async :refer :all]))
55

6-
(def ^:private ^:dynamic *db*)
6+
(def ^:dynamic *db*)
77
(def table "clj_pg_test")
88

9-
(defn- wait [channel]
9+
(defn wait [channel]
1010
(let [r (<!! channel)]
1111
(if (instance? Throwable r)
1212
(throw r))
@@ -17,7 +17,7 @@
1717
(wait (execute! db [(str "create table " table " (
1818
id serial, t varchar(10))")])))
1919

20-
(defn- db-fixture [f]
20+
(defn db-fixture [f]
2121
(letfn [(env [name default]
2222
(or (System/getenv name) default))]
2323
(binding [*db* (open-db {:hostname (env "PG_HOST" "localhost")
@@ -34,25 +34,31 @@
3434
(use-fixtures :each db-fixture)
3535

3636
(deftest queries
37+
3738
(testing "query returns rows as map"
3839
(let [rs (wait (query! *db* ["select 1 as x"]))]
3940
(is (= 1 (get-in rs [0 :x]))))))
4041

4142
(deftest query-for-array
43+
4244
(testing "arrays are converted to vectors"
4345
(let [rs (wait (query! *db* ["select '{1,2}'::INT[] as a"]))]
4446
(is (= [1 2] (get-in rs [0 :a])))))
47+
4548
(testing "nested arrays are converted to vectors"
4649
(let [rs (wait (query! *db* ["select '{{1,2},{3,4},{5,NULL}}'::INT[][] as a"]))]
4750
(is (= [[1 2] [3 4] [5 nil]] (get-in rs [0 :a]))))))
4851

4952
(deftest inserts
53+
5054
(testing "insert returns row count"
5155
(let [rs (wait (insert! *db* {:table table} {:t "x"}))]
5256
(is (= 1 (:updated rs)))))
57+
5358
(testing "insert with returning returns generated keys"
5459
(let [rs (wait (insert! *db* {:table table :returning "id"} {:t "y"}))]
5560
(is (get-in rs [:rows 0 :id]))))
61+
5662
(testing "multiple rows can be inserted"
5763
(let [rs (wait (insert! *db*
5864
{:table table :returning "id"}
@@ -61,6 +67,7 @@
6167
(is (= 2 (count (:rows rs)))))))
6268

6369
(deftest updates
70+
6471
(testing "update returns row count"
6572
(let [rs (wait (insert! *db*
6673
{:table table :returning "id"}
@@ -73,13 +80,15 @@
7380
(is (= 2 (:updated rs))))))
7481

7582
(deftest sql-macro
83+
7684
(testing "dosql returns last form"
7785
(is (= "123" (wait (go (dosql
7886
[tx (begin! *db*)
7987
rs (query! tx ["select 123 as x"])
8088
rs (query! tx ["select $1::text as t" (:x (first rs))])
8189
_ (commit! tx)]
8290
(:t (first rs))))))))
91+
8392
(testing "dosql short-circuits on errors"
8493
(let [e (Exception. "Oops!")
8594
executed (atom 0)]

0 commit comments

Comments
 (0)