-
Notifications
You must be signed in to change notification settings - Fork 4
/
flint.cljc
239 lines (219 loc) · 9.7 KB
/
flint.cljc
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
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
(ns com.yetanalytics.flint
(:require [clojure.spec.alpha :as s]
[com.yetanalytics.flint.format.query]
[com.yetanalytics.flint.spec.query :as qs]
[com.yetanalytics.flint.spec.update :as us]
[com.yetanalytics.flint.format :as f]
[com.yetanalytics.flint.format.update :as uf]
[com.yetanalytics.flint.error :as err]
[com.yetanalytics.flint.validate :as v]
[com.yetanalytics.flint.validate.aggregate :as va]
[com.yetanalytics.flint.validate.bnode :as vb]
[com.yetanalytics.flint.validate.prefix :as vp]
[com.yetanalytics.flint.validate.scope :as vs]))
(def xsd-iri-prefix
"The XMLSchema IRI prefix used for datatype annotation of literals,
including dateTime timestamps."
"<http://www.w3.org/2001/XMLSchema#>")
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Conform Helpers
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn- get-xsd-prefix
[prefixes]
(reduce-kv (fn [_ k v]
(if (= xsd-iri-prefix v)
(reduced (name k))
nil))
nil
prefixes))
(defn- conform-sparql-err-map
[error-kw error-loc-kws sparql]
{:kind error-kw
:input sparql
:clauses error-loc-kws})
(defn- conform-sparql
([error-kw spec spec-err? sparql]
(let [ast (s/conform spec sparql)]
(if (= ::s/invalid ast)
(let [spec-ed (s/explain-data spec sparql)
err-kws (err/spec-error-keywords spec-ed)
err-msg (err/spec-error-msg err-kws)
err-map (if spec-err?
spec-ed
(conform-sparql-err-map error-kw err-kws sparql))]
(throw (ex-info err-msg err-map)))
ast)))
([error-kw spec spec-err? sparql index]
(let [ast (s/conform spec sparql)]
(if (= ::s/invalid ast)
(let [spec-ed (s/explain-data spec sparql)
err-kws (err/spec-error-keywords spec-ed)
err-msg (err/spec-error-msg err-kws index)
err-map (if spec-err?
(assoc spec-ed
::index index)
(assoc (conform-sparql-err-map error-kw err-kws sparql)
:index index))]
(throw (ex-info err-msg err-map)))
ast))))
(def ^:private conform-query
(partial conform-sparql ::invalid-query qs/query-spec))
(def ^:private conform-update
(partial conform-sparql ::invalid-update us/update-spec))
(defn- assert-prefixes-err-map
[prefix-errs sparql ast]
{:kind ::invalid-prefixes
:errors prefix-errs
:input sparql
:ast ast})
(defn- assert-prefixes
([sparql ast nodes-m ?prefixes]
(let [prefixes (or ?prefixes {})]
(when-some [prefix-errs (vp/validate-prefixes prefixes nodes-m)]
(throw (ex-info (err/prefix-error-msg prefix-errs)
(assert-prefixes-err-map prefix-errs sparql ast))))))
([sparql ast nodes-m ?prefixes index]
(let [prefixes (or ?prefixes {})]
(when-some [prefix-errs (vp/validate-prefixes prefixes nodes-m)]
(throw (ex-info (err/prefix-error-msg prefix-errs index)
(assoc (assert-prefixes-err-map prefix-errs sparql ast)
:index index)))))))
(defn- assert-scope-err-map
[scope-errs sparql ast]
{:kind ::invalid-scoped-vars
:errors scope-errs
:input sparql
:ast ast})
(defn- assert-scoped-vars
([sparql ast nodes-m]
(when-some [errs (vs/validate-scoped-vars nodes-m)]
(throw (ex-info (err/scope-error-msg errs)
(assert-scope-err-map errs sparql ast)))))
([sparql ast nodes-m index]
(when-some [errs (vs/validate-scoped-vars nodes-m)]
(throw (ex-info (err/scope-error-msg errs index)
(assoc (assert-scope-err-map errs sparql ast)
:index index))))))
(defn- assert-aggregates-err-map
[agg-errs sparql ast]
{:kind ::invalid-aggregates
:errors agg-errs
:input sparql
:ast ast})
(defn- assert-aggregates
([sparql ast nodes-m]
(when-some [errs (va/validate-agg-selects nodes-m)]
(throw (ex-info (err/aggregate-error-msg errs)
(assert-aggregates-err-map errs sparql ast)))))
([sparql ast nodes-m index]
(when-some [errs (va/validate-agg-selects nodes-m)]
(throw (ex-info (err/aggregate-error-msg errs)
(assoc (assert-aggregates-err-map errs sparql ast)
:index index))))))
(defn- assert-bnode-err-map
[{:keys [kind errors prev-bnodes]} sparql ast]
(cond-> {:errors errors
:input sparql
:ast ast}
(= ::vb/dupe-bnodes-bgp kind)
(assoc :kind ::invalid-bnodes-bgp)
(= ::vb/dupe-bnodes-update kind)
(assoc :kind ::invalid-bnodes-update)
prev-bnodes
(assoc :prev-bnodes prev-bnodes)))
(defn- assert-bnodes
[sparql ast nodes-m]
(let [res (vb/validate-bnodes nodes-m)]
(when-some [errs (second res)]
(throw (ex-info (err/bnode-error-msg errs)
(assert-bnode-err-map errs sparql ast))))))
(defn- assert-bnodes-coll
[sparql-coll ast-coll nodes-m-coll]
(loop [inputs sparql-coll
asts ast-coll
nodes-ms nodes-m-coll
bnodes #{}
idx 0]
(when-some [nodes-m (first nodes-ms)]
(let [res (vb/validate-bnodes bnodes nodes-m)]
(if-some [errs (second res)]
(throw (ex-info (err/bnode-error-msg errs idx)
(assoc (assert-bnode-err-map errs
(first inputs)
(first asts))
:index idx)))
(recur (rest inputs)
(rest asts)
(rest nodes-ms)
(first res)
(inc idx)))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Public API Functions
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
(defn format-query
"Format `query` into a SPARQL Query string. Throws an exception if `query`
does not conform to spec or otherwise fails validation."
[query & {:keys [pretty? validate? spec-ed?] :or {pretty? false
validate? true
spec-ed? false}}]
(let [ast (conform-query spec-ed? query)
prefix-m (:prefixes query)
_ (when validate?
(let [nodes-m (v/collect-nodes ast)]
(assert-prefixes query ast nodes-m prefix-m)
(assert-scoped-vars query ast nodes-m)
(assert-aggregates query ast nodes-m)
(assert-bnodes query ast nodes-m)))
?xsd-pre (get-xsd-prefix prefix-m)
opt-m (cond-> {:pretty? pretty?}
?xsd-pre (assoc :xsd-prefix ?xsd-pre))]
(f/format-ast ast opt-m)))
;; `format-updates` is internally quite different from a simple coll
;; map, hence the need for a separate coll fn. Not only that, but the
;; two fns emit slightly different error messages.
(defn format-update
"Format `update` into a SPARQL Update string. Throws an exception if `update`
does not conform to spec or otherwise fails validation."
[update & {:keys [pretty? validate? spec-ed?] :or {pretty? false
validate? true
spec-ed? false}}]
(let [ast (conform-update spec-ed? update)
prefix-m (:prefixes update)
_ (when validate?
(let [nodes-m (v/collect-nodes ast)]
(assert-prefixes update ast nodes-m prefix-m)
(assert-scoped-vars update ast nodes-m)
(assert-aggregates update ast nodes-m)
(assert-bnodes update ast nodes-m)))
?xsd-pre (get-xsd-prefix prefix-m)
opt-m (cond-> {:pretty? pretty?}
?xsd-pre (assoc :xsd-prefix ?xsd-pre))]
(f/format-ast ast opt-m)))
(defn format-updates
"Format the coll `updates` into a SPARQL Update Request string. Throws
an exception if any update does not conform to spec or otherwise
fails validation."
[updates & {:keys [pretty? validate? spec-ed?] :or {pretty? false
validate? true
spec-ed? false}}]
(let [idxs (-> updates count range)
asts (map (partial conform-update spec-ed?) updates idxs)
pre-maps (reduce (fn [pm-coll {pm :prefixes :as _update}]
(let [last-pm (last pm-coll)
new-pm (merge last-pm pm)]
(conj pm-coll new-pm)))
[]
updates)
_ (when validate?
(let [nodes-m-coll (map v/collect-nodes asts)]
(dorun (map assert-prefixes updates asts nodes-m-coll pre-maps idxs))
(dorun (map assert-scoped-vars updates asts nodes-m-coll idxs))
(dorun (map assert-aggregates updates asts nodes-m-coll idxs))
(assert-bnodes-coll updates asts nodes-m-coll)))
xsd-pres (map get-xsd-prefix pre-maps)
opt-maps (map (fn [?xsd-pre]
(cond-> {:pretty? pretty?}
?xsd-pre (assoc :xsd-prefix ?xsd-pre)))
xsd-pres)]
(-> (map f/format-ast asts opt-maps)
(uf/join-updates pretty?))))