Skip to content

Commit 90f6434

Browse files
committed
xtdb, datalevin, sqlite and sql result to datoms
1 parent 7fb491d commit 90f6434

File tree

10 files changed

+538
-0
lines changed

10 files changed

+538
-0
lines changed

deps.edn

+30
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{:paths ["src" "resources"]
2+
:deps {org.clojure/tools.logging {:mvn/version "1.1.0"}
3+
org.slf4j/slf4j-log4j12 {:mvn/version "1.7.32"}
4+
integrant/integrant {:mvn/version "0.8.0"}
5+
aleph/aleph {:mvn/version "0.4.7-alpha7"}
6+
metosin/reitit {:mvn/version "0.5.15"}
7+
metosin/muuntaja {:mvn/version "0.6.8"}
8+
ring/ring-defaults {:mvn/version "0.3.3"}
9+
10+
;; XTDB
11+
com.xtdb/xtdb-core {:mvn/version "1.19.0-beta1"}
12+
com.xtdb/xtdb-rocksdb {:mvn/version "1.19.0-beta1"}
13+
14+
;; Datalevin
15+
datalevin/datalevin {:mvn/version "0.5.12"}
16+
com.cognitect/transit-clj {:mvn/version "1.0.324"}
17+
18+
;; SQL ;)
19+
com.github.seancorfield/next.jdbc {:mvn/version "1.2.709"}
20+
com.github.seancorfield/honeysql {:mvn/version "2.0.783"}
21+
hikari-cp/hikari-cp {:mvn/version "2.13.0"}
22+
org.xerial/sqlite-jdbc {:mvn/version "3.36.0.3"}
23+
ragtime/ragtime {:mvn/version "0.8.1"}
24+
25+
;; for time-ordered UUIDs
26+
com.github.f4b6a3/uuid-creator {:mvn/version "4.1.1"}
27+
28+
;;Log
29+
30+
}}

resources/log4j.properties

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
log4j.rootLogger=DEBUG, console
2+
log4j.appender.console=org.apache.log4j.ConsoleAppender
3+
log4j.appender.console.layout=org.apache.log4j.PatternLayout
4+
log4j.appender.console.layout.ConversionPattern=%-5p %c: %m%n
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{:up ["CREATE TABLE article (
2+
id VARCHAR(36) PRIMARY KEY,
3+
title VARCHAR(100),
4+
author VARCHAR(100)
5+
);"]
6+
:down ["DROP TABLE article;"]}

src/reitit_db_fun/core.clj

+239
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
(ns reitit-db-fun.core
2+
(:require [clojure.java.io :as io]
3+
[integrant.core :as ig]
4+
[aleph.http :as http]
5+
[reitit.ring :as ring]
6+
[muuntaja.core :as m]
7+
[reitit.ring.middleware.muuntaja :as muuntaja]
8+
[reitit.coercion.malli]
9+
[reitit.coercion :as coercion]
10+
[clojure.tools.logging :as log]
11+
12+
[reitit-db-fun.model]
13+
[reitit-db-fun.model-impl]
14+
[reitit-db-fun.storage]
15+
;; XTDB
16+
[xtdb.api :as xt]
17+
18+
;; Datalevin
19+
[datalevin.core :as d]
20+
21+
;; JDBC
22+
[next.jdbc :as jdbc]
23+
[next.jdbc.sql :as jdbc-sql]
24+
[honey.sql :as sql]
25+
[honey.sql.helpers :as h]
26+
27+
;; Datoms
28+
[reitit-db-fun.datom :as datom]))
29+
30+
;; ==== Config ====
31+
32+
(def config {:app/handler {:keys-to-wrap
33+
{#_#_:node (ig/ref :storage/xtdb)
34+
:model (ig/ref #_:model/article-datalevin
35+
:model/article-sql
36+
#_:model/article-xtdb)}}
37+
38+
:model/article-xtdb {:node (ig/ref :storage/xtdb)}
39+
:model/article-datalevin {:conn (ig/ref :storage/datalevin)}
40+
:model/article-sql {:datasource (ig/ref :storage/sql)}
41+
42+
:adapter/aleph {:port 8080
43+
:handler (ig/ref :app/handler)}
44+
45+
:storage/xtdb {}
46+
:storage/datalevin {:uri "datalevin.db"
47+
:schema {}}
48+
:storage/sql {:conn-options {:jdbc-url "jdbc:sqlite:database.sqlite"}
49+
:migrations-dir "migrations"}})
50+
51+
52+
(defn wrap-keys
53+
[keys-to-wrap]
54+
(fn [handler]
55+
(fn [request]
56+
(handler (merge request keys-to-wrap)))))
57+
58+
(defn get-article-handler
59+
[{:keys [model path-params]}]
60+
(println "DEBUG:" (pr-str path-params))
61+
;; TODO proper coercion
62+
{:body (reitit-db-fun.model/get-article model (:article-id path-params))})
63+
64+
(defn get-articles-handler [{:keys [model]}]
65+
{:body (reitit-db-fun.model/get-articles model)})
66+
67+
(defn update-article-handler [{:keys [model body-params]}]
68+
{:body (reitit-db-fun.model/update-article model body-params)})
69+
70+
71+
(defn get-app-handler [{:keys [keys-to-wrap]}]
72+
(ring/ring-handler
73+
(ring/router
74+
["/api"
75+
["/articles"
76+
{:get get-articles-handler}]
77+
["/article"
78+
{:post update-article-handler}]
79+
["/article/:article-id"
80+
{:get get-article-handler}]
81+
["/ping"
82+
{:get {:handler (fn [req]
83+
{:status 200
84+
:body {:message "pong"
85+
:request (pr-str req)}})}}]
86+
["/status"
87+
{:get {:handler (fn [req]
88+
{:status 200
89+
:body {:message "status"
90+
:model (pr-str (:model req))}})}}]]
91+
;; router data affecting all routes
92+
{:data {:muuntaja m/instance
93+
:middleware [muuntaja/format-middleware
94+
muuntaja/format-response-middleware
95+
(wrap-keys keys-to-wrap)]}})))
96+
97+
(defonce main-system (atom nil))
98+
99+
(defmethod ig/init-key :adapter/aleph [_ {:keys [handler port]}]
100+
(http/start-server handler {:port port}))
101+
102+
(defmethod ig/halt-key! :adapter/aleph [_ server]
103+
(.close server))
104+
105+
106+
107+
(defmethod ig/init-key :app/handler [_ {:keys [keys-to-wrap]}]
108+
(get-app-handler {:keys-to-wrap keys-to-wrap}))
109+
110+
(defn start-system [system-atom config]
111+
(log/info "Starting system")
112+
(let [system @system-atom]
113+
(when-not system
114+
(ig/load-namespaces config)
115+
(reset! system-atom (ig/init config)))))
116+
117+
(defn stop-system [system-atom]
118+
(log/info "Stopping system")
119+
(let [system @system-atom]
120+
(when system
121+
(reset! system-atom (ig/halt! system)))))
122+
123+
(defn -main [_]
124+
(start-system main-system config))
125+
126+
127+
(defn restart-system []
128+
(stop-system main-system)
129+
(start-system main-system config))
130+
131+
132+
(comment
133+
(restart-system)
134+
135+
136+
(-> ((:app/handler @main-system)
137+
{:request-method :post
138+
:uri "/api/article"
139+
:body-params {#_#_:xt/id 1
140+
:article/id "3ba51497-4a08-4e48-9b2c-ec4c88930da7"
141+
:article/title "Title zmieniony ponownie"
142+
:article/author "pkoza"
143+
:article/gerne "blog"}})
144+
:body
145+
slurp
146+
)
147+
148+
(-> ((:app/handler @main-system)
149+
{:request-method :get
150+
:uri "/api/article/1"})
151+
:body
152+
slurp)
153+
154+
(-> ((:app/handler @main-system)
155+
{:request-method :get
156+
:uri "/api/articles"})
157+
:body
158+
slurp)
159+
160+
161+
(let [app (:app/handler @main-system)]
162+
(doseq [idx (range 1000)]
163+
(app {:request-method :post
164+
:uri "/api/article"
165+
:body-params {:article/title (str "Test-" (inc idx))
166+
:article/author "pkoza"
167+
#_#_:article/id idx}})))
168+
169+
(let [node (:storage/xtdb @main-system)
170+
id 1]
171+
(xt/q (xt/db node)
172+
'{:find [(pull ?e [*])]
173+
:in [id]
174+
:where [[?e :xt/id id]]}
175+
id))
176+
177+
(-> (:model/article-datalevin @main-system)
178+
(reitit-db-fun.model/get-articles {}))
179+
180+
181+
;; Datalevin test
182+
183+
(let [conn (:storage/datalevin @main-system)]
184+
(d/transact! conn
185+
[{:article/title "A Frege", :db/id -1, :article/nation "France", :article/aka ["foo" "fred"]}
186+
{:article/title "A Peirce", :db/id -2, :article/nation "france"}
187+
{:article/title "De Morgan", :db/id -3, :article/nation "English"}]))
188+
189+
(let [conn (:storage/datalevin @main-system)]
190+
(d/q '[:find ?nation
191+
:in $ ?alias
192+
:where
193+
[?e :article/aka ?alias]
194+
[?e :article/nation ?nation]]
195+
(d/db conn)
196+
"foo"))
197+
198+
(let [conn (:storage/datalevin @main-system)]
199+
(d/q '[:find (pull ?e [*])
200+
:where
201+
[?e :article/title]]
202+
(d/db conn)))
203+
(let [conn (:storage/datalevin @main-system)]
204+
(d/q '[:find (pull ?article-id [*])
205+
:in $ ?article-id
206+
:where
207+
[?e :db/id ?article-id]]
208+
(d/db conn)
209+
18))
210+
;; jdbc
211+
212+
213+
(->
214+
(let [datasource (:storage/sql @main-system)
215+
{:article/keys [id title author]} {:article/id "9898906f-ce7f-42ad-a66d-7173b2c2bd03"
216+
:article/title "Tytuł zmieniony"
217+
:article/author "tester!"}
218+
219+
result (if id
220+
(jdbc-sql/update! datasource :articles
221+
{:title title :author author}
222+
{:id id})
223+
(jdbc-sql/insert! datasource :articles
224+
{:id (str (java.util.UUID/randomUUID))
225+
:title title
226+
:author author}))]
227+
(if id
228+
(jdbc-sql/query datasource ["select * from articles where id = ?" id])
229+
(jdbc-sql/query datasource ["select * from articles where rowid = ?"
230+
((keyword "last_insert_rowid()")) result]))))
231+
232+
(time (count (let [datasource (:storage/sql @main-system)
233+
query (sql/format {:select :*
234+
:from :article
235+
#_#_:limit 10})]
236+
(->> (jdbc/execute! datasource query)
237+
(into #{} (mapcat datom/entity->datoms))))))
238+
239+
)

src/reitit_db_fun/datom.clj

+63
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
(ns reitit-db-fun.datom
2+
(:require [cognitect.transit :as transit]))
3+
4+
(defn -reduce-kv
5+
"Variant of reduce-kv that does not unwrap (reduced)"
6+
[f init coll]
7+
(reduce-kv #(let [result (f %1 %2 %3)]
8+
(cond-> result
9+
;; wrap twice because reduce-kv will unwrap one reduced
10+
;; but we want to pass that info down the line
11+
(reduced? result) reduced))
12+
init coll))
13+
14+
(defn- entity->flat
15+
"Transducer mapujący encję w postaci mapy na listę datomów.
16+
Mapa z encją powinna zawierać klucz :db/id
17+
Do rozważenia czy generować datomy z :<namespace>/id.
18+
Są redundantne i zwiekszają ilość datomów.
19+
Wystarczy w warunku zmienić z
20+
(= attr :db/id) -> (= (name attr) \"id\")
21+
i żadnych idków nie będzie."
22+
[rf]
23+
(completing
24+
(fn [result entity]
25+
(-reduce-kv
26+
(fn [result attr val]
27+
(if (= attr :db/id)
28+
result
29+
(rf result [(:db/id entity) attr val])))
30+
result entity))))
31+
32+
33+
(def -entity-datoms
34+
(comp entity->flat
35+
(map #(transit/tagged-value "datascript/Datom" %))))
36+
37+
(defn entity->datoms
38+
"Konwertuje pojedynczą encję na datomy.
39+
grupuje wg namespace klucza. wymaga aby każda tabela/namespae miało podane unikalne id
40+
przykład:
41+
42+
{:user/id 1
43+
:user/name \"John\"
44+
:user/email \"[email protected]\"
45+
:user/address 2
46+
:address/id 2
47+
:address/street \"Second Street\"
48+
:address/city \"NY\"}"
49+
50+
[entity]
51+
(into #{}
52+
(comp
53+
(map (fn [[table entity-for-table]]
54+
(let [id (get entity (keyword table "id"))
55+
_ (when-not id
56+
(throw (ex-info (format "Missing :%s/id key" table)
57+
{:table table
58+
:entity entity-for-table})))]
59+
(-> {}
60+
(into entity-for-table)
61+
(assoc :db/id id)))))
62+
entity->flat)
63+
(group-by (comp namespace first) entity)))

src/reitit_db_fun/model.clj

+7
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
(ns reitit-db-fun.model
2+
(:gen-class))
3+
4+
(defprotocol IArticle
5+
(update-article [_ article])
6+
(get-articles [_])
7+
(get-article [_ article-id]))

0 commit comments

Comments
 (0)