-
Notifications
You must be signed in to change notification settings - Fork 0
/
core.clj
140 lines (124 loc) · 5.11 KB
/
core.clj
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
(ns views.sql.core
(:import
(net.sf.jsqlparser.parser CCJSqlParserUtil)
(net.sf.jsqlparser.util TablesNamesFinder)
(net.sf.jsqlparser.statement Statement)
(net.sf.jsqlparser.statement.update Update)
(net.sf.jsqlparser.statement.delete Delete)
(net.sf.jsqlparser.statement.insert Insert)
(net.sf.jsqlparser.statement.select Select)
(clojure.lang Atom))
(:require
[clojure.java.jdbc :as jdbc]
[views.core :as views]))
(def hint-type :sql-table-name)
(def ^:private query-types
{Select :select
Insert :insert
Update :update
Delete :delete})
(defn- get-query-tables-set
[^Statement stmt]
(as-> (TablesNamesFinder.) x
(.getTableList x stmt)
(map keyword x)
(set x)))
(defn- sql-stmt-returning?
[^Statement stmt stmt-type]
(condp = stmt-type
:select true
:insert (let [return-expr (.getReturningExpressionList ^Insert stmt)
returning-all? (.isReturningAllColumns ^Insert stmt)]
(or returning-all?
(and return-expr
(not (.isEmpty return-expr)))))
; TODO: JSqlParser doesn't currently support PostgreSQL's RETURNING clause
; support in UPDATE and DELETE queries
:update false
:delete false))
(defn- get-query-info
[^String sql]
(let [stmt (CCJSqlParserUtil/parse sql)
stmt-type (get query-types (type stmt))]
(if-not stmt-type
(throw (new Exception "Unsupported SQL query. Only SELECT, INSERT, UPDATE and DELETE queries are supported!"))
{:type stmt-type
:returning? (boolean (sql-stmt-returning? stmt stmt-type))
:tables (get-query-tables-set stmt)})))
(defonce query-info-cache (atom {}))
(defn query-info
[^String sql]
(if-let [info (get @query-info-cache sql)]
info
(let [info (get-query-info sql)]
(swap! query-info-cache assoc sql info)
info)))
(defn query-tables
[^String sql]
(:tables (query-info sql)))
(defmacro with-view-transaction
"Works exactly like clojure.java.jdbc/with-db-transaction. Use this instead to wrap
vexec! calls which need to be run in a transaction. Holds all view system hints
generated by any vexec! calls within the transaction until the end, at which point
they are all sent to the view system."
[^Atom view-system binding & forms]
(let [tvar (first binding)
db (second binding)
args (drop 2 binding)]
`(if (:views-sql/hints ~db) ;; check if we are in a nested transaction
(let [~tvar ~db] ~@forms)
(let [hints# (atom [])
result# (jdbc/with-db-transaction [t# ~db ~@args]
(let [~tvar (assoc ~db :views-sql/hints hints#)]
~@forms))]
(views/put-hints! ~view-system @hints#)
result#))))
(defn- execute-sql!
[db [sql & params :as sqlvec] returning?]
(if returning?
(jdbc/query db sqlvec)
(jdbc/execute! db sqlvec)))
(defn- vexec!*
[^Atom view-system db [sql & params :as sqlvec] {:keys [parse? namespace tables returning?] :as options}]
(let [tables (if parse? (query-tables sql) tables)
returning? (if parse? (:returning? (query-info sql)) returning?)
results (execute-sql! db sqlvec returning?)
hint (views/hint namespace tables hint-type)]
(if-let [tx-hints (:views-sql/hints db)]
(swap! tx-hints conj hint)
(views/put-hints! view-system [hint]))
results))
(defn vexec!
"Used to run any SQL insert/update/delete query on the database while ensuring
that view hints are sent to the view system to trigger any relevant view
refreshes. Use this instead of calling clojure.java.jdbc/execute! or
clojure.java.jdbc/insert!. If you need to perform an operation in a transaction
use with-view-transaction.
Arguments are:
- db: a clojure.java.jdbc database connection
- sqlvec: a JDBC-style vector containing a SQL query string followed by any
parameters for the query.
Options are:
- namespace: a namespace that will be included in the hints sent out
NOTE:
If the SQL being run cannot be parsed (e.g. due to use of database-specific
extensions, or other limitations of JSqlParser), you will need to manually
specify the list of table names (as keywords) that the SQL query will affect
as the optional tables argument. In addition, if the SQL query includes a
RETURNING clause, you should specify :returning true in the options given
to vexec!, since automatic detection of this will not work if the SQL cannot
be parsed."
{:arglists '([^Atom view-system db sqlvec & options]
[^Atom view-system db sqlvec tables & options])}
[^Atom view-system db sqlvec & options]
(let [[first-option & [other-options]] options]
(if (or (nil? options)
(map? first-option))
(vexec!* view-system db sqlvec
(-> first-option
(assoc :parse? true)
(dissoc :returning?)))
(vexec!* view-system db sqlvec
(assoc other-options
:tables (set first-option)
:parse? false)))))