Coverage report: /development/source/library/org/datagraph/spocq-shard/src/store/rlmdb/spocq-classes.lisp
| Kind | Covered | All | % |
| expression | 716 | 1655 | 43.3 |
| branch | 43 | 136 | 31.6 |
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 "SPOCQ lmdb extensions"
6
"Specialize repository, revision and transaction classes
7
to refer to rlmdb wrappers for lmdb entities.
8
Class definitions are in core/rlmdb.
11
- lmdb-repository : binds a rlmdb:repository instance
12
as a reference to the respective lmdb environment and to its databases.
13
Open the environment when initializing and close it when finalizing.
14
This remains the only reference to the environment - all others use
15
it, but do not modify its condition.
16
The storage schema is specified as the storage-class, which is one of
17
- rlmdb:repository : spog indices, when contingently revisioned with cardinality maps
18
- rlmdb:temporal-repository : revision-identifier to quad graph index, spog ignored
19
- rlmdb:replicable-repository : spog indices with revision-identifier maps
20
- lmdb-revision : specialized in order to use an lmdb-transaction.
21
binds also the respective repository's lmdb-repository, but does not
23
- lmdb-transaction : specialized in order to bind a reference to the
24
lmdb repository, from which the environment is then used to establish an
25
lmdb transaction, as required.
28
The lmdb practice is that an environment remain open for a process' duration.
29
On the other hand, each must be closed in order to release lmdb resources,
30
which are otherwise eventually exhausted. This occurs in terms of either
31
environments or databases at a relatively low limit.
32
The practice is also that a database be opened on the first occasion and then
33
remain open until the environment is closed. In order to effect this, the
34
respective transaction must be committed - even when it is read-only, while
35
the database remain open, after which the database is re-used in other
37
The practice with transactions is that they are created on-demand and must be
38
closed in order to release cursors - whereby cursors in a read-only
39
transaction must be closed explicitly.
41
This leads to the following pattern:
43
-> spocq:lmdb-repository
45
-> lmdb:environment : finalize closes lmdb entity iff still open
46
-> spocq:lmdb-revision
47
-> spocq:lmdb-repository
48
-> rlmdb:repository : cloned from its referent repository
49
-> spocq:lmdb-transaction : destroy transaction closes
52
This leads to two different processing patterns
56
the former arranges for the lmdb entity to have dynamic extent: the entity is
57
created/opened, as needed, upon entry and closed/destroyed upon exit iff it
59
the latter arranges for the entity to have indefinite extent: the entity is
60
created/opened, as needed, upon entry, but left intact upon exit.
62
this is reflected in individual operations as follows:
63
- autonomous operations use with-open-transaction.
64
this creates and manages a dynamic transaction for the cases where it should
65
be closed. eg. when opening the repository, the transaction must be closed in
66
order to afford the repository databases indefinite extent.
67
- retrieval operation use ensure-open-transaction with the single
68
lmdb:transaction bound into the spocq transaction.
69
this creates an lmdb entity with indefinite extent which is reused for all
70
get/put/cursor operations to cause all to use the same lmdb state.
72
access operations must support two modes:
73
- in the context of an lmdb-repository, establish a dynamic transaction
74
wrt the lmdb environment for the databases to reuse. commit it at the
75
conclusion in order to leave them intact.
76
- in the context of an lmdb-transaction, use its lmdb entity directly
77
for databases and leave it unchanged at the conclusion.
79
The constaint on environment extent affects the logic in
80
initialize-repository-storage. It mus take case to close the environment
81
which is uses to create the sub-databases.
82
If it is not, it is observed that the access to the revision information
83
for a subsequently instantiated repository's environment will yield, of all
84
things, the record from the meta database of the <account>/system repository,
85
which had been accessed immediatly prior.
86
The lmdb [documantation](http://www.lmdb.tech/doc/) does advise:
87
'Do not have open an LMDB database twice in the same process at the same time.'
88
but it does not indicate that the behaviour is that poorly defined.
92
(defmethod make-ephemeral-repository (&rest args)
93
(apply #'make-instance 'lmdb-ephemeral-repository
94
:open-flags (+ LIBLMDB:+NOTLS+ LIBLMDB:+NOSYNC+)
97
(defmethod initialize-instance :after ((instance lmdb-repository) &rest initargs)
98
(declare (dynamic-extent initargs))
99
;;; class-llocate this
100
;;; check for a special storage class before creating the instance
101
;;; (setf-repository-storage-class (repository-storage-class instance) instance)
102
(let ((rlmdb:repository (apply #'compute-reference-lmdb-repository instance initargs)))
103
(rlmdb:open-repository rlmdb:repository)
104
(setf-repository-lmdb-repository rlmdb:repository instance)))
106
(defmethod clear-repository ((repository lmdb-repository) &rest args)
107
(apply #'rlmdb:clear-repository (repository-lmdb-repository repository) args))
109
(defmethod repository-last-revision ((repository lmdb-repository))
110
(rlmdb:find-last-ordinal repository))
112
(defmethod repository-write-date ((repository lmdb-repository))
113
"return the universal time for the last write"
114
(let* ((timestamp (rlmdb:get-metadata-timestamp repository)))
116
(rlmdb:timeline-location-date-time timestamp))))
118
(defmethod repository-write-timestamp ((repository lmdb-repository))
119
"return the universal time for the last write"
120
(rlmdb:get-metadata-timestamp repository))
122
(defmethod repository-object-term-number ((repository lmdb-repository) object)
123
(rlmdb:value-term-number object))
126
(defmethod compute-reference-lmdb-repository ((repository lmdb-repository) &rest args
128
(repository-id (repository-id repository))
130
;; construct an lmdb environment with reference to this repository
131
;; open it once for the base repository
132
(let ((rlmdb:repository (apply #'make-instance (repository-storage-class repository)
133
:repository-id repository-id
137
(defmethod resolve-repository-revision-id ((repository lmdb-repository) &key (revision "HEAD") (if-does-not-exist :error))
138
(let* ((lmdb-repository (repository-lmdb-repository repository))
139
(uuid (if (typecase revision
140
(string (equalp revision "HEAD"))
142
(symbol (eql revision :head)))
143
(rlmdb:get-metadata-uuid lmdb-repository) ;; should always return something
144
(rlmdb:find-revision-uuid lmdb-repository revision :if-does-not-exist if-does-not-exist))))
146
(string-downcase uuid))))
149
(defmethod compute-reference-lmdb-repository ((reference repository-revision) &key
151
;; copy the instance from the respective revision's referent repository
153
(repository-lmdb-repository (repository-revision-reference reference)))
155
;; given the above, not strictly necessary
156
(defmethod repository-lmdb-repository ((revision repository-revision))
157
(repository-lmdb-repository (repository-revision-reference revision)))
159
(defmethod repository-write-date ((repository lmdb-revision))
160
"return the universal time for the revision end timestamp"
161
(repository-revision-write-date repository (repository-revision-id repository)))
163
(defmethod repository-write-timestamp ((repository lmdb-revision))
164
"return the universal time for the revision end timestamp"
165
(repository-revision-write-timestamp repository (repository-revision-id repository)))
167
(defmethod repository-revision-write-date ((repository lmdb-repository) (revision-id string))
168
(let* ((timestamp (rlmdb:get-revision-timestamp repository revision-id)))
170
(rlmdb:timeline-location-date-time timestamp))))
172
(defmethod repository-revision-write-timestamp ((repository lmdb-repository) (revision-id string))
173
(rlmdb:get-revision-timestamp repository revision-id))
175
(defmethod revision-relation ((revision lmdb-revision))
176
"The default relation ensures the statement visibility includes the specified location.
177
Where just the start is specified, that serves also as the end."
178
(or (call-next-method)
179
(setf (revision-relation revision)
180
(let ((min-id (revision-min-revision-ordinal revision))
181
(max-id (revision-max-revision-ordinal revision)))
183
`(|time|::|versionIncludes| ,min-id ,min-id)
184
`(|time|::|versionIncludes| ,min-id ,max-id))))))
187
(defmethod initialize-instance ((instance lmdb-transaction) &rest initargs
189
(min-revision-ordinal (revision-min-revision-ordinal revision))
190
(max-revision-ordinal (or (revision-max-revision-ordinal revision) min-revision-ordinal)))
191
(declare (dynamic-extent initargs))
192
(apply #'call-next-method instance
193
:min-revision-ordinal min-revision-ordinal
194
:max-revision-ordinal max-revision-ordinal
196
(setf-transaction-lmdb-transaction nil instance))
198
(when (transaction-read-only-p instance)
199
(let* ((rlmdb:repository (repository-lmdb-repository revision))
200
(lmdb:transaction (lmdb:make-transaction rlmdb:repository)))
201
;; created, but not opened
202
(setf-transaction-lmdb-transaction lmdb:transaction instance))))
205
(defun revision-max-uuid () (rlmdb:revision-record-uuid (transaction-max-revision-record *transaction*)))
206
(defun revision-min-uuid () (rlmdb:revision-record-uuid (transaction-min-revision-record *transaction*)))
207
(defun revision-max-timestamp () (rlmdb:revision-record-uuid (transaction-max-revision-record *transaction*)))
208
(defun revision-min-timestamp () (rlmdb:revision-record-uuid (transaction-min-revision-record *transaction*)))
209
(defun revision-max-ordinal () (transaction-max-revision-ordinal *transaction*))
210
(defun revision-min-ordinal () (transaction-min-revision-ordinal *transaction*))
212
(defmethod rlmdb:get-revision-record ((transaction lmdb-transaction) ordinal)
213
(rlmdb:get-revision-record (transaction-lmdb-repository transaction) ordinal))
215
(defgeneric revision-max-revision-id (revision)
216
(:method ((revision lmdb-revision))
217
(let ((record (revision-max-revision-record revision)))
219
(rlmdb:revision-record-uuid record))))
220
(:method ((revision repository-revision))
221
(repository-revision-id revision)))
223
(defgeneric revision-min-revision-id (revision)
224
(:method ((revision lmdb-revision))
225
(let ((record (revision-min-revision-record revision)))
227
(rlmdb:revision-record-uuid record))))
228
(:method ((revision repository-revision))
229
(repository-revision-id revision)))
231
(defmethod destroy-transaction ((transaction lmdb-transaction))
232
;;There is no locking here, as the generic function handles it.
234
(let ((lmdb:transaction (transaction-lmdb-transaction transaction)))
235
(when (lmdb:open-p lmdb:transaction)
236
;; if at this point it is still open, abort it
237
(lmdb:abort-transaction lmdb:transaction))))
239
(defmethod transaction-write-event ((transaction lmdb-transaction))
240
"In this case, rdfcache still handles the write transaction - even for revisioned repositories.
241
This means it has written the metadata and revision log records."
242
(let* ((revision-id (transaction-id transaction))
243
(repository (transaction-repository transaction))
244
(agent (when *task* (task-agent *task*)))
245
(agent-id (agent-name agent))
246
(agent-tag (when *task* (task-user-tag *task*))))
247
(flet ((ste (start-time end-time insert remove)
248
(store-transaction-event :revision-id revision-id
249
:task-id (or (task-id *task*) revision-id)
250
:timestamp-start start-time
251
:timestamp-end end-time
252
:repository repository
257
(if (repository-is-revisioned repository)
258
(let* ((rlr (rlmdb:get-revision-log-record repository revision-id)))
260
(ste (when rlr (timeline-location-date-time (rlmdb:revision-log-record-timestamp-begun rlr)))
261
(when rlr (timeline-location-date-time (rlmdb:revision-log-record-timestamp rlr)))
262
(when rlr (rlmdb:revision-log-record-inserted-count rlr))
263
(when rlr (rlmdb:revision-log-record-removed-count rlr)))
264
(log-warn "transaction-write-record: no revision log record: ~s ~s" repository revision-id)))
265
(let* ((mr (rlmdb:get-metadata-record repository)))
267
(let ((timestamp (timeline-location-date-time (rlmdb:metadata-record-timestamp mr))))
268
(ste timestamp timestamp nil nil))
269
(log-warn "transaction-write-record: no metadata record: ~s" repository)))))))
271
(defmethod repository-list-revision-ids ((repository lmdb-repository))
272
(or (rlmdb:get-revision-uuids (repository-lmdb-repository repository))
273
(let ((only-one-uuid (rlmdb:get-metadata-uuid repository)))
274
(when only-one-uuid (list only-one-uuid)))))
276
(defmethod initialize-repository-storage ((repository-id string) (prototype lmdb-repository) &key)
277
"Given the root lmdb database, create a temporary reference repository and
278
open it with :create. This causes it to create the sub-databases respective
280
Creating the sub-databases also stores the initial meta record, the class
281
property and an eventual revision record.
283
nb. Close it in order that it does not linger (see the opening notes, above)."
285
(let ((storage (compute-reference-lmdb-repository prototype :repository-id repository-id)))
286
(assert (validate-storage-class prototype storage) ()
287
"The repository and storage classes are not compatible: ~s ~s"
288
(type-of prototype) (type-of storage))
289
(lmdb:open-environment storage :if-does-not-exist :create :class (type-of prototype))
290
(lmdb:close-environment storage))
293
(defmethod initialize-repository-storage ((repository-id string) (prototype lmdb-temporal-repository) &key temporal-properties)
295
(assert (or (stringp temporal-properties)
296
(and (consp temporal-properties) (every #'iri-p temporal-properties)))
298
"initialize-repository-storage(lmdb-temporal-repository): temporal-properties is required.")
299
(etypecase temporal-properties
301
(cons (setf temporal-properties
302
(format nil "~{~a~^ ~}" (loop for property in temporal-properties collect (iri-lexical-form property))))))
303
(rlmdb:put-metadata repository-id
304
(list "temporal-properties" temporal-properties)))
306
(defmethod initialize-repository-storage ((repository-id string) (prototype lmdb-time-series-repository) &key time-series-properties)
308
(assert (or (stringp time-series-properties)
309
(and (consp time-series-properties) (every #'iri-p time-series-properties))) ()
310
"initialize-repository-storage(lmdb-time-series-repository): time-series-properties is required.")
311
(etypecase time-series-properties
313
(cons (setf time-series-properties
314
(format nil "~{~a~^ ~}" (loop for property in time-series-properties collect (iri-lexical-form property))))))
315
(rlmdb:put-metadata repository-id
316
(list "time-series-properties" time-series-properties)))
319
(defmethod repository-revision-bounds ((repository lmdb-repository))
320
(when (repository-is-revisioned repository)
321
(let ((start (rlmdb:find-revision-timestamp repository :tail))
322
(end (rlmdb:find-revision-timestamp repository :head)))
323
(spocq:make-date-time-interval :start (timeline-location-date-time start)
324
:end (timeline-location-date-time end)))))
326
(defmethod compute-repository-revision ((reference lmdb-repository) (revision-designator integer) &rest args)
327
(declare (dynamic-extent args))
328
(apply #'compute-repository-revision reference (rlmdb:find-revision-uuid reference revision-designator)
331
(defmethod compute-repository-revision ((reference lmdb-repository) (revision-designator string)
332
&key (if-does-not-exist :error)
333
(class (repository-revision-class reference))
334
(revision-class class))
335
"The lmdb-repository version deconstructs the revision designator, resolves
336
bounds to concrete min/max ordinals and records, resolves and window/repeat
337
intervals to ordinals or temporals and locates or instantiates the located instance.
338
based on the properties, assert the proper modes"
339
(multiple-value-bind (min-designator min-offset max-designator max-offset window-interval repeat-interval repeat-count)
340
(rlmdb:parse-compound-revision-designator revision-designator)
341
;; require that the designator was parsed
342
(unless min-designator
343
(error 'spocq.e:revision-invalid-error :datum revision-designator))
344
;; given the explicit revision specification
345
;; - if repetition is specified, convert a date-time interval to a duration
346
;; - if no repetition is specified, convert a duration interval to an end time
347
(let* ((lmdb-repository (repository-lmdb-repository reference))
348
(min-record (rlmdb:find-revision-record lmdb-repository min-designator
349
:if-does-not-exist if-does-not-exist
353
;; resolve to either min+max or min+internval+repeat
354
(cond ((null min-record)
355
;; the repository is _completely_ empty?
356
(spocq.e:revision-not-found-error :identifier revision-designator))
357
((and repeat-interval max-designator window-interval repeat-count)
358
;; repeat-count and maximum and window is over-constrained
359
(spocq.e:revision-invalid-error :identifier revision-designator))
360
((and repeat-interval max-designator window-interval)
361
;; this is a repeated window to a limit
362
(push :retrospective modes)
363
(push :interval modes)
364
(setf max-record (rlmdb:find-revision-record lmdb-repository max-designator
365
:offset max-offset)))
366
((and repeat-interval window-interval repeat-count)
367
;; this is a repeated window to a count
368
(cond ((equalp min-designator "HEAD") (push :prospective modes))
369
(t (push :retrospective modes)))
370
(push :interval modes))
371
((and repeat-interval max-designator repeat-count)
372
;; this is a repeated window to a count, but the window interval is
373
;; expressed as an initial upper bound
374
;; convert date-time, timestamp into a duration
375
;; convert uuid, ordinal, name into an offset
376
(push :interval modes)
377
(cond ((equalp min-designator "HEAD") (push :prospective modes))
378
(t (push :retrospective modes)))
379
(push :interval modes)
380
(etypecase max-designator
381
(spocq:temporal-location
384
(timeline-location-date-time (rlmdb:revision-record-timestamp
385
(rlmdb:find-revision-record lmdb-repository max-designator
386
:offset max-offset)))))
387
(setf window-interval (spocq.e:- max-designator (timeline-location-date-time (rlmdb:revision-record-timestamp min-record)))))
388
((or string (and symbol (not null)))
389
(setf window-interval (- (rlmdb:revision-record-ordinal (rlmdb:find-revision-record lmdb-repository max-designator
391
(rlmdb:revision-record-ordinal min-record))))
393
(setf window-interval (- max-designator (rlmdb:revision-record-ordinal min-record)))))
394
(setf max-designator nil))
395
((and repeat-interval repeat-count)
396
(cond ((equalp min-designator "HEAD") (push :prospective modes))
397
(t (push :retrospective modes)))
398
(push :interval modes)
399
;; convert a date-time into a duration. leave a duration or offset as is
400
(etypecase repeat-interval
401
(spocq:temporal-location
402
(setf repeat-interval
403
(spocq.e:- repeat-interval
404
(timeline-location-date-time (rlmdb:revision-record-timestamp min-record)))))
405
((or integer spocq:duration) )))
407
(cond ((equalp min-designator "HEAD") (push :prospective modes))
408
(t (push :retrospective modes)))
409
(push :interval modes)
410
;; 20200302: no record of the motivating case
411
;; removed, as this precludes a form such as HEAD/1/1, which wuld be indefinitie monitoring
412
;; given just an interval transform it into the max desinator
414
(etypecase repeat-interval
415
((or spocq:temporal-location integer)
416
(setf max-record (rlmdb:find-revision-record lmdb-repository repeat-interval)))
418
(setf max-record (rlmdb:find-revision-record lmdb-repository min-record :offset repeat-interval)))))
420
;; just the end revision
421
(push :interval modes)
422
(setf max-record (rlmdb:find-revision-record lmdb-repository max-designator
423
:offset max-offset)))
425
(cond ((equalp min-designator "HEAD"))
426
(t (push :retrospective modes)))
427
(push :instant modes)))
428
;; augment with alternative designators and instantiate the revision
429
(let* ((min-uuid (rlmdb:revision-record-uuid min-record))
430
(min-ordinal (rlmdb:revision-record-ordinal min-record))
431
(max-uuid (when max-record (rlmdb:revision-record-uuid max-record)))
432
(max-ordinal (when max-record (rlmdb:revision-record-ordinal max-record)))
433
(interval-key (etypecase window-interval
435
(integer window-interval)
436
(spocq:temporal (spocq:literal-lexical-form window-interval))))
437
(repeat-key (etypecase repeat-interval
439
(integer repeat-interval)
440
(spocq:duration (spocq:literal-lexical-form repeat-interval))))
441
(revision-key (list min-uuid max-uuid interval-key repeat-key repeat-count))
442
(existing-instance (get-registry revision-key *repositories*))
443
(revision (if (and existing-instance (typep existing-instance revision-class)) ;; allow class change to invalidate old instance
445
(setf (get-registry revision-key *repositories*)
446
(make-instance revision-class
447
:reference-revision-id (if (string-equal revision-designator "HEAD")
449
(rlmdb:find-revision-uuid reference :head))
450
:revision-id min-uuid :reference reference
451
:min-revision-record min-record :max-revision-record max-record
452
:min-revision-ordinal min-ordinal :max-revision-ordinal max-ordinal
453
:window-interval window-interval
454
:repeat-interval repeat-interval
455
:repeat-limit repeat-count
456
:designator revision-designator
458
(assert (equal (repository-id reference) (repository-id (repository-revision-reference revision))) ()
459
"compute-repository-revision: clone does not match reference : for designator->id ~s->~s : ~s != ~s"
460
revision-designator min-uuid
461
(repository-id reference) (repository-id (repository-revision-reference revision)))
465
(defgeneric map-repository-revision-intervals (operator revision)
466
(:documentation "Given a revision interpret combinations of min/max bounds, windows and counts
467
to iterate over the sequence of designated revisions and invoke the given operator with the
468
respective min/max revision records.
469
The revision modes are used to distinguish the three principal cases:
470
- instant: a single revision can be with a static window
471
- retrospective: slide the window min/max over the global min/max
472
- prospective: re-establish the bounds for every new revision module the repeat interval")
473
(:method (operator (revision repository-revision))
474
"Given a static revision, the bounds never change."
476
(revision-min-revision-record revision)
477
(revision-max-revision-record revision)))
479
(:method (operator (revision lmdb-revision))
480
(let ((min-record (revision-min-revision-record revision))
481
(max-record (revision-max-revision-record revision))
482
(window-interval (revision-window-interval revision))
483
(repeat-interval (revision-repeat-interval revision))
484
(repeat-limit (revision-repeat-limit revision))
486
;; (print (list :to-map min-record max-record window-interval repeat-interval repeat-limit))
487
(cond ((is-instant-revision revision)
488
(setf result (funcall operator min-record (or min-record max-record))))
489
((is-retrospective-revision revision)
491
;; iterate for indicate window count
492
(loop for step from 0 below repeat-limit
493
with w-min-record = min-record
494
with w-max-record = (compute-first-revision-bounds (repository-lmdb-repository revision)
495
min-record window-interval)
496
while (and w-max-record
497
(or (null max-record)
498
(< (rlmdb:revision-record-timestamp w-max-record) (rlmdb:revision-record-timestamp max-record))))
499
do (progn (setf result (funcall operator w-min-record (or w-min-record w-max-record)))
500
(multiple-value-setq (w-min-record w-max-record)
501
(compute-next-revision-bounds (repository-lmdb-repository revision)
502
w-min-record window-interval repeat-interval))))
504
with w-min-record = min-record
505
with w-max-record = (compute-first-revision-bounds (repository-lmdb-repository revision)
506
min-record window-interval)
507
while (and w-max-record
508
(or (null max-record)
509
(< (rlmdb:revision-record-timestamp w-max-record) (rlmdb:revision-record-timestamp max-record))))
510
do (progn (setf result (funcall operator min-record (or w-min-record w-max-record)))
511
(multiple-value-setq (w-min-record w-max-record)
512
(compute-next-revision-bounds (repository-lmdb-repository revision)
513
w-min-record window-interval repeat-interval))))))
514
((is-prospective-revision revision)
515
;; drive the iteration from commit notifications
516
;; just the repeat interval and count govern the iteration
517
;; the window-interval would matter only it is to be allowed that the min-revision is offset
518
;; into the past from HEAD
519
(with-repository-commit-notifications (new-revision revision :interval repeat-interval :limit repeat-limit)
520
;; recompute the revision properties based on the original designator
521
(let* ((min-record (revision-min-revision-record revision))
522
(max-record (revision-max-revision-record revision)))
523
(setf result (funcall operator min-record (or min-record max-record)))))))
527
(defgeneric compute-first-revision-bounds (repository min-record window-interval)
528
(:method ((repository rlmdb:repository) (min-record rlmdb:revision-record) (window-interval integer))
529
"For two ordinal repetition parameters, return the revision records for the
530
offset ordinal bounds."
531
(let* ((min-ordinal (rlmdb:revision-record-ordinal min-record))
532
(max-ordinal (+ min-ordinal window-interval))
533
(max-record (rlmdb:get-revision-record repository max-ordinal)))
534
(values min-record max-record)))
535
(:method ((repository rlmdb:repository) (min-record rlmdb:revision-record) (window-interval spocq:duration))
536
"For a duration window and an ordinal step, offset the min and locate the max."
537
(let* ((max-date-time (spocq.e:+ (rlmdb:revision-record-date-time min-record) window-interval))
538
(max-record (rlmdb:find-revision-record repository max-date-time)))
539
(values min-record max-record))))
542
(defgeneric compute-next-revision-bounds (repository min-record window-interval repeat-interval)
543
(:method ((repository rlmdb:repository) (min-record rlmdb:revision-record) (window-interval integer) (repeat-interval integer))
544
"For two ordinal repetition parameters, return the revision records for the
545
offset ordinal bounds."
546
(let* ((min-ordinal (+ (rlmdb:revision-record-ordinal min-record) repeat-interval))
547
(min-record (rlmdb:get-revision-record repository min-ordinal))
548
(max-ordinal (+ min-ordinal window-interval))
549
(max-record (rlmdb:get-revision-record repository max-ordinal)))
550
(values min-record max-record)))
551
(:method ((repository rlmdb:repository) (min-record rlmdb:revision-record) (window-interval integer) (repeat-interval spocq:duration))
552
"For an ordinal window and duration step, locate the min and offset the max."
553
(let* ((min-date-time (spocq.e:+ (rlmdb:revision-record-date-time min-record) repeat-interval))
554
(min-record (rlmdb:find-revision-record repository min-date-time))
555
(max-ordinal (rlmdb:revision-record-ordinal min-record))
556
(max-record (rlmdb:get-revision-record repository max-ordinal)))
557
(values min-record max-record)))
558
(:method ((repository rlmdb:repository) (min-record rlmdb:revision-record) (window-interval spocq:duration) (repeat-interval integer))
559
"For a duration window and an ordinal step, offset the min and locate the max."
560
(let* ((min-ordinal (+ (rlmdb:revision-record-ordinal min-record) repeat-interval))
561
(min-record (rlmdb:get-revision-record repository min-ordinal))
562
(max-date-time (spocq.e:+ (rlmdb:revision-record-date-time min-record) window-interval))
563
(max-record (rlmdb:find-revision-record repository max-date-time)))
564
(values min-record max-record)))
565
(:method ((repository rlmdb:repository) (min-record rlmdb:revision-record) (window-interval spocq:duration) (repeat-interval spocq:duration))
566
"For two duration repstition parameters, locate the min record and then,
568
(let* ((min-date-time (spocq.e:+ (rlmdb:revision-record-date-time min-record) repeat-interval))
569
(min-record (rlmdb:find-revision-record repository min-date-time))
570
(max-date-time (spocq.e:+ (rlmdb:revision-record-date-time min-record) window-interval))
571
(max-record (rlmdb:find-revision-record repository max-date-time)))
572
(values min-record max-record))))
574
(defgeneric compute-previous-revision-bounds (repository min-record window-interval repeat-interval)
575
(:method ((repository rlmdb:repository) (min-record rlmdb:revision-record) (window-interval integer) (repeat-interval integer))
576
"For two ordinal repetition parameters, return the revision records for the
577
offset ordinal bounds."
578
(let* ((min-ordinal (- (rlmdb:revision-record-ordinal min-record) repeat-interval))
579
(min-record (rlmdb:get-revision-record repository min-ordinal))
580
(max-ordinal (+ min-ordinal window-interval))
581
(max-record (rlmdb:get-revision-record repository max-ordinal)))
582
(values min-record max-record)))
583
(:method ((repository rlmdb:repository) (min-record rlmdb:revision-record) (window-interval integer) (repeat-interval spocq:duration))
584
"For an ordinal window and duration step, locate the min and offset the max."
585
(let* ((min-date-time (spocq.e:- (rlmdb:revision-record-date-time min-record) repeat-interval))
586
(min-record (rlmdb:find-revision-record repository min-date-time))
587
(max-ordinal (rlmdb:revision-record-ordinal min-record))
588
(max-record (rlmdb:get-revision-record repository max-ordinal)))
589
(values min-record max-record)))
590
(:method ((repository rlmdb:repository) (min-record rlmdb:revision-record) (window-interval spocq:duration) (repeat-interval integer))
591
"For a duration window and an ordinal step, offset the min and locate the max."
592
(let* ((min-ordinal (- (rlmdb:revision-record-ordinal min-record) repeat-interval))
593
(min-record (rlmdb:get-revision-record repository min-ordinal))
594
(max-date-time (spocq.e:+ (rlmdb:revision-record-date-time min-record) window-interval))
595
(max-record (rlmdb:find-revision-record repository max-date-time)))
596
(values min-record max-record)))
597
(:method ((repository rlmdb:repository) (min-record rlmdb:revision-record) (window-interval spocq:duration) (repeat-interval spocq:duration))
598
"For two duration repstition parameters, locate the min record and then,
600
(let* ((min-date-time (spocq.e:- (rlmdb:revision-record-date-time min-record) repeat-interval))
601
(min-record (rlmdb:find-revision-record repository min-date-time))
602
(max-date-time (spocq.e:+ (rlmdb:revision-record-date-time min-record) window-interval))
603
(max-record (rlmdb:find-revision-record repository max-date-time)))
604
(values (values min-record max-record)))))
607
(defmethod call-with-open-transaction ((operator function) (transaction lmdb-transaction) &rest args)
608
"Provide a thread-specific lmdb transaction.
609
This is cloned from the respective repository's initial transaction and active
610
with dynamic extent only. As the lmdb data path is read-only, the disposition
612
(declare (ignore args))
613
(let* ((revision (transaction-revision transaction))
614
(rlmdb:repository (repository-lmdb-repository revision)))
615
(declare (ignorable rlmdb:repository))
616
#+lmdb-write-transactions
617
;; if the transaction is read-only, reuse the global transaction
618
(cond ((transaction-read-only-p transaction)
620
(let ((lmdb:transaction (transaction-lmdb-transaction transaction)))
621
(lmdb:with-transaction (lmdb:transaction :normal-disposition :continue :error-disposition :continue)
624
;; otherwise establish a new - thread-specific transaction over the repository environment
626
(let ((lmdb:transaction (lmdb:make-transaction rlmdb:repository :flags 0)))
627
(lmdb:with-transaction (lmdb:transaction :normal-disposition :commit)
628
(call-next-method)))))
629
#-lmdb-write-transactions
634
(defgeneric transaction-lmdb-repository (transaction)
635
(:method ((transaction lmdb-transaction))
636
(let* ((revision (transaction-revision transaction))
637
(rlmdb:repository (repository-lmdb-repository revision)))
641
(defmethod transaction-open ((transaction lmdb-transaction) &rest args)
642
(declare (ignore args) (dynamic-extent args))
645
(defmethod transaction-close ((transaction lmdb-transaction) (disposition (eql :continue)))
648
(defmethod transaction-close ((transaction lmdb-transaction) (disposition (eql :abort)))
650
(let ((lmdb:transaction (bound-slot-value transaction 'lmdb-transaction)))
651
(when (lmdb:open-p lmdb:transaction)
652
(lmdb:abort-transaction lmdb:transaction))
653
(setf-transaction-lmdb-transaction nil transaction)))
655
(defmethod transaction-close ((transaction lmdb-transaction) (disposition (eql :commit)))
657
(let ((lmdb:transaction (bound-slot-value transaction 'lmdb-transaction)))
658
(when (lmdb:open-p lmdb:transaction)
659
(lmdb:commit-transaction lmdb:transaction))
660
(setf-transaction-lmdb-transaction nil transaction)))
662
(defmethod call-for-task-revisions (operator (task task) (revision lmdb-revision))
663
"iterate over revisions..."
664
(declare (dynamic-extent operator))
665
(flet ((call-for-next-bounds (min-record max-record)
666
(setf (revision-min-revision-record revision) min-record
667
(revision-max-revision-record revision) max-record
668
(revision-min-revision-ordinal revision) (rlmdb:revision-record-ordinal min-record)
669
(revision-max-revision-ordinal revision) (rlmdb:revision-record-ordinal max-record))
670
(funcall operator task revision)))
671
(declare (dynamic-extent #'call-for-next-bounds))
672
(map-repository-revision-intervals #'call-for-next-bounds revision)))
674
(defun get-lmdb-session-data (session-id)
675
(let* ((location #p"/srv/dydra/runtime/sessions/")
676
(env (lmdb:make-environment location)))
677
(lmdb:with-environment (env)
678
(lmdb:with-transaction ((txn (lmdb:make-transaction env)))
679
(let ((db (lmdb:make-database "id:data")))
680
(lmdb:ensure-open-database db)
681
(let ((data-string (map 'string #'code-char (lmdb:get db session-id))))
682
(when (plusp (length data-string)) (parse-json data-string))))))))
683
;;; 20171108 on atomgraph: (get-lmdb-session-data "f5515bd328215305b7a44cbd9eb09aca")
685
(defgeneric query-account-by-number (repository id)
686
(:method ((repository-id string) (account-id t))
687
(query-account-by-number (repository repository-id) account-id))
688
(:method ((repository repository) (account-id integer))
689
(query-account-by-number repository (prin1-to-string account-id)))
690
(:method ((repository repository) (account-id string))
695
(spocq.a:|graph| ?::account
697
(spocq.a:|triple| ?::account |sioc|:|id| ,account-id)
698
(spocq.a:|triple| ?::account |sioc|:|has_owner| ?::user)))
699
(spocq.a:|graph| |urn:dydra|:|users|
701
(spocq.a:|triple| ?::user |dc|:|title| ?::name))))
702
(spocq.a:|graph| |urn:dydra|:|users|
704
(spocq.a:|triple| ?::user |sioc|:|administrator_of| ?::isAdminOf))))
705
(?::user ?::isAdminOf ?::name))
706
:repository repository
707
:agent (system-agent))))
709
(defmethod authenticate-user-session ((repository lmdb-repository) (session-id string))
710
;; ginve an lmdb-based implementation, retrieve he information from the dydra sesions dataabse
711
(let ((warden-data (rest (assoc "warden.user.account.key" (get-lmdb-session-data session-id) :test #'equalp))))
713
(let* ((account-id (elt (elt warden-data 1) 0))
714
(results (query-account-by-number repository account-id)))
715
(when (consp results)
717
;; error. it could yield an uintended user
718
(error "authenticate-user-session: anomalous results for session ~s: ~s" session-id results))
719
(destructuring-bind (user-id is-admin user-name) (first results)
720
(unless (stringp user-name)
721
(log-warn "anomalous user session authentication results: ~s"
722
(list user-id is-admin user-name session-id))
723
(let* ((iri-string (iri-lexical-form user-id))
724
(iri-name (first (last (split-string iri-string "/")))))
725
(setf user-name iri-name)))
726
(values user-id (not (spocq:unbound-variable-p is-admin)) user-name)))))))
728
;;; => "{\"http_referrer\":\"none\",\"_csrf_token\":\"A/QYimj0YZHUDNXO6794feXVyxDd9FkY4QeBq+9syx8=\",\"warden.user.account.key\":[\"Account\",[4],\"$2a$10$m/PkvH/vyr8/o5H.4sS8je\"]}"
729
;;; (parse-json "{\"http_referrer\":\"none\",\"_csrf_token\":\"A/QYimj0YZHUDNXO6794feXVyxDd9FkY4QeBq+9syx8=\",\"warden.user.account.key\":[\"Account\",[4],\"$2a$10$m/PkvH/vyr8/o5H.4sS8je\"]}")
730
;;; => (("http_referrer" . "none") ("_csrf_token" . "A/QYimj0YZHUDNXO6794feXVyxDd9FkY4QeBq+9syx8=") ("warden.user.account.key" . #("Account" #(4) "$2a$10$m/PkvH/vyr8/o5H.4sS8je")))
731
;;; (authenticate-user-session (system-repository) "c71e1eb0c3c96e408d7f423ec6b7abff")
732
;;; (test-sparql "delete { <http://dydra.com/accounts/christophe-dupriez> ?p ?o
734
;;; repository signatures
736
(defmethod compute-revision-signature ((repository-id string) &rest args)
737
(declare (dynamic-extent args))
738
(apply #'compute-revision-signature (repository repository-id) args))
740
(defmethod compute-revision-signature ((repository lmdb-repository) &rest args)
741
(declare (dynamic-extent args))
742
(apply #'rlmdb.i::compute-rlmdb-revision-signature (repository-lmdb-repository repository) args))
744
(defmethod compute-revision-signature ((revision lmdb-revision) &rest args &key
746
(first (revision-min-revision-ordinal revision))
747
(last (revision-max-revision-ordinal revision)))
748
(declare (dynamic-extent args) (ignore digest-type))
749
(apply #'rlmdb.i::compute-rlmdb-revision-signature (repository-lmdb-repository revision)
750
:revision-predicate (compute-revision-predicate (list :first first :last last))
756
#+(or) ;; direct lexical form retrieval
757
(defmethod repository-term-deconstructor ((transaction lmdb-transaction))
758
"Where a direct interface to the lmdb store is available, the deconstructor
759
retrieves the term aspects directly within its own transaction.
761
there should really be an alterative which is given a stream, to write the results directly,
762
without first retrieving them "
766
(defgeneric term-encoder (transaction encoding)
767
(:documentation "returns an operator of two parameters, a term number and a
768
stream. The operator accepts the two arguments and encodes the term as per
769
the encoding media type to the stream. The encoding may be the complete
770
lexical form of an n-quads term or just the uri attribute value for an xml
771
encoding, as is required by the encoding."))
773
;;; support for single-transaction describes
775
(defmethod process-describe ((subject-source lmdb-transaction) (object-source lmdb-transaction)
776
(destination array-page-channel)
777
(base-source array-page-channel)
778
base-dimensions subjects)
779
(let* ((revision (transaction-revision subject-source))
780
(rlmdb:repository (repository-lmdb-repository revision))
781
(lmdb:transaction (lmdb:make-transaction rlmdb:repository))
782
(all-term-number (object-term-number '|urn:dydra|:|all|))
783
(type-term-number (object-term-number '|rdf|:|type|)) ;; anything constant
784
(subject-quad-pattern (vector all-term-number type-term-number 0 0))
785
(object-quad-pattern (vector all-term-number 0 0 type-term-number)))
786
(lmdb:with-transaction (lmdb:transaction :initial-disposition :begin
787
:normal-disposition :abort
788
:error-disposition :abort)
789
(let ((subject-index-database (rlmdb.i::repository-quad-pattern-index rlmdb:repository subject-quad-pattern))
790
(object-index-database (rlmdb.i::repository-quad-pattern-index rlmdb:repository object-quad-pattern)))
791
(spocq.i::process-describe subject-index-database object-index-database
792
destination base-source
793
base-dimensions subjects)))))
796
the arrangment saves about five microseconds per describe traversal step by using just one transaction
797
an example from atomgraph with 11K steps was (neglecting profile) about .9 seconds faster with the single transaction.
799
;; multiple transactions:
803
(test-sparql (read-file #P"/mnt/hdd/dydra/history/queries/linkeddatahub/08/08eee7bdc9ceff95ad31ead1defb43ab6360f1b7")
804
:repository-id "linkeddatahub/docs-admin-dev")))
807
0.440 seconds of real time
808
0.500000 seconds of total run time (0.412000 user, 0.088000 system)
810
367 lambdas converted
811
1,537,379,817 processor cycles
812
53,044,048 bytes consed
815
* (sb-profile:report)
817
seconds | gc | consed | calls | sec/call | name
818
---------------------------------------------------------
819
0.210 | 0.000 | 24,499,552 | 21,156 | 0.000010 | ORG.DATAGRAPH.RDF.LMDB:MAP-REPOSITORY-STATEMENTS
820
0.041 | 0.000 | 2,824,880 | 10,585 | 0.000004 | LMDB:MAKE-TRANSACTION
821
0.009 | 0.000 | 917,280 | 10,585 | 0.000001 | LMDB:BEGIN-TRANSACTION
822
0.000 | 0.000 | 688,352 | 10,585 | 0.000000 | LMDB:ABORT-TRANSACTION
823
---------------------------------------------------------
824
0.259 | 0.000 | 28,930,064 | 52,911 | | Total
826
estimated total profiling overhead: 0.04 seconds
827
overhead estimation parameters:
828
1.6000001e-8s/call, 7.76e-7s total profiling, 3.04e-7s internal profiling
832
;;; single transaction
835
(test-sparql (read-file #P"/mnt/hdd/dydra/history/queries/linkeddatahub/08/08eee7bdc9ceff95ad31ead1defb43ab6360f1b7")
836
:repository-id "linkeddatahub/docs-admin-dev")))
839
0.349 seconds of real time
840
0.404000 seconds of total run time (0.316000 user, 0.088000 system)
842
367 lambdas converted
843
1,218,773,796 processor cycles
844
49,704,336 bytes consed
846
Resuming thread #<THREAD "main thread" RUNNING {1005D3E413}>
848
* (sb-profile:report)
850
seconds | gc | consed | calls | sec/call | name
851
---------------------------------------------------------
852
0.169 | 0.000 | 25,321,248 | 10,594 | 0.000016 | ORG.DATAGRAPH.RDF.LMDB:MAP-REPOSITORY-STATEMENTS
853
0.004 | 0.000 | 32,768 | 24 | 0.000166 | LMDB:MAKE-TRANSACTION
854
0.000 | 0.000 | 0 | 24 | 0.000000 | LMDB:BEGIN-TRANSACTION
855
0.000 | 0.000 | 32,768 | 24 | 0.000000 | LMDB:ABORT-TRANSACTION
856
---------------------------------------------------------
857
0.173 | 0.000 | 25,386,784 | 10,666 | | Total
859
estimated total profiling overhead: 0.01 seconds
860
overhead estimation parameters:
861
8.000001e-9s/call, 8.16e-7s total profiling, 2.88e-7s internal profiling
868
;;; kleen star path support
870
(defmethod map-repository-subject-and-objects ((continuation function) (transaction lmdb-transaction) context &rest args)
871
(declare (ignore args)) ;; distinct is ignored
872
;; the path processsing does it anyway to merge sujects and object
873
;; function (context termid)
874
(flet ((lmdb-continuation (%quad)
875
(funcall continuation (%quad-context %quad) (%quad-subject %quad))
876
(funcall continuation (%quad-context %quad) (%quad-object %quad))))
877
(declare (dynamic-extent #'lmdb-continuation))
878
(let ((quad-pattern (vector context 0 0 0)))
879
(rlmdb:map-repository-statements #'lmdb-continuation transaction
881
:revision-predicate (compute-revision-predicate transaction)))))
883
(defun read-rlmdb-pattern-count (lmdb-repository-designator stn ptn otn ctn &rest args)
885
(flet ((count-quad (%quad) (declare (ignore %quad)) (incf count)))
886
(declare (dynamic-extent #'count-quad))
887
(apply #'rlmdb:map-repository-statements #'count-quad lmdb-repository-designator
888
(vector ctn stn ptn otn)
893
(defgeneric test-rlmdb-pattern-match (lmdb-repository-designator stn ptn otn ctn &rest args)
894
(:method ((repository lmdb-repository) stn ptn otn ctn &rest args)
895
(flet ((match-quad (%quad)
896
(declare (ignore %quad))
897
(return-from test-rlmdb-pattern-match t)))
898
(declare (dynamic-extent #'match-quad))
899
(apply #'rlmdb:map-repository-statements #'match-quad (repository-lmdb-repository repository)
900
(vector ctn stn ptn otn)
903
(:method ((revision lmdb-revision) stn ptn otn ctn &rest args
905
(first (revision-min-revision-ordinal revision))
906
(last (revision-max-revision-ordinal revision))
908
(declare (dynamic-extent args))
909
(apply #'test-rlmdb-pattern-match (repository-revision-reference revision) stn ptn otn ctn
910
:revision-predicate (compute-revision-predicate (list :first first :last last))
913
(defmethod repository-context-term-number ((transaction lmdb-transaction) context)
914
(repository-context-term-number (transaction-repository transaction) context))
916
(defmethod repository-context-term-number ((transaction lmdb-repository) context)
918
((nil) (if transaction
919
(repository-default-context-term-number transaction)
920
rlmdb:*default-context-number*))
921
((|urn:dydra|:|all| :all t) rlmdb:*all-context-number*)
922
((|urn:dydra|:|default| :default) rlmdb:*default-context-number*)
923
((|urn:dydra|:|named| :named) rlmdb:*named-context-number*)
925
(if (variable-p context)
926
(ecase (named-contexts-term)
927
(|urn:dydra|:|all| rlmdb:*all-context-number*)
928
(|urn:dydra|:|default| rlmdb:*default-context-number*)
929
(|urn:dydra|:|named| rlmdb:*named-context-number*))
930
(rlmdb:value-term-number context :if-does-not-exist nil)))))
932
(defparameter *wild-blank-nodes-p* t)
934
(defmethod repository-pattern-term-number ((transaction lmdb-transaction) term)
935
;; this can treat blank nodes as variables for the use in presence matching and counting
937
(and (spocq:blank-node-p term) *wild-blank-nodes-p*)
940
rlmdb:*wildcard-term-number*
941
(rlmdb:value-term-number term :if-does-not-exist nil)))
943
(defmethod repository-pattern-term-number ((transaction lmdb-repository) term)
944
;; this can treat blank nodes as variables for the use in presence matching and counting
946
(and (spocq:blank-node-p term) *wild-blank-nodes-p*)
949
rlmdb:*wildcard-term-number*
950
(rlmdb:value-term-number term :if-does-not-exist nil)))
952
(defgeneric repository-pattern-match-p (repository subject predicate object context)
954
"Resolve all terms from sybolic to ordinal values and test presence.")
955
(:method ((repository repository) subject predicate object context)
956
(if (extension-operator-p predicate)
958
(let* ((context (repository-context-term-number repository context))
959
(subject (repository-pattern-term-number repository subject))
960
(predicate (repository-pattern-term-number repository predicate))
961
(object (repository-pattern-term-number repository object)))
962
(when (and context subject predicate object)
963
(repository-pattern-match-p repository subject predicate object context)))))
964
(:method ((repository shard-repository) (subject integer) (predicate integer) (object integer) (context integer))
965
(test-rlmdb-pattern-match repository subject predicate object context))
966
(:method ((transaction lmdb-transaction) subject predicate object context)
967
(repository-pattern-match-p (transaction-revision transaction) subject predicate object context)))
969
(defmethod read-repository-pattern-count ((repository shard-repository) subject predicate object context)
970
"Resolve all terms from symbolic to ordinal values and perform the count."
971
(if (extension-operator-p predicate)
973
(let* ((context (repository-context-term-number repository context))
974
(subject (repository-pattern-term-number repository subject))
975
(predicate (repository-pattern-term-number repository predicate))
976
(object (repository-pattern-term-number repository object)))
977
(if (and context subject predicate object)
978
(if (and (zerop subject) (zerop predicate) (zerop object) (zerop context))
979
;;!! all-wild for repository ignores the revision
980
;; can delegate to a method which reads the count directly from the repo statistics record
981
(read-repository-statement-count repository)
982
(read-rlmdb-pattern-count repository subject predicate object context))
985
(defmethod read-repository-pattern-count ((transaction shard-transaction) subject predicate object context)
986
"Resolve all terms from symbolic to ordinal values and perform the count.
987
Given a transaction, the context term may resolve specific to the invocation."
988
(if (extension-operator-p predicate)
990
(let* ((context (repository-context-term-number transaction context))
991
(subject (repository-pattern-term-number transaction subject))
992
(predicate (repository-pattern-term-number transaction predicate))
993
(object (repository-pattern-term-number transaction object)))
994
(if (and context subject predicate object)
995
(if (and (zerop subject) (zerop predicate) (zerop object) (zerop context))
996
;; can delegate to a method which reads the count directly from the repo statistics record
997
(read-repository-statement-count transaction)
998
(read-rlmdb-pattern-count transaction subject predicate object context
999
:revision-predicate (compute-revision-predicate (list :first (transaction-min-revision-ordinal transaction)
1000
:last (transaction-max-revision-ordinal transaction)))))
1003
(defmethod repository-pattern-count ((repository shard-repository) subject (predicate active-verb) object context)
1006
(defmethod repository-pattern-count ((repository shard-repository) subject predicate object context)
1007
"Resolve all terms from symbolic to ordinal values and perform the count."
1008
(if (extension-operator-p predicate)
1010
(let* ((context (repository-context-term-number repository context))
1011
(subject (repository-pattern-term-number repository subject))
1012
(predicate (repository-pattern-term-number repository predicate))
1013
(object (repository-pattern-term-number repository object)))
1014
(if (and context subject predicate object)
1015
(if (and (zerop subject) (zerop predicate) (zerop object) (zerop context))
1016
;;!! all-wild for repository ignores the revision
1017
;; can delegate to a method which reads the count directly from the repo statistics record
1018
(read-repository-statement-count repository)
1019
(repository-pattern-count repository subject predicate object context))
1023
(defmethod repository-pattern-count ((repository shard-repository)
1024
(subject integer) (predicate integer) (object integer) (context integer))
1025
(flet ((count-pattern ()
1026
(read-rlmdb-pattern-count repository subject predicate object context)))
1027
(let ((stats (repository-statistics repository))
1028
(pattern (list subject predicate object context)))
1029
(declare (dynamic-extent pattern))
1030
(if (operation-read-only-p *task*)
1031
(or (gethash pattern stats)
1032
(setf (get-registry (copy-list pattern) stats) (count-pattern)))
1036
(defmethod repository-pattern-count ((transaction shard-transaction) subject predicate object context)
1037
"Resolve all terms from symbolic to ordinal values and perform the count.
1038
Given a transaction, the context term may resolve specific to the invocation."
1039
(if (extension-operator-p predicate)
1041
(let* ((context (repository-context-term-number transaction context))
1042
(subject (repository-pattern-term-number transaction subject))
1043
(predicate (repository-pattern-term-number transaction predicate))
1044
(object (repository-pattern-term-number transaction object)))
1045
(if (and context subject predicate object)
1046
(if (and (zerop subject) (zerop predicate) (zerop object) (zerop context))
1047
;; can delegate to a method which reads the count directly from the repo statistics record
1048
(read-repository-statement-count transaction)
1049
(repository-pattern-count transaction subject predicate object context))
1052
(defmethod repository-pattern-count ((transaction shard-transaction)
1053
(subject integer) (predicate integer) (object integer) (context integer))
1054
(flet ((count-pattern ()
1055
(read-rlmdb-pattern-count transaction subject predicate object context
1056
:revision-predicate (compute-revision-predicate (list :first (transaction-min-revision-ordinal transaction)
1057
:last (transaction-max-revision-ordinal transaction))))))
1058
(let* ((revision (transaction-revision transaction))
1059
(stats (repository-statistics revision))
1060
(pattern (list subject predicate object context)))
1061
(declare (dynamic-extent pattern))
1062
(if (operation-read-only-p *task*)
1063
(or (gethash pattern stats)
1064
(setf (get-registry (copy-list pattern) stats) (count-pattern)))
1067
(defmethod repository-statement-count ((repository lmdb-repository))
1068
(read-repository-statement-count repository))
1070
(defmethod read-repository-statement-count ((repository lmdb-repository))
1071
"Given a repository, return the global eindex entry count"
1072
(rlmdb::entry-count repository))
1074
(defmethod read-repository-statement-count ((revision lmdb-revision))
1075
"Given a repository, return the global eindex entry count"
1076
(read-rlmdb-pattern-count revision 0 0 0 0
1077
:revision-predicate (compute-revision-predicate (list :first (revision-min-revision-ordinal revision)
1078
:last (revision-max-revision-ordinal revision)))))
1080
(defmethod read-repository-statement-count ((transaction lmdb-transaction))
1081
"Given a transaction, count statements specific to the respective version window"
1082
(read-rlmdb-pattern-count transaction 0 0 0 0
1083
:revision-predicate (compute-revision-predicate (list :first (transaction-min-revision-ordinal transaction)
1084
:last (transaction-max-revision-ordinal transaction)))))
1086
(defmethod repository-statement-count ((transaction lmdb-transaction))
1087
(read-repository-statement-count transaction))
1089
(defmethod repository-intern-property-path ((repository shard-repository) property-path)
1090
(rlmdb:intern-property-path property-path))
1092
(defmethod repository-intern-property-path ((repository shard-transaction) property-path)
1093
(rlmdb:intern-property-path property-path))
1098
(defmethod map-repository-contexts (function (transaction lmdb-transaction) &key
1101
(rlmdb::map-context-numbers function (transaction-repository transaction)
1102
:distinct distinct :default default
1103
:revision-predicate (compute-revision-predicate transaction)))
1105
(defmethod map-repository-contexts (function (repository lmdb-repository) &key
1108
(rlmdb::map-context-numbers function repository
1109
:distinct distinct :default default
1110
:revision-predicate (compute-revision-predicate repository)))
1113
(defmethod map-repository-subjects (function (transaction lmdb-transaction) &key
1114
(context nil) (distinct t))
1115
(declare (dynamic-extent function))
1116
(let ((context-term (repository-context-term-number transaction context)))
1118
(rlmdb:map-subject-numbers function (transaction-repository transaction)
1120
:context context-term
1121
:revision-predicate (compute-revision-predicate transaction)))))
1123
(defmethod map-repository-subjects (function (repository lmdb-repository) &key
1124
(context nil) (distinct t))
1125
(declare (dynamic-extent function))
1126
(let ((context-term (repository-context-term-number repository context)))
1128
(rlmdb:map-subject-numbers function repository
1130
:context context-term
1131
:revision-predicate (compute-revision-predicate repository)))))
1133
(defmethod map-repository-predicates (function (transaction lmdb-transaction) &key
1134
(context nil) (distinct t))
1135
(declare (dynamic-extent function))
1136
(let ((context-term (repository-context-term-number transaction context)))
1138
(rlmdb:map-predicate-numbers function (transaction-repository transaction)
1140
:context context-term
1141
:revision-predicate (compute-revision-predicate transaction)))))
1143
(defmethod map-repository-predicates (function (repository lmdb-repository) &key
1144
(context nil) (distinct t))
1145
(declare (dynamic-extent function))
1146
(let ((context-term (repository-context-term-number repository context)))
1148
(rlmdb:map-predicate-numbers function repository
1150
:context context-term
1151
:revision-predicate (compute-revision-predicate repository)))))
1154
(defmethod map-repository-objects (function (transaction lmdb-transaction) &key
1155
(context nil) (distinct t))
1156
(declare (dynamic-extent function))
1157
(let ((context-term (repository-context-term-number transaction context)))
1159
(rlmdb:map-object-numbers function (transaction-repository transaction)
1161
:context context-term
1162
:revision-predicate (compute-revision-predicate transaction)))))
1164
(defmethod map-repository-objects (function (repository lmdb-repository) &key
1165
(context nil) (distinct t))
1166
(declare (dynamic-extent function))
1167
(let ((context-term (repository-context-term-number repository context)))
1169
(rlmdb:map-object-numbers function repository
1171
:context context-term
1172
:revision-predicate (compute-revision-predicate repository)))))
1177
(defmethod repository-match-field ((transaction lmdb-transaction) context subject predicate object &rest args)
1178
(declare (dynamic-extent args))
1179
(apply #'repository-match-field (transaction-revision transaction) context subject predicate object args))
1181
(defmethod repository-match-field ((revision lmdb-revision) context subject predicate object &rest args
1183
(first (revision-min-revision-ordinal revision))
1184
(last (revision-max-revision-ordinal revision))
1186
(declare (dynamic-extent args))
1187
(apply #'repository-match-field (repository-revision-reference revision) context subject predicate object
1188
:revision-predicate (compute-revision-predicate (list :first first :last last))
1192
(defmethod repository-match-field ((repository lmdb-repository) context subject predicate object
1193
&key (start 0) end dimensions
1194
((:wild-blank-nodes-p *wild-blank-nodes-p*) t)
1196
"generate a solution sequence for the statements which match the argument pattern.
1197
Intern all terms. Should some term not be present, then return null.
1198
Associate a (possible sparse) dimension list with the result to indicate which columns
1200
The result field elements are ordered (c s p o)
1202
The variant for lmdb requires no rdfcache transaction, as they are read-only.
1203
Split the retrieval and interning to limit the transactions on the string db."
1204
(let* ((default-context-term (repository-default-context-term repository))
1205
(dataset (task-dataset-graphs *task*))
1207
(cond ((null context) (if dataset (dataset-default-graphs dataset) (list default-context-term)))
1208
((variable-p context) (dataset-named-graphs dataset))
1209
((typep context 'iri) (list context))
1211
(error "Invalid dataset specification: graph ~s, dataset ~s."
1215
(sort-dimensions ()))
1216
(labels ((when-variable (term)
1217
(when (variable-p term) term))
1218
(_repository-pattern-term-number (term)
1219
(or (repository-pattern-term-number repository term)
1220
(return-from repository-match-field nil)))
1221
(collect-quad (%quad)
1222
(when (> (incf count) start)
1223
(when (and end (> count end))
1225
(push (list (%quad-context %quad)
1226
(%quad-subject %quad)
1227
(%quad-predicate %quad)
1228
(%quad-object %quad))
1231
;; intern and reverse
1232
(rlmdb:with-string-database (db)
1233
(let ((interned-result (loop for (context subject predicate object) in (nreverse result)
1234
collect (list (rlmdb:term-number-value context)
1235
(rlmdb:term-number-value subject)
1236
(rlmdb:term-number-value predicate)
1237
(rlmdb:term-number-value object)))))
1238
(return-from repository-match-field (values interned-result sort-dimensions))))))
1239
(declare (dynamic-extent #'collect-quad))
1240
(setf sort-dimensions (remove nil (or dimensions
1241
(list (when-variable context)
1242
(when-variable subject)
1243
(when-variable predicate)
1244
(when-variable object)))))
1245
(let ((interned-subject (_repository-pattern-term-number subject))
1246
(interned-predicate (_repository-pattern-term-number predicate))
1247
(interned-object (_repository-pattern-term-number object))
1248
(interned-effective-contexts (loop for context in effective-contexts
1249
for term-number = (repository-context-term-number repository context)
1250
;; if it is not known, there can be no result
1252
collect term-number))
1253
(lmdb-repository (repository-lmdb-repository repository)))
1254
(loop for interned-context in interned-effective-contexts
1255
;; would be good to get this within a single rlmdb transaction
1256
do (rlmdb:map-repository-statements #'collect-quad lmdb-repository
1257
(vector interned-context
1261
:revision-predicate revision-predicate)))