Skip to content

Commit 168adce

Browse files
refsetMichal Pisanko
andauthored
Multi-statement UX (#64)
* rejig the UI * multiple statements * use pgwire to run SQL (what used to be :sql/v1) * use consistent spacing * remove unneeded divs * stop adding default query on reload * correctly display results - do not show transaction begin/commit results * fix tests * fix init, rename var * missing key on error elem and incorrect default case * enable queries mixed with TXs * separate DMLs from queries, rollback after fail, accommodate results in the UI * unify XTQL results * statement+multi semis * better handling of semi-colons within strings * move X (clear results) button, allow rows to flex for narrow screens --------- Co-authored-by: Michal Pisanko <[email protected]>
1 parent 2fb7219 commit 168adce

File tree

16 files changed

+424
-308
lines changed

16 files changed

+424
-308
lines changed

src/clj/xt_play/handler.clj

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -38,11 +38,11 @@
3838

3939
(s/def ::system-time (s/nilable string?))
4040
(s/def ::txs string?)
41-
(s/def ::tx-batches (s/coll-of (s/keys :req-un [::system-time ::txs])))
42-
(s/def ::query string?)
41+
(s/def ::query (s/nilable string?))
42+
(s/def ::tx-batches (s/coll-of (s/keys :req-un [::system-time ::txs] :opt-un [::query])))
4343
(s/def ::tx-type #{"sql-v2" "xtql" "sql"})
4444
(s/def ::db-run (s/keys :req-un [::tx-batches ::query]))
45-
(s/def ::beta-db-run (s/keys :req-un [::tx-batches ::query ::tx-type]))
45+
(s/def ::beta-db-run (s/keys :req-un [::tx-batches ::tx-type]))
4646

4747
(defn- handle-client-error [ex _]
4848
{:status 400
@@ -97,7 +97,7 @@
9797
:handler #'docs-run-handler}}]
9898

9999
["/beta-db-run"
100-
{:post {:summary "Run transactions + a query"
100+
{:post {:summary "Run statements"
101101
:parameters {:body ::beta-db-run}
102102
:handler #'run-handler}}]
103103

src/clj/xt_play/main.clj

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@
1313
(defn -main [& _args]
1414
(ig/init system)
1515
@(delay))
16+
17+
(comment
18+
(-main))

src/clj/xt_play/transactions.clj

Lines changed: 122 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -6,31 +6,72 @@
66
[xt-play.util :as util]
77
[xt-play.xtdb :as xtdb]))
88

9-
(defn- encode-txs [tx-type txs]
9+
(defn- encode-txs [tx-type query? txs]
1010
(case (keyword tx-type)
11-
:sql (if (string? txs)
11+
:sql (if (and (not query?)
12+
(string? txs))
1213
(->> (str/split txs #";")
1314
(remove str/blank?)
1415
(map #(do [:sql %]))
1516
(vec))
1617
txs)
17-
:xtql (util/read-edn (str "[" txs "]"))
18+
:xtql (util/read-edn (if query? txs (str "[" txs "]")))
1819
;;else
1920
txs))
2021

22+
(defn- dml? [statement]
23+
(when statement
24+
(let [upper-st (str/upper-case statement)]
25+
(or (str/includes? upper-st "INSERT")
26+
(str/includes? upper-st "UPDATE")
27+
(str/includes? upper-st "DELETE")
28+
(str/includes? upper-st "ERASE")
29+
(str/includes? upper-st "MERGE")
30+
(str/includes? upper-st "PATCH")))))
31+
32+
(defn split-sql [sql]
33+
(loop [chars (seq sql) current [] statements [] in-string? false escape? false]
34+
(if (empty? chars)
35+
(conj statements (apply str current))
36+
(let [c (first chars)
37+
rest-chars (rest chars)]
38+
(cond
39+
(and (= c \') (not escape?))
40+
(recur rest-chars (conj current c) statements (not in-string?) false)
41+
42+
(and in-string? (= c \\))
43+
(recur rest-chars (conj current c) statements in-string? (not escape?))
44+
45+
(and (= c \;) (not in-string?))
46+
(recur rest-chars [] (conj statements (apply str current)) in-string? false)
47+
48+
:else
49+
(recur rest-chars (conj current c) statements in-string? false))))))
50+
2151
(defn- prepare-statements
2252
"Takes a batch of transactions and prepares the jdbc execution args to
23-
be run sequentially"
53+
be run sequentially. It groups statements by type and wraps DMLs in transactions if system time specified."
2454
[tx-batches]
2555
(for [{:keys [txs system-time]} tx-batches]
2656
(remove nil?
27-
(concat
28-
(when system-time
29-
[[(format "BEGIN AT SYSTEM_TIME TIMESTAMP '%s'" system-time)]])
30-
(mapv (fn [q] (vector (str q ";")))
31-
(str/split txs #"\s*;\s*"))
32-
(when system-time
33-
[["COMMIT"]])))))
57+
(when txs
58+
(let [statements (split-sql txs)
59+
by-type (partition-by dml? statements)]
60+
(log/warn "by-type" by-type)
61+
(mapcat
62+
(fn [grp]
63+
(let [dmls? (dml? (first grp))]
64+
(concat
65+
(when (and dmls? system-time)
66+
[[(format "BEGIN AT SYSTEM_TIME TIMESTAMP '%s'" system-time)]])
67+
(vec
68+
(keep (fn [q] (when-not (empty? q)
69+
[(str/trim q)]))
70+
grp))
71+
(when (and dmls? system-time)
72+
[["COMMIT"]]))))
73+
by-type)
74+
)))))
3475

3576
(defn format-system-time [s]
3677
(when s (read-instant-date s)))
@@ -54,42 +95,82 @@
5495
(mapv PG->clj row))
5596
result))
5697

57-
(defn- run!-tx [node tx-type tx-batches query]
98+
(defn- detect-xtql-queries [batch]
99+
(if (:query batch)
100+
batch
101+
(if (and (string? (:txs batch))
102+
(re-matches #"(?i)^\s*(\(->)?\s*\((from|unify|rel).+" (:txs batch)))
103+
(assoc batch :query true)
104+
batch)))
105+
106+
(defn- run!-tx [node tx-type tx-batches]
58107
(let [tx-batches (->> tx-batches
108+
(map detect-xtql-queries)
59109
(map #(update % :system-time format-system-time))
60-
(map #(update % :txs (partial encode-txs tx-type))))]
61-
(doseq [{:keys [system-time txs] :as batch} tx-batches]
62-
(log/info tx-type "running batch: " batch)
63-
(xtdb/submit! node txs {:system-time system-time})))
64-
(log/info tx-type "running query:" query)
65-
(let [res (xtdb/query node query)]
66-
(log/info tx-type "XTDB query response:" res)
67-
res))
68-
69-
(defn- run!-with-jdbc-conn [tx-batches query]
110+
(map #(update % :txs (partial encode-txs tx-type (:query %)))))
111+
tx-results
112+
(doall (mapv
113+
(fn [{:keys [system-time query txs] :as batch}]
114+
(log/info tx-type "running batch: " batch)
115+
(try
116+
(if query
117+
(xtdb/query node txs)
118+
(xtdb/submit! node txs {:system-time system-time}))
119+
(catch Throwable ex
120+
(log/error "Exception while running transaction" (ex-message ex))
121+
(parse-result
122+
[{:message (ex-message ex)
123+
:exception (.getClass ex)
124+
:data (ex-data ex)}]))))
125+
tx-batches))]
126+
(log/warn "run!-tx-res" tx-results)
127+
tx-results))
128+
129+
130+
(defn- run!-with-jdbc-conn [tx-batches]
70131
(xtdb/with-jdbc
71132
(fn [conn]
72-
(doseq [txs (prepare-statements tx-batches)
73-
statement txs]
74-
(log/info "beta executing statement:" statement)
75-
(xtdb/jdbc-execute! conn statement))
76-
(log/info "beta running query:" query)
77-
(let [res (xtdb/jdbc-execute! conn [query])]
78-
(log/info "beta query response" res)
79-
(parse-result res)))))
133+
(let [tx-in-progress? (atom false)
134+
res (mapv (fn [txs]
135+
(vec
136+
(mapcat
137+
(fn [statement]
138+
(log/info "beta executing statement:" statement)
139+
(when (str/includes? (str/upper-case (first statement)) "BEGIN")
140+
(reset! tx-in-progress? true))
141+
(try
142+
(let [res (parse-result (xtdb/jdbc-execute! conn statement))]
143+
(when (str/includes? (str/upper-case (first statement)) "COMMIT")
144+
(reset! tx-in-progress? false))
145+
(if-not (vector? (ffirst res))
146+
[res]
147+
res))
148+
(catch Exception ex
149+
(log/error "Exception while running statement" (ex-message ex))
150+
(when @tx-in-progress?
151+
(log/warn "Rolling back transaction")
152+
(xtdb/jdbc-execute! conn ["ROLLBACK"]))
153+
(parse-result
154+
[{:message (ex-message ex)
155+
:exception (.getClass ex)
156+
:data (ex-data ex)}]))))
157+
txs)))
158+
(prepare-statements tx-batches))]
159+
(log/info "run!-with-jdbc-conn-res" res)
160+
res))))
80161

81162
(defn run!!
82163
"Given transaction batches, a query and the type of transaction to
83164
use, will run transaction batches and queries sequentially,
84165
returning the last query response in column format."
85-
[{:keys [tx-batches query tx-type]}]
86-
(let [query (if (= "xtql" tx-type) (util/read-edn query) query)]
87-
(xtdb/with-xtdb
88-
(fn [node]
89-
(if (= "sql-v2" tx-type)
90-
(run!-with-jdbc-conn tx-batches query)
91-
(let [res (run!-tx node tx-type tx-batches query)]
92-
(util/map-results->rows res)))))))
166+
[{:keys [tx-batches tx-type]}]
167+
(xtdb/with-xtdb
168+
(fn [node]
169+
(if (#{"sql-v2" "sql"} tx-type)
170+
(run!-with-jdbc-conn tx-batches)
171+
(let [res (run!-tx node tx-type tx-batches)]
172+
(log/warn "run!!" res)
173+
(mapv (comp vector util/map-results->rows) res))))))
93174

94175
(defn docs-run!!
95176
"Given transaction batches and a query from the docs, will return the query
@@ -98,5 +179,6 @@
98179
(xtdb/with-xtdb
99180
(fn [node]
100181
(run!-tx node "sql"
101-
(mapv #(update % :txs util/read-edn) tx-batches)
102-
(util/read-edn query)))))
182+
(vec
183+
(conj (mapv #(update % :txs util/read-edn) tx-batches)
184+
{:txs (util/read-edn query) :query true}))))))

src/clj/xt_play/xtdb.clj

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -7,26 +7,24 @@
77
[xtdb.node :as xtn]))
88

99
(defn with-xtdb [f]
10-
(try
11-
(with-open [node (xtn/start-node config/node-config)]
12-
(f node))
13-
(catch Exception e
14-
(log/warn :submit-error {:e e})
15-
(throw e))))
10+
(with-open [node (xtn/start-node config/node-config)]
11+
(f node)))
12+
13+
(defn query [node q]
14+
(log/info :query q)
15+
(xt/q node q (when (string? q) {:key-fn :snake-case-string})))
1616

1717
(defn submit! [node txs opts]
1818
(log/info :submit-tx txs opts)
1919
(let [tx (xt/submit-tx node txs opts)
2020
results (xt/q node '(from :xt/txs [{:xt/id $tx-id} xt/error])
2121
{:args {:tx-id (:tx-id tx)}})]
22-
(log/info :submit-tx results)
23-
;; If any transaction fails, throw the error
24-
(when-let [error (-> results first :xt/error)]
25-
(throw error))))
26-
27-
(defn query [node q]
28-
(log/info :query q)
29-
(xt/q node q (when (string? q) {:key-fn :snake-case-string})))
22+
(log/info :submit-tx-result results)
23+
(if-let [error (-> results first :xt/error)]
24+
{:message (ex-message error)
25+
:exception (.getClass error)
26+
:data (ex-data error)}
27+
results)))
3028

3129
(defn with-jdbc [f]
3230
(with-open [conn (jdbc/get-connection config/db)]

src/cljc/xt_play/util.cljc

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,11 @@
1313

1414
(defn map-results->rows
1515
[results]
16-
(let [ks (keys (apply merge results))]
17-
(into [(vec ks)]
18-
(mapv (fn [row]
19-
(mapv #(get row %) ks))
20-
results))))
16+
(if (and (seq results)
17+
(every? map? results))
18+
(let [ks (keys (apply merge results))]
19+
(into [(vec ks)]
20+
(mapv (fn [row]
21+
(mapv #(get row %) ks))
22+
results)))
23+
results))

src/cljs/xt_play/app.cljs

Lines changed: 13 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,6 @@
77
[reagent.core :as r]
88
[goog.dom :as gdom]
99
[xt-play.components.highlight :as hl]
10-
[xt-play.model.query :as query]
1110
[xt-play.model.query-params :as query-params]
1211
[xt-play.model.tx-batch :as tx-batch]
1312
[xt-play.view :as view]))
@@ -25,29 +24,33 @@
2524
js/JSON.parse
2625
(js->clj :keywordize-keys true))]
2726
(->> txs
28-
(map #(update % :system-time (fn [d] (when d (js/Date. d))))))))
27+
(mapv #(update % :system-time (fn [d] (when d (js/Date. d))))))))
2928

3029
(rf/reg-event-fx
3130
::init
3231
[(rf/inject-cofx ::query-params/get)]
3332
(fn [{:keys [query-params]} [_ xt-version]]
33+
;; keep query here - this is so that any old URLs/doc links keep working - just put query w/txs (statements)
3434
(let [{:keys [type txs query enc]} query-params
3535
;; in case of saved link with sql-beta - translate to sql-v2
3636
type (keyword
3737
(if (or (empty? type)
3838
(= "sql-beta" type))
3939
"sql-v2"
40-
type))]
40+
type))
41+
txs (if txs
42+
(param-decode txs enc)
43+
[(tx-batch/default type)
44+
(tx-batch/default-query type)])
45+
statements (if query
46+
(conj txs {:txs (query-params/decode-from-binary query enc)
47+
:query "true"})
48+
txs)]
4149
{:db {:version xt-version
4250
:type type
43-
:enc enc
44-
:query (if query
45-
(query-params/decode-from-binary query enc)
46-
(query/default type))}
51+
:enc enc}
4752
:dispatch [::tx-batch/init
48-
(if txs
49-
(param-decode txs enc)
50-
[(tx-batch/default type)])]})))
53+
statements]})))
5154

5255
(defonce root (createRoot (gdom/getElement "app")))
5356

src/cljs/xt_play/config.cljs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,24 @@
33
(def ^:private default-dml
44
"[:put-docs :docs {:xt/id 1 :foo \"bar\"}]")
55

6+
(def ^:private default-xtql-query
7+
"(from :docs [xt/id foo])")
8+
69
(def ^:private default-sql-insert
710
"INSERT INTO docs (_id, col1) VALUES (1, 'foo');
811
INSERT INTO docs RECORDS {_id: 2, col1: 'bar', col2:' baz'};")
912

13+
(def ^:private default-sql-query
14+
"SELECT *, _valid_from FROM docs;")
15+
1016
(def default-transaction
1117
{:sql-v2 default-sql-insert
1218
:xtql default-dml})
1319

20+
(def default-query
21+
{:sql-v2 default-sql-query
22+
:xtql default-xtql-query})
23+
1424
(def tx-types
1525
{:sql-v2 {:value :sql-v2
1626
:label "SQL"}

0 commit comments

Comments
 (0)