Coverage report: /development/source/library/org/datagraph/spocq-shard/src/algebra/operators/sparql-update-operators.lisp
| Kind | Covered | All | % |
| expression | 333 | 668 | 49.9 |
| branch | 21 | 68 | 30.9 |
Key
Not instrumented
Conditionalized out
Executed
Not executed
Both branches taken
One branch taken
Neither branch taken
1
;;; -*- Mode: lisp; Syntax: ansi-common-lisp; Base: 10; Package: org.datagraph.spocq.implementation; -*-
3
(in-package :org.datagraph.spocq.implementation)
5
(:documentation "This file defines the standard SPARQL update operators for the 'org.datagraph.spocq' RDF engine."
8
"Copyright 2011 [james anderson](mailto:james@datagraph.org) All Rights Reserved.")
11
"The file defines the update operators for the Dydra Sparql Algebra Engine from the 'SPARQL 1.1 Update'[1]
14
As with the query operators, each is defined in two aspects. A macro in the specification package
15
(spocq.a) implements any control and evaluation order transformations and rewrites the expression in terms
16
of an operator in the evaluation package (spocq.e). That function implements the operation either directly -
17
for immediate data operations like insert-data and delete-data, or as a construct which streams intermediate
18
results from a select phrase to a modify phrase.
20
[1]: http://www.w3.org/TR/sparql11-update"))
23
(defmacro spocq.a:|add| (from-graph to-graph &key verbose)
24
"Add the contents of from-graph of the current task's repository to that if its to to-graph.
25
Each may be either an iri or the keyword :default."
26
`(spocq.e::add-graph ,from-graph ,to-graph ,@(if verbose '(:if-does-not-exist :error))))
28
(defun spocq.e:add-graph (from-graph to-graph &rest args &key if-does-not-exist)
29
"Add the contents of from-graph of the current task's repository to that if its to-graph.
30
Each may be either an iri or the keyword :default."
31
(declare (dynamic-extent args)
32
(ignore if-does-not-exist))
33
(apply #'repository-add-graph *transaction* from-graph to-graph args)
37
(defmacro spocq.a:|clear| (graph &key verbose)
38
"Clear the content of the given graph in the current task's repository. The graphs value may be
39
either an iri or one of the keywords
40
:default : to clear the repository's default graph
41
:all : to clear all of the repository's graphs
42
:named : to clear all of the repository's named graphs"
43
(setf (variable-opacity (expression-variables graph)) :transparent)
44
`(spocq.e:clear-graph ,graph ,@(if verbose '(:if-does-not-exist :error))))
46
(defun spocq.e:clear-graph (graph-designator &rest args &key if-does-not-exist)
47
(declare (dynamic-extent args)
48
(ignore if-does-not-exist))
49
(apply #'repository-clear-graph *transaction* graph-designator args)
53
(defmacro spocq.a:|copy| (from-graph to-graph &key verbose)
54
`(spocq.e:copy-graph ,from-graph ,to-graph ,@(if verbose '(:if-does-not-exist :error))))
56
(defun spocq.e:copy-graph (from-graph to-graph &rest args &key if-does-not-exist)
57
(declare (dynamic-extent args)
58
(ignore if-does-not-exist))
59
(apply #'repository-copy-graph *transaction* from-graph to-graph args)
63
(defmacro spocq.a:|create| (graph &key verbose)
64
(setf (variable-opacity (expression-variables graph)) :transparent)
65
`(spocq.e:create-graph ,graph ,@(if verbose '(:if-exists :error))))
67
(defun spocq.e:create-graph (graph-designator &rest args &key if-exists)
68
"Check for existence and optionally signal an error if the count is non-zero.
69
If no statements are found or silent, return true to indicate 'success'.
70
If statements are found and non-silent, signal an error."
71
(declare (dynamic-extent args)
73
(apply #'repository-create-graph *transaction* graph-designator args)
77
(defmacro spocq.a:|deleteData| (quad-or-triple-data)
78
;; data is intended to be constant
79
(validate-quad-data quad-or-triple-data :delete)
80
`(spocq.e:delete-data (quote ,quad-or-triple-data)))
82
(defgeneric spocq.e:delete-data (quad-or-triple-field)
83
(:method ((null-field null))
86
(:method ((quad-or-triple-data list))
87
(spocq.e:delete-data (intern-quad-or-triple-data *repository* quad-or-triple-data)))
89
(:method ((fields vector))
90
(loop for field across fields
91
unless (equalp spocq.a:|true| (spocq.e:delete-data field))
92
return spocq.a:|false|
93
finally (return spocq.a:|true|)))
95
(:method ((term-field array))
96
(repository-delete-field *repository* term-field)
99
(:method ((source pathname))
100
(let ((type (pathname-type source)))
101
(cond ((or (string-equal type "nq") (string-equal type "nt"))
102
(spocq.e:delete-data (parse-nquads-to-cspo source)))
103
((string-equal type "cspo")
104
(spocq.e:delete-data (parse-nquads source)))
106
(error "spocq.e:delete-data: RDF encoding not supported: ~a" type))))))
109
(defmacro spocq.a:|deleteWhere| (quad-pattern)
110
"Given a list of quad patterns, use them both to match against the store and to construct the
111
statements to be deleted."
112
(macroexpand-delete-where quad-pattern))
115
(defun macroexpand-delete-where (quad-pattern)
116
"Generate a modify form by rewriting the quad-pattern, which is a list of bgp triple templates
117
and/or graph forms, into a valid group graph pattern for the modify's where argument with
118
the original quad pattern passed as the :delete clause."
120
(labels ((rewrite-quad-pattern (expression)
121
(if (consp (first expression))
122
(if (eq 'spocq.a:|triple| (caar expression))
123
(let* ((pattern-rest (member 'spocq.a:|triple| expression :test-not #'eq :key #'first))
124
(bgp-triples (ldiff expression pattern-rest ))
125
(bgp `(spocq.a:|bgp| ,@(copy-tree bgp-triples))))
127
`(spocq.a:|join| ,bgp ,(rewrite-quad-pattern pattern-rest))
129
(if (rest expression)
130
`(spocq.a:|join| ,(rewrite-quad-pattern (first expression))
131
,(rewrite-quad-pattern (rest expression)))
132
(rewrite-quad-pattern (first expression))))
133
(ecase (first expression)
134
(spocq.a:|graph| (destructuring-bind (graph triples) (cdr expression)
135
`(spocq.a:|bgp| (spocq.a:|graph| ,graph) ,@(copy-tree triples))))
136
(spocq.a:|bgp| (copy-tree expression))))))
137
`(spocq.a:|modify| ,(rewrite-quad-pattern quad-pattern) :delete ,quad-pattern)))
140
(defmacro spocq.a:|drop| (graph &key verbose)
141
(setf (variable-opacity (expression-variables graph)) :transparent)
142
`(spocq.e:drop-graph ,graph ,@(if verbose '(:if-does-not-exist :error))))
144
(defun spocq.e:drop-graph (graph-designator &rest args &key if-does-not-exist)
145
(declare (dynamic-extent args)
146
(ignore if-does-not-exist))
147
(apply #'repository-delete-graph *transaction* graph-designator args)
151
(defmacro spocq.a:|insertData| (quad-or-triple-data)
152
;; data is intended to be constant
153
(validate-quad-data quad-or-triple-data :insert)
154
`(spocq.e:insert-data (quote ,quad-or-triple-data)))
156
(defgeneric spocq.e:insert-data (quad-data)
157
(:method ((null-field null))
160
(:method ((quad-or-triple-data cons))
161
(spocq.e:insert-data (intern-quad-or-triple-data *repository* quad-or-triple-data)))
163
(:method ((fields vector))
164
(loop for field across fields
165
unless (equalp spocq.a:|true| (spocq.e:insert-data field))
166
return spocq.a:|false|
167
finally (return spocq.a:|true|)))
169
(:method ((term-field array))
170
(repository-insert-field *repository* term-field)
173
(:method ((source pathname))
174
(let ((type (pathname-type source)))
175
(cond ((or (string-equal type "nq") (string-equal type "nt"))
176
(spocq.e:insert-data (parse-nquads-to-cspo source)))
177
((string-equal type "cspo")
178
(spocq.e:insert-data (parse-nquads source)))
180
(error "spocq.e:insert-data: RDF encoding not supported: ~a" type))))))
183
(defmacro spocq.a:|load| (from-location graph-designator &rest args &key verbose)
184
(declare (ignore verbose))
185
(setf (variable-opacity (expression-variables from-location)) :transparent)
186
(setf (variable-opacity (expression-variables graph-designator)) :transparent)
187
`(spocq.e:load-graph ,from-location ,graph-designator ,@args))
189
(defmethod spocq.e:load-graph (from-location (graph-designator t) &key (verbose t))
190
(when *transaction* ; record provenance detail
191
(let ((to-graph-term-number (repository-graph-term-number *transaction* graph-designator :allow-all t)))
192
(if (repository-pattern-match-p *transaction* nil nil nil graph-designator)
193
(setf (transaction-graph-id-modified *transaction* to-graph-term-number) t)
194
(setf (transaction-graph-id-created *transaction* to-graph-term-number) t))))
196
(repository-load-graph *transaction* from-location graph-designator)
197
(ignore-errors (repository-load-graph *transaction* from-location graph-designator)))
201
(defmacro spocq.a:|modify| (field-expression &rest args &key delete insert with graphs named-graphs
203
(declare (ignore delete insert with graphs named-graphs))
204
(apply #'macroexpand-modify field-expression :environment env args))
206
(defun macroexpand-modify (field-expression &key delete insert with graphs named-graphs environment)
207
(declare (ignore environment))
208
(flet ((augment-bgps (expression graphs named-graphs)
209
(flet ((augment-bgp (expression)
211
(cons (case (first expression)
212
(spocq.a:|bgp| `(spocq.a:|bgp|
213
,@(when (and named-graphs (not (assoc 'spocq.a:|from-named| (rest expression))))
214
`((spocq.a:|from-named| ,@named-graphs)))
215
,@(when (and graphs (not (assoc 'spocq.a:|from| (rest expression))))
216
`((spocq.a:|from| ,@graphs)))
217
,@(rest expression)))
218
(spocq.a:|graph| `(spocq.a:|graph| ,@(rest expression)))
221
(declare (dynamic-extent #'augment-bgp))
222
(map-tree #'augment-bgp expression)))
223
(augment-triples (expressions graph)
224
(loop for expression in expressions
225
collect (if (triple-form-p expression)
226
`(spocq.a:|quad| ,@(rest expression) ,graph)
228
;; as per the 1.1 modify description [http://www.w3.org/TR/sparql11-update/#deleteInsert], the from/from-named
229
;; apply to the retrieval caluse while the with applies to both, whereby the from/from-named take precedence in the
231
(when (or graphs named-graphs)
232
(setf field-expression (augment-bgps field-expression graphs named-graphs)))
234
;; this would allows for only those constructors which include nothing additional in their where clause
235
;; and would not allow for recursive consequences
236
;; see map-modified-resources
237
(setf delete (compute-entailed-pattern (task-repository *task*) delete (query-entailment-method *task*)
239
:class <spin:constructor>))
240
(setf insert (compute-entailed-pattern (task-repository *task*) insert (query-entailment-method *task*)
242
:class <spin:constructor>))
245
(setf field-expression (augment-bgps field-expression (list with) nil))
246
(setf delete (augment-triples delete with))
247
(setf insert (augment-triples insert with)))
248
`(spocq.e::modify ,(if (select-form-p field-expression) field-expression `(spocq.a:|select| ,field-expression *))
249
,@(when delete `(:delete ',delete))
250
,@(when insert `(:insert ',insert)))))
252
(defgeneric spocq.e:modify (solution-field &key delete insert)
253
(:documentation "Accept a solution field, together with patterns for deletion and insertion,
254
iterate over the solutions, projecting each onto first the deletion pattern and then the insertion
255
pattern and effecting each modification in turn.
256
nb. http://www.w3.org/TR/sparql11-update/#def_deleteinsertoperation indicates that the deletion
257
and insertion stages are distinct. that is, all deletion operations are performed prior to the insertions.
258
no WITH clause is supported, since it must already have been apparent in the solution-field result,
259
the DELETE and INSERT clauses are handled symmetrically by the macro-expansion.")
261
(:method ((field agp) &rest args)
262
(declare (dynamic-extent args))
263
(apply #'spocq.e::modify (agp-generator field) args))
265
(:method ((solution-field solution-generator) &rest args)
266
(declare (dynamic-extent args))
267
;; double-check access configuration
268
(assert (api-authorized-p *api-access-mode* |acl|:|Write|) ()
269
"Write access not permitted.")
270
(apply #'spocq.e:stream-modify solution-field args)))
272
(defun spocq.e:stream-modify (field-generator &rest args &key delete insert)
273
(declare (ignore delete insert))
274
(let* ((base-dimensions (solution-generator-dimensions field-generator))
275
(result-channel (make-unit-table-channel :name (list 'spocq.a:|modify| (task-id *query*)))))
276
(labels ((run-modify-thread (result-channel field-generator)
277
(let ((base-channel (solution-generator-channel field-generator))
278
(base-expression (solution-generator-expression field-generator))
279
(*thread-operations* (cons (list* 'spocq.a:|modify| (task-id *task*)
282
*thread-operations*)))
283
(push 'spocq.a:|modify| (channel-name base-channel))
284
;; delegate the base operation to another thread, but
285
(query-run-in-thread *query* base-expression)
286
;; keep the modify aspect in this one
287
(apply #'process-modify result-channel base-channel
291
;; return the binding function to the combination operator
292
(make-boolean-generator :expression (list #'run-modify-thread result-channel field-generator)
293
:channel result-channel))))
295
(defparameter *update-cache-limit* 1024)
297
(defun process-modify (destination base-source base-dimensions &key delete insert)
298
(incf-stat *algebra-operations*)
299
(trace-algebra process-modify base-source base-dimensions delete insert)
302
(validate-quad-pattern delete :delete)
303
(validate-quad-pattern insert :insert)
304
(multiple-value-bind (solution-cached-p delete-constructor delete-length delete-width
305
insert-constructor insert-length insert-width)
306
(compute-modify-operators base-dimensions delete insert)
307
(let* ((pattern-page nil)
308
(pattern-page-width 0)
309
(pattern-page-length 0)
310
(pattern-page-index 0)
312
(base-cache (make-term-id-cache :single-thread t))
315
(labels ((delete-processor (base-page)
316
(trace-algebra process-modify.delete base-page (term-value-field base-page))
317
(dotimes (base-index (array-dimension base-page 0))
318
(unless (funcall solution-cached-p base-page base-index base-cache)
319
(unless (= (array-dimension pattern-page 0) delete-length)
320
(setf pattern-page (make-page delete-length delete-width)))
321
(setf pattern-page-index 0)
322
(funcall delete-constructor #'collector base-page base-index)
323
(when (plusp pattern-page-index)
324
(unless (= pattern-page-index delete-length)
326
(adjust-array pattern-page (list pattern-page-index delete-width))))
327
(repository-delete-field (task-repository *query*) pattern-page)))))
328
(insert-processor (base-page)
329
(trace-algebra process-modify.insert base-page (term-value-field base-page))
330
(dotimes (base-index (array-dimension base-page 0))
331
(unless (funcall solution-cached-p base-page base-index base-cache)
332
(unless (= (array-dimension pattern-page 0) insert-length)
333
(setf pattern-page (make-page insert-length insert-width)))
334
(setf pattern-page-index 0)
335
(funcall insert-constructor #'collector base-page base-index)
336
(when (plusp pattern-page-index)
337
(unless (= pattern-page-index insert-length)
339
(adjust-array pattern-page (list pattern-page-index insert-width))))
340
(repository-insert-field (task-repository *query*) pattern-page)))))
341
(collector (&rest term-id-list)
342
(declare (dynamic-extent term-id-list))
343
(assert (< pattern-page-index pattern-page-length) ()
344
"Pattern page length exceeded.")
345
(trace-algebra process-modify.collector pattern-page-index term-id-list)
346
(loop for i from 0 below pattern-page-width
347
for id in term-id-list
348
do (if (= id +null-term-id+)
349
(return-from collector) ;; the default graph == +null-term-id+
350
(setf (aref pattern-page pattern-page-index i) id)))
351
(incf pattern-page-index)))
352
(declare (dynamic-extent #'collector))
353
;; if deletion was specified, that happens first with a running cache for the base field
354
(when delete-constructor
355
(setf pattern-page (make-page delete-length delete-width))
356
(setf pattern-page-width delete-width
357
pattern-page-length delete-length)
358
(do-pages (solution-page base-source)
359
(push (copy-page solution-page) solution-pages)
360
(delete-processor solution-page)
361
(when (> (hash-table-count base-cache) *update-cache-limit*)
362
(clrhash base-cache))
363
(incf solution-count (array-dimension solution-page 0))))
364
(when insert-constructor
366
(setf pattern-page (make-page insert-length insert-width))
367
(setf pattern-page-width insert-width
368
pattern-page-length insert-length)
369
(if delete-constructor
370
;; reuse the cached field
371
(loop for solution-page in (reverse solution-pages)
372
do (progn (check-query-status *query*)
373
(insert-processor solution-page)
374
(when (> (hash-table-count base-cache) *update-cache-limit*)
375
(clrhash base-cache))
376
(incf solution-count (array-dimension solution-page 0))))
377
;; process the base field from the source
378
(do-pages (solution-page base-source)
379
(check-query-status *query*)
380
(insert-processor solution-page)
381
(incf solution-count (array-dimension solution-page 0))))))
382
(put-field-page destination (unit-table))
383
(complete-field destination)
384
(incf-stat *solutions-processed* solution-count)
385
(incf-stat *solutions-constructed* result-count)
386
(values solution-count result-count))))
388
(defun compute-modify-operators (base-dimensions delete-pattern insert-pattern)
389
(multiple-value-bind (delete-constructor delete-length delete-width)
390
(when delete-pattern (compute-insert-pattern-constructor base-dimensions delete-pattern))
391
(multiple-value-bind (insert-constructor insert-length insert-width)
392
(when insert-pattern (compute-insert-pattern-constructor base-dimensions insert-pattern))
393
(values (compute-flag-cache-op base-dimensions)
394
delete-constructor delete-length delete-width
395
insert-constructor insert-length insert-width))))
398
(defmacro spocq.a:|move| (from-graph to-graph &key verbose)
399
`(spocq.e:move-graph ,from-graph ,to-graph ,@(when verbose '(:if-does-not-exist :error))))
401
(defun spocq.e:move-graph (from-graph to-graph &rest args &key if-does-not-exist)
402
(declare (dynamic-extent args)
403
(ignore if-does-not-exist))
404
(apply #'repository-move-graph *transaction* from-graph to-graph args)
408
(defmacro spocq.a::|update| (&rest operations)
409
(apply #'macroexpand-update operations))
411
#+(or) ;; the originl version would have initiated any modifies in parallel
412
(defun macroexpand-update (&rest operations)
413
`(spocq.e:update ,@(loop for operation in operations
414
for (operator . nil) = operation
415
;; delay all executionn until the transaction context is established
416
collect `(lambda () ,operation))))
418
(defun macroexpand-update (&rest operations)
419
`(spocq.e:update ,@(loop for operation in operations
420
for (operator . nil) = operation
421
collect `(lambda () ,operation))))
423
(defun spocq.e:update (&rest results-and-generators)
424
(trace-algebra update results-and-generators)
425
;; double-check access
426
(assert (api-authorized-p *api-access-mode* |acl|:|Write|) ()
427
"Write access not permitted.")
428
(let ((result-channel (make-unit-table-channel :name (list 'spocq.a::|update| (task-id *query*)))))
429
(make-boolean-generator :expression (list #'run-update-thread result-channel results-and-generators)
430
:channel result-channel)))
432
(defun run-update-thread (result-channel results-and-generators)
434
(trace-algebra update.fail)
435
(complete-field result-channel)
438
(trace-algebra update.succeed)
439
(put-field-page result-channel (unit-table))
440
(complete-field result-channel)
442
(test-update-result (result)
443
(trace-algebra update.test result)
446
(equalp spocq.a:|true| result))
447
(abstract-field-generator
448
;; invoke the generator form directly, rather than delegating to another
449
;; thread in order to keep the same transaction context and to serialize
451
(let ((base-channel (abstract-field-generator-channel result))
452
(expression (abstract-field-generator-expression result)))
454
(trace-algebra run-update-thread :update expression)
455
(apply (first expression) (rest expression)))
456
(not (null (get-field-page base-channel)))))
458
;; initiate deferred execution to ensure the order and the transaction context
459
(test-update-result (funcall result))))))
460
;; nb. this is the only location which as a matter of course should specify to commit
461
;; this is the only thread which should write and everyplace else should close with :continue
462
(if (spocq.e::with-transaction (:normal-disposition :commit)
463
(loop for result in results-and-generators
464
unless (test-update-result result)
465
do (abort-transaction "update step failed: ~a." result)
466
finally (let* ((view-name (task-commit-constraint *query*)))
467
(when (stringp view-name)
468
(let ((test-text (or (authorized-repository-view *repository* view-name (task-agent *query*))
469
(spocq.e:resource-not-found-error :identifier view-name))))
470
(unless (run-precommit-test test-text *repository*)
471
(abort-transaction "commit constraint failed: ~a." view-name)
478
;;; (trace run-update-thread )