From 9e1074641091fa944d1b0321de6b902090fce6fc Mon Sep 17 00:00:00 2001 From: Marat Surmashev Date: Mon, 16 Oct 2023 13:20:05 +0200 Subject: [PATCH] PG insert jackson support --- deps.edn | 6 +++-- src/dsql/core.clj | 1 + src/dsql/pg.clj | 59 +++++++++++++++++++++++++++++++++++-------- test/dsql/pg_test.clj | 33 +++++++++++++++++++++++- 4 files changed, 85 insertions(+), 14 deletions(-) diff --git a/deps.edn b/deps.edn index aa20740..b4ee993 100644 --- a/deps.edn +++ b/deps.edn @@ -1,6 +1,8 @@ {:paths ["src"] - :deps {org.clojure/clojure {:mvn/version "1.11.1"} - cheshire/cheshire {:mvn/version "5.11.0"}} + :deps {org.clojure/clojure {:mvn/version "1.11.1"} + cheshire/cheshire {:mvn/version "5.11.0"} + com.fasterxml.jackson.core/jackson-core {:mvn/version "2.15.2"} + com.fasterxml.jackson.core/jackson-databind {:mvn/version "2.15.2"}} :aliases {:dev diff --git a/src/dsql/core.clj b/src/dsql/core.clj index dbb0647..0e82a0b 100644 --- a/src/dsql/core.clj +++ b/src/dsql/core.clj @@ -33,6 +33,7 @@ (meta node))))) (defn dispatch-sql [opts x] + (prn "->>> " x) (or (get-type x) (when-let [resolver (:resolve-type opts)] diff --git a/src/dsql/pg.clj b/src/dsql/pg.clj index 91422a4..ef25856 100644 --- a/src/dsql/pg.clj +++ b/src/dsql/pg.clj @@ -2,6 +2,8 @@ (:require [dsql.core :as ql] [cheshire.core] [clojure.string :as str]) + (:import [com.fasterxml.jackson.databind.node ObjectNode ArrayNode TextNode IntNode BooleanNode] + [com.fasterxml.jackson.databind JsonNode ObjectMapper]) (:refer-clojure :exclude [format])) (def keywords @@ -100,6 +102,9 @@ ;; [ FETCH { FIRST | NEXT } [ число ] { ROW | ROWS } ONLY ] ;; [ FOR { UPDATE | NO KEY UPDATE | SHARE | KEY SHARE } [ OF имя_таблицы [, ...] ] [ NOWAIT | SKIP LOCKED ] [...] ] + +(defonce ^ObjectMapper object-mapper (ObjectMapper.)) + (defn acc-identity [acc & _] acc) @@ -402,6 +407,20 @@ (conj acc (ql/string-litteral (cheshire.core/generate-string (second v)))) (conj acc (ql/string-litteral (cheshire.core/generate-string v))))) +(defn to-json-string [^JsonNode json] + (.writeValueAsString object-mapper json)) + +(defn jackson-param [acc v] (conj acc ["?" (to-json-string v)])) +(defn jackson-param-as-text [acc ^JsonNode v] (conj acc ["?" (.asText v)])) + +(defmethod ql/to-sql ObjectNode [acc opts v] (jackson-param acc v)) +(defmethod ql/to-sql ArrayNode [acc opts v] (jackson-param acc v)) +(defmethod ql/to-sql TextNode [acc opts v] (jackson-param-as-text acc v)) +(defmethod ql/to-sql IntNode [acc opts v] (jackson-param-as-text acc v)) +(defmethod ql/to-sql BooleanNode [acc opts v] (jackson-param-as-text acc v)) + + + (defmethod ql/to-sql :pg/obj [acc opts obj] @@ -659,14 +678,14 @@ (defn resolve-type [x] (if-let [m (meta x)] (cond - (:pg/op m) :pg/op - (:pg/fn m) :pg/fn - (:pg/obj m) :pg/obj - (:jsonb/obj m) :jsonb/obj - (:jsonb/array m) :jsonb/array - (:pg/jsonb m) :pg/jsonb - (:pg/kfn m) :pg/kfn - (:pg/select m) :pg/select + (:pg/op m) :pg/op + (:pg/fn m) :pg/fn + (:pg/obj m) :pg/obj + (:jsonb/obj m) :jsonb/obj + (:jsonb/array m) :jsonb/array + (:pg/jsonb m) :pg/jsonb + (:pg/kfn m) :pg/kfn + (:pg/select m) :pg/select (:pg/sub-select m) :pg/sub-select :else nil))) @@ -1379,10 +1398,23 @@ ret (-> (conj "RETURNING") (ql/to-sql opts ret)))))) + +(defn jackson-get-keys [^ObjectNode object] + (let [it (.fieldNames object)] + (loop [keys []] + (if (.hasNext it) + (recur (conj keys (.next it))) + keys)))) + +(defn get-by-key [^ObjectNode node ^String key] + (.get node key)) + (defmethod ql/to-sql :pg/insert - [acc opts {tbl :into vls :value ret :returning on-conflict :on-conflict}] - (let [cols (->> (keys vls) (sort))] + [acc opts {tbl :into vls :value jackson :jackson-value ret :returning on-conflict :on-conflict}] + (let [cols (if jackson + (jackson-get-keys jackson) + (->> (keys vls) (sort)))] (-> acc (conj "INSERT INTO") (conj (name tbl)) @@ -1391,7 +1423,12 @@ (conj ")") (conj "VALUES") (conj "(") - (ql/reduce-separated2 "," (fn [acc c] (ql/to-sql acc opts (get vls c))) cols) + (ql/reduce-separated2 + "," + (if jackson + (fn [acc c] (ql/to-sql acc opts (get-by-key jackson c))) + (fn [acc c] (ql/to-sql acc opts (get vls c)))) + cols) (conj ")") (cond-> on-conflict (-> (conj "ON CONFLICT") diff --git a/test/dsql/pg_test.clj b/test/dsql/pg_test.clj index ac6a505..49585db 100644 --- a/test/dsql/pg_test.clj +++ b/test/dsql/pg_test.clj @@ -1,14 +1,45 @@ (ns dsql.pg-test (:require [dsql.pg :as sut] [clojure.test :refer [deftest is testing]] - [dsql.core :as ql])) + [cheshire.core]) + (:import [com.fasterxml.jackson.databind JsonNode ObjectMapper])) (defmacro format= [q patt] `(let [res# (sut/format ~q)] (is (= ~patt res#)) res#)) + +(defonce ^ObjectMapper object-mapper (ObjectMapper.)) +;; Only for tests purproses +(defn to-jackson [o] (.readTree object-mapper (cheshire.core/generate-string o)) ) + (deftest test-dsql-pgi + + + + (testing "jackson" + (def r + {:id "fd209869-1f1c-42eb-b0bf-b5942370452c" + :meta_partition 123 + :birthDate "1991-11-08" + :name [{:given "Ibragim"}] + :deceasedBoolean false + :extension {:foo "bar"}} ) + (format= + {:ql/type :pg/insert + :into :patient + :jackson-value (to-jackson r) + :returning :*} + ["INSERT INTO patient ( \"id\", \"meta_partition\", \"birthDate\", \"name\", \"deceasedBoolean\", \"extension\" ) VALUES ( ? , ? , ? , ? , ? , ? ) RETURNING *" + "fd209869-1f1c-42eb-b0bf-b5942370452c" + "123" + "1991-11-08" + "[{\"given\":\"Ibragim\"}]" + "false" + "{\"foo\":\"bar\"}"]) + ) + (testing "select" (format= {:ql/type :pg/projection