Coverage report: /development/source/library/org/datagraph/spocq-shard/src/store/rlmdb/replicable-database.lisp
| Kind | Covered | All | % |
| expression | 0 | 1998 | 0.0 |
| branch | 0 | 170 | 0.0 |
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.rlmdb.implementation; -*-
4
;;; (load "patches/replicable-database.lisp")
5
;;; (load "patches/revision-identifier.lisp")
7
(:documentation "replicable databases"
8
"A replicable database extends the rlmdb:repository with a specialized rlmdb:database
9
which store revision sequence vectors keyed by their hash codes and supports revision presence tests
10
against quad presence vectors which are sequence vector hash codes.
13
- rlmdb:replicable-repository : add a revision-sequence-db
14
- rlmdb:replicable-database
15
- rlmdb::replicable-quad-database
16
- rlmdb:revision-sequence-database : stores revision sequences by hash code.
20
(bytes :uint8 :count 20))
23
(defun %decode-revision-sequence-id (%data &optional (result (make-array spocq.i::+rsid-length+ :element-type '(unsigned-byte 8))))
24
"Extract a sequence of uuid vectors from foreign data"
25
(loop for i below spocq.i::+rsid-length+
26
do (setf (aref result i) (cffi:mem-aref %data :uint8 i)))
29
(defun %encode-revision-sequence-id (%data revision-sequence-id)
30
"Extract a sequence of uuid vectors from foreign data"
31
(loop for i below spocq.i::+rsid-length+
32
do (setf (cffi:mem-aref %data :uint8 i) (aref revision-sequence-id i)))
35
(defun %decode-revision-sequence (%data length)
36
"Extract a sequence of uuid vectors from foreign data"
37
(let* ((count (/ length spocq.i::+uuid-length+))
38
(result (make-array count)))
39
(loop for i below count
41
for uuid = (spocq.i::make-uuid-vector)
42
do (progn (setf (aref result i) uuid)
43
(loop for j below spocq.i::+uuid-length+
44
do (setf (aref uuid j) (cffi:mem-aref %data :uint8 offset)
45
offset (1+ offset)))))
48
(defun %encode-revision-sequence (%data revision-sequence)
49
"Constract a packed foreign sequence from a revision uuid sequence."
50
(let* ((count (length revision-sequence)))
51
(loop for i below count
53
for uuid = (aref revision-sequence i)
54
do (loop for j below spocq.i::+uuid-length+
55
do (setf (cffi:mem-aref %data :uint8 offset) (aref uuid j)
56
offset (1+ offset)))))
59
(defun get-metadata-uuid-vector (designator)
60
(spocq.i::copy-uuid-vector (rlmdb:get-metadata-uuid designator)))
62
(defstruct (rlmdb::revision-application-log-record (:include rlmdb:revision-log-record))
63
"An application log record adds the uuid from the replica/application in order to place the transaction bi-temporally"
64
(application-uuid +null-uuid-string+ :type string))
66
;;; a revision application log record augments the standard revision information with the replica uuid
67
;;; this combines that revision with the local revision into a 2-d revision location.
68
(cffi:defcstruct revision_application_log_record
71
(transaction_uuid :unsigned-char :count 16)
72
(timestamp_begun :uint64 :offset 18)
73
(timestamp_committed :uint64 :offset 26)
74
(visible_count :uint64 :offset 34)
75
(removed_count :uint64 :offset 42)
76
(inserted_count :uint64 :offset 50)
77
(application_uuid :unsigned-char :count 16))
78
;;; (+ 1 1 16 8 8 8 8 8 16)
80
(defparameter +application_log_record-size+ 74)
82
(defun %decode-revision-application-log-record (record)
83
(make-revision-application-log-record
84
:version (cffi:foreign-slot-value record '(:struct revision_application_log_record) 'version)
85
:flags (cffi:foreign-slot-value record '(:struct revision_application_log_record) 'flags)
86
:uuid (rlmdb:decode-metadata :|revision-uuid| (cffi:foreign-slot-value record '(:struct revision_application_log_record) 'transaction_uuid))
87
:timestamp (cffi:foreign-slot-value record '(:struct revision_application_log_record) 'timestamp_committed)
88
:timestamp-begun (cffi:foreign-slot-value record '(:struct revision_application_log_record) 'timestamp_begun)
89
;; nb. the visible count is not maintained by the rdfcache implementation
90
:visible-count (cffi:foreign-slot-value record '(:struct revision_application_log_record) 'visible_count)
91
:removed-count (cffi:foreign-slot-value record '(:struct revision_application_log_record) 'removed_count)
92
:inserted-count (cffi:foreign-slot-value record '(:struct revision_application_log_record) 'inserted_count)
93
:uuid (rlmdb:decode-metadata :|revision-uuid| (cffi:foreign-slot-value record '(:struct revision_application_log_record) 'application_uuid))))
95
(defgeneric %encode-revision-application-log-record (record %record)
96
(:method ((record rlmdb::revision-application-log-record) (%record SB-SYS:SYSTEM-AREA-POINTER))
97
(cffi:with-foreign-slots ((version flags
98
timestamp_committed timestamp_begun
99
visible_count removed_count inserted_count)
101
(:struct revision_application_log_record))
102
(spocq.i::%encode-uuid (rlmdb:revision-log-record-uuid record)
103
(cffi:foreign-slot-pointer %record '(:struct revision_application_log_record) 'transaction_uuid))
104
(setf version (rlmdb:revision-log-record-version record)
105
flags (rlmdb:revision-log-record-flags record)
106
timestamp_committed (rlmdb:revision-log-record-timestamp record)
107
timestamp_begun (rlmdb:revision-log-record-timestamp-begun record)
108
visible_count (rlmdb:revision-log-record-visible-count record)
109
removed_count (rlmdb:revision-log-record-removed-count record)
110
inserted_count (rlmdb:revision-log-record-inserted-count record))
111
(spocq.i::%encode-uuid (revision-application-log-record-application-uuid record)
112
(cffi:foreign-slot-pointer %record '(:struct revision_application_log_record) 'application_uuid))
115
;;; (with-lmdb-values ((key 1 2) (data 3 4)) (list key data))
122
(defmethod rlmdb:clear-repository max ((repository rlmdb:replicable-repository) &key (type 'nil))
124
(flet ((clear-db (db)
125
(when (typep db type)
126
(unless count (setf count (rlmdb::entry-count db)))
127
(lmdb:drop-database db :delete 0))))
128
(clear-db (repository-revision-sequence-database repository)))
133
(defgeneric repository-revision-sequence (repository sequence-id)
134
(:documentation "retrieve and decode the identified revision sequence.")
135
(:method ((repository rlmdb:replicable-repository) (sequence-id vector))
136
"given a vector sha1 value extract the native pointer and continue"
137
(cffi:with-foreign-pointer (%sequence-id spocq.i::+rsid-length+)
138
(%encode-revision-sequence-id %sequence-id sequence-id)
139
(repository-revision-sequence repository %sequence-id)))
140
(:method ((repository rlmdb:replicable-repository) (%sequence-id SB-SYS:SYSTEM-AREA-POINTER))
141
"given an external sha1 value, retrieve the vector, if known and return the boxed sequence"
142
(lmdb::with-empty-value (%key)
143
(lmdb::with-empty-value (%value)
144
(setf (%mdb-val-size %key) spocq.i::+rsid-length+
145
(%mdb-val-data %key) %sequence-id)
146
(let* ((db (repository-revision-sequence-database repository))
147
(return-code (liblmdb:get (lmdb::handle lmdb:*transaction*) (lmdb::handle db) %key %value)))
148
(cond ((zerop return-code)
149
(%decode-revision-sequence (%mdb-val-data %value) (%mdb-val-size %value)))
150
((eql return-code liblmdb:+notfound+)
151
(error "no state-vector found: ~a" %sequence-id))
153
(lmdb::unknown-error return-code))))))))
158
(defmethod rlmdb:map-repository-statements (operator (index rlmdb::replicable-quad-database) (quad-pattern t)
159
&key (first nil) (last nil) (mode 'every)
160
revision-predicate domain-predicate
162
"Use the pattern to locate and constrain a scan over the statements' index values.
163
The abstract process is the same as for an isolated repository.
164
In the replicated case, however, each index entry is used, first to check in a transaction cache for a visibility determination.
165
the cache key is the sequence sha1, as the index entry does not de-duplicate sequences.
166
if a cache entry is present, that determines the respective statement visibilty.
167
if no cache entry is present, the actual sha1 hash is used to retrieve the complete revision sequence.
168
this is used to test visibility given the transaction revision - latest or temporally significant.
169
the eventual determination is then cached per sequence sha1.
171
(declare (ignore predicates revision-predicate temporal-predicate scan-order))
173
(setf first (rlmdb:find-last-ordinal index)))
175
(setf first (spocq.i::copy-uuid-vector (rlmdb:get-revision-uuid index first))
176
last (spocq.i::copy-uuid-vector (rlmdb:get-revision-uuid index last)))
178
(setf first (spocq.i::copy-uuid-vector (rlmdb:get-revision-uuid index first))
181
(lmdb:with-database (index)
182
(let* ((cur (lmdb:make-cursor index :transaction lmdb:*transaction*))
183
(named-only (case (graph quad-pattern)
184
((-2 |urn:dydra|:|named|) t)
187
(wild-pattern-p (wild-quad-pattern-p quad-pattern))
188
(visibility-cache (make-hash-table :test 'equalp))
189
(test-rsid (make-array spocq.i::+rsid-length+ :element-type 'unsigned-byte))
190
(default-graph-term-id (rlmdb:transaction-default-context-term-id lmdb:*transaction*)))
191
;;(let ((%key-quad (cffi:foreign-alloc '(:struct rdfcache::quad))))
192
(cffi:with-foreign-objects ((%quad-pattern '(:struct rdfcache::quad))
193
(%key-quad '(:struct rdfcache::quad)))
194
(lmdb::with-empty-value (%key)
195
(lmdb::with-empty-value (%value)
196
(flet ((map-for-graph (quad-pattern)
198
(incf spocq.i::*match-requests*)
199
(quad-to-quad-record quad-pattern %quad-pattern)
200
(%copy-quad %quad-pattern %key-quad)
202
;;(%print-quad %quad-pattern *trace-output*)
203
;;(%print-quad %key-quad *trace-output*)
204
(lmdb:with-cursor (cur)
205
(let ((%cursor (lmdb::handle cur)))
206
(labels ((get-quad (get-op)
207
;(lmdb::with-empty-value (%key)
208
;(lmdb::with-empty-value (%value)
211
(setf (%mdb-val-size %key) 16
212
(%mdb-val-data %key) %key-quad)
213
;; (%print-quad %key-quad *trace-output*)
216
;;(print :key-quad *trace-output*)
217
;;(%print-quad %key-quad *trace-output*)
218
(let ((return-code (liblmdb:cursor-get %cursor %key %value get-op)))
219
;; (print (list quad-pattern return-code))
220
(alexandria:switch (return-code)
222
(call-with-quad-entry %key %value))
226
(lmdb::unknown-error return-code)))))
227
(call-with-quad-entry (%k %v)
228
;;(print :call-with-quad-entry)
229
(assert (= 16 (cffi:pointer-address (cffi:foreign-slot-value %k '(:struct liblmdb:val) 'liblmdb:mv-size)))
231
"key size is invalid: ~s" (cffi:pointer-address (cffi:foreign-slot-value %k '(:struct liblmdb:val) 'liblmdb:mv-size)))
232
(let* ((%index-quad (%mdb-val-data %k))
233
(%index-sha1 (%mdb-val-data %v))
234
;; (index-sha1-addr (cffi:pointer-address %index-sha1))
236
;; continue until either no longer matched or the operator returns nil
237
;; (print (list :no named-only :qm quad-map :qgi quad-graph-index :g (cffi:mem-aref %index-quad 'term-id quad-graph-index)))
238
;; (%print-quad %index-quad *trace-output*)
240
(print (list :rms named-only
241
(= (cffi:mem-aref %index-quad 'term-id quad-graph-index) default-graph-term-id)
243
(%quad-match-p %quad-pattern %index-quad)))
244
(cond ((and named-only (= (cffi:mem-aref %index-quad 'term-id quad-graph-index) default-graph-term-id))
247
((or wild-pattern-p (%quad-match-p %quad-pattern %index-quad))
248
;; iff still in range,
250
;; recall or derive visibility
251
(%decode-revision-sequence-id %index-sha1 test-rsid)
252
#+(or) (print (list :rsid test-rsid))
253
(multiple-value-bind (visible known) (gethash test-rsid visibility-cache)
256
(let* ((revision-sequence (replica-get-revision-sequence (database-repository index)
258
(visibility (spocq.i::test-uuid-range-visibility first last revision-sequence :mode mode)))
259
(setf (gethash (copy-seq test-rsid) visibility-cache) visibility)))
260
;; if visible apply op and return the yes/no continue indication
261
(map-repository-statements-callback operator %index-quad)
264
;; without constraints return the raw values
265
(t (funcall operator %index-quad %index-sha1))))
268
(loop for op = :+set-range+ then :+next+
272
finally (return count)))))))
273
(typecase (graph quad-pattern)
274
(cons (loop with single-graph-quad-pattern = (copy-quad-pattern quad-pattern)
275
for graph in (graph quad-pattern)
276
do (progn (setf (graph single-graph-quad-pattern) graph))
277
sum (map-for-graph single-graph-quad-pattern)))
278
(t (when (eql (graph quad-pattern) -2)
279
(setf (graph quad-pattern) 0))
280
(map-for-graph quad-pattern))))))))))
283
(defmethod rlmdb:map-repository-statements-filtered (operator (index rlmdb::replicable-quad-database) (quad-pattern t) filter)
284
"Map across statements according to logic which combined the replication visibility logic with the interval relation logic.
286
In the replicated case, however, each index entry is used, first to check in an expression-related cache for a visibility determination.
287
the cache key is the sequence sha1, as the index entry does not de-duplicate sequences.
288
if a cache entry is present, that determines the respective statement visibilty.
289
if no cache entry is present, the actual sha1 hash is decoded and used to retrieve the complete revision sequence.
290
this is used to test visibility given the transaction revision - latest or temporally significant.
291
the eventual determination is then cached per sequence sha1 and then determines visibility.
294
(lmdb:with-database (index)
295
(let* ((cur (lmdb:make-cursor index :transaction lmdb:*transaction*))
296
(named-only (case (graph quad-pattern)
297
((-2 |urn:dydra|:|named|) t)
300
(wild-pattern-p (wild-quad-pattern-p quad-pattern))
301
(visibility-cache (make-hash-table :test 'equalp))
302
(test-rsid (make-array spocq.i::+rsid-length+ :element-type 'unsigned-byte))
305
(default-graph-term-id (rlmdb:transaction-default-context-term-id lmdb:*transaction*)))
306
;;(let ((%key-quad (cffi:foreign-alloc '(:struct rdfcache::quad))))
307
(cffi:with-foreign-objects ((%quad-pattern '(:struct rdfcache::quad))
308
(%key-quad '(:struct rdfcache::quad)))
309
(lmdb::with-empty-value (%key)
310
(lmdb::with-empty-value (%value)
311
(flet ((map-for-graph (quad-pattern)
313
(incf spocq.i::*match-requests*)
314
(quad-to-quad-record quad-pattern %quad-pattern)
315
(%copy-quad %quad-pattern %key-quad)
317
;;(%print-quad %quad-pattern *trace-output*)
318
;;(%print-quad %key-quad *trace-output*)
319
(lmdb:with-cursor (cur)
320
(let ((%cursor (lmdb::handle cur)))
321
(labels ((get-quad (get-op)
322
;(lmdb::with-empty-value (%key)
323
;(lmdb::with-empty-value (%value)
326
(setf (%mdb-val-size %key) 16
327
(%mdb-val-data %key) %key-quad)
328
;; (%print-quad %key-quad *trace-output*)
331
;;(print :key-quad *trace-output*)
332
;;(%print-quad %key-quad *trace-output*)
333
(let ((return-code (liblmdb:cursor-get %cursor %key %value get-op)))
334
;; (print (list quad-pattern return-code))
335
(alexandria:switch (return-code)
338
(call-with-quad-entry %key %value))
342
(lmdb::unknown-error return-code)))))
343
(call-with-quad-entry (%k %v)
344
;;(print :call-with-quad-entry)
345
(assert (= 16 (%mdb-val-size %k))
347
"key size is invalid: ~s" (%mdb-val-size %k))
348
(let* ((%index-quad (%mdb-val-data %k))
349
(%index-sha1 (%mdb-val-data %v))
350
(visibility-bytes (%mdb-val-size %v))
351
;; (index-sha1-addr (cffi:pointer-address %index-sha1))
353
;; continue until either no longer matched or the operator returns nil
354
;; (print (list :no named-only :qm quad-map :qgi quad-graph-index :g (cffi:mem-aref %index-quad 'term-id quad-graph-index)))
355
;; (%print-quad %index-quad *trace-output*)
357
(print (list :rms named-only
358
(= (cffi:mem-aref %index-quad 'term-id quad-graph-index) default-graph-term-id)
360
(%quad-match-p %quad-pattern %index-quad)))
361
(cond ((and named-only (= (cffi:mem-aref %index-quad 'term-id quad-graph-index) default-graph-term-id))
364
((or wild-pattern-p (%quad-match-p %quad-pattern %index-quad))
365
;; iff still in range,
366
;; recall or derive visibility
367
(if (zerop visibility-bytes)
368
(if (map-repository-statements-filtered-callback operator %index-quad nil nil)
370
(return-from call-with-quad-entry nil))
371
(flet ((continue-with-success (first last)
372
;; success values augment the quad to be bound in the bgp
373
;; continuation control is left to continue
374
(unless (map-repository-statements-filtered-callback operator %index-quad first last)
375
;; terminate iff caller indicates to stop
376
(return-from call-with-quad-entry nil))))
377
(%decode-revision-sequence-id %index-sha1 test-rsid)
378
#+(or) (print (list :rsid test-rsid))
379
(multiple-value-bind (visibility known) (gethash test-rsid visibility-cache)
381
(flet ((record-bounds (first last)
382
(setf visibility (list first last))))
383
(declare (dynamic-extent #'record-bounds))
384
(let ((version-map (replica-get-revision-sequence (database-repository index)
386
;; this will need to record a list of visibilities
387
(filter-matched-statements filter #'record-bounds version-map)))
388
(setf (gethash (copy-seq test-rsid) visibility-cache) visibility))
391
;; if visible apply op and return the yes/no continue indication
392
(apply #'continue-with-success visibility))
397
(loop for op = :+set-range+ then :+next+
398
while (get-quad op)))))))
399
(typecase (graph quad-pattern)
400
(cons (loop with single-graph-quad-pattern = (copy-quad-pattern quad-pattern)
401
for graph in (graph quad-pattern)
402
do (progn (setf (graph single-graph-quad-pattern) graph)
403
(map-for-graph single-graph-quad-pattern))))
405
(setf (graph quad-pattern) 0))
406
(map-for-graph quad-pattern)))))))
407
(values scan-count match-count visibility-cache))))
410
;;; !!! filter form should permit revision id intervals as well as atomic values
413
(defgeneric match-replicated-statement-visibility (continue revision-sequence-map relation)
416
(defgeneric match-replicated-revision-interval (continue map position-or-test)
417
(:documentation "Given a visibility map and either a relation - as either an expression or a function,
418
or a revision identifier - as a vector; determine whether the visibility satisfies the relation and,
419
if so, invoke the continuation with the bounds.")
420
(:method (continue map (revision-designator vector))
421
"Given just a position, locate the immediately previous operation.
422
If one is present and if it inserted the statement, locate any end position
423
and yield the bounds."
424
(let ((position-start (position-uuid-vector revision-designator map)))
426
(let ((uuid-start (aref map position-start)))
427
(when (insert-uuid-p uuid-start)
428
(let ((position-end (revision-transition-next (1+ position-start) map :transition-p #'delete-uuid-p)))
429
(funcall continue uuid-start (when position-end (aref map position-end)))))))))
430
(:method (continue map (relation-test function))
431
"Given a predicate function, apply it to the map and allow it to yield any result."
432
(funcall relation-test continue map))
433
(:method (continue map (relation-expression cons))
434
"Given a test expression, apply it to the map and yield any match."
435
(test-visibility-map continue map relation-expression)))
438
;;; (trace match-statement-visibility-disjoint statement-visibility-next-bounds statement-visibility-previous-bounds)
441
each basic search functions yields one of threeintervls in relation to the given designator
443
previous designator next
449
where the bounds and bounds-after can yielf null for the end
454
(defun search-revision-bounds-next (revision-designator sequence &optional (test #'insert-uuid-p))
455
"return start and end bounds for the interval starting at a revision > the given designator
456
iff it is accepted by the test.
457
a beginning w/o a complementing end yields a null end."
458
(let* ((position-<= (position-uuid-vector revision-designator sequence)))
460
(let ((uuid-<= (aref sequence position-<=)))
461
(when (not (funcall test uuid-<=))
462
(let ((position-start (revision-transition-next (1+ position-<=) sequence :transition-p test)))
464
(let ((uuid-start (aref sequence position-start))) ;; no need to retest
465
(let ((position-end (revision-transition-next (1+ position-start) sequence :transition-p (complement test))))
466
(values uuid-start (when position-end (aref sequence position-end)) position-start position-end))))))))))
470
(defun search-revision-bounds>= (revision-designator sequence &optional (test #'insert-uuid-p))
471
"return start and end bounds for the interval starting at a revision >= the given designator
472
iff it is accepted by the test.
473
a beginning w/o a complementing end yields a null end.
474
the default predicate locates insert-delete intervals."
475
;; in this case, the immediate position is itself the lower bounds
476
(flet ((try-next-transition (start)
477
(let ((position-> (revision-transition-next start sequence :transition-p test)))
479
(let ((uuid-start (aref sequence position->))) ;; no need to retest
480
(let ((position-end (revision-transition-next (1+ position->) sequence :transition-p (complement test))))
481
(values uuid-start (when position-end (aref sequence position-end)))))))))
482
(let* ((position-<= (position-uuid-vector revision-designator sequence)))
483
(cond (position-<= ;; some earlier transition exists
484
(let ((uuid-start (aref sequence position-<=)))
485
;; if the revision at that location meets the test
486
(if (and (revision-coincident-p uuid-start revision-designator) (funcall test uuid-start))
487
;; then locate its matching successor
488
(let ((position-end (revision-transition-next (1+ position-<=) sequence :transition-p (complement test))))
489
(values uuid-start (when position-end (aref sequence position-end))))
490
;; otherwise, look for the next one
491
(try-next-transition (1+ position-<=)))))
492
((> (length sequence) 0) ;; nothing earlier, look for next
493
(try-next-transition 0))))))
495
(defun search-revision-bounds< (revision-designator sequence &optional (test #'insert-uuid-p))
496
"return start and end bounds for the interval ending at a revision < the given designator.
497
the end is established by being accepted by a complement of the test.
498
the default test locates insert-delete intervals."
499
;; in this case, the immediate position is itself the lower bounds
500
(flet ((try-previous-transition (end)
501
(let ((position< (revision-transition-previous end sequence :transition-p (complement test))))
503
(let ((uuid-end (aref sequence position<))) ;; no need to retest
504
(let ((position-start (revision-transition-previous (1- position<) sequence :transition-p test)))
506
(values (aref sequence position-start) uuid-end))))))))
507
(let* ((position<= (position-uuid-vector revision-designator sequence)))
509
;; some earlier transition exists
510
(let ((uuid-end (aref sequence position<=)))
511
;; if it meets the test return the previous test/fail bounds
512
;; otherwise, iff it is previous, try it as end, otherwise locate its start and try before there
513
(cond ((funcall test uuid-end)
514
(try-previous-transition (1- position<=)))
515
((revision-coincident-p revision-designator uuid-end)
516
;; if it is coincident, look for a previous interval
517
(let ((position-start (revision-transition-previous (1- position<=) sequence :transition-p (complement test))))
519
(try-previous-transition (1- position-start)))))
521
;; if the end precedes, find its start
522
(let ((position-start (revision-transition-previous (1- position<=) sequence :transition-p test)))
524
(values (aref sequence position-start) uuid-end))))))))))
526
(defun search-revision-bounds<= (revision-designator sequence &optional (test #'insert-uuid-p))
527
"return start and end bounds for the interval ending at a revision < the given designator.
528
the end is established by being accepted by a complement of the test.
529
the default test locates insert-delete intervals."
530
;; in this case, the immediate position is itself the lower bounds
531
(flet ((try-previous-transition (end)
532
(let ((position< (revision-transition-previous end sequence :transition-p (complement test))))
534
(let ((uuid-end (aref sequence position<))) ;; no need to retest
535
(let ((position-start (revision-transition-previous (1- position<) sequence :transition-p test)))
537
(values (aref sequence position-start) uuid-end))))))))
538
(let* ((position<= (position-uuid-vector revision-designator sequence)))
540
;; some earlier transition exists
541
(let ((uuid<= (aref sequence position<=)))
542
;; if it meets the test return the previous test/fail bounds
543
;; otherwise, iff it is previous, try it as end, otherwise locate its start and try before there
544
(cond ((funcall test uuid<=)
545
(let ((position-end (revision-transition-next (1+ position<=) sequence :transition-p (complement test))))
546
(values uuid<= (when position-end (aref sequence position-end)))))
548
;; if the end precedes, find its start
549
(let ((position-start (revision-transition-previous (1- position<=) sequence :transition-p test)))
551
(values (aref sequence position-start) uuid<=))))))))))
553
(defun search-revision-bounds> (revision-designator sequence &optional (test #'insert-uuid-p))
554
"return start and end bounds for the interval starting at a revision > the given designator
555
the start is established by being accepted by the test.
556
the default test locates insert-delete intervals."
557
;; in this case, the immediate position is itself the lower bounds
558
(flet ((try-next-transition (start)
559
(let ((position-> (revision-transition-next start sequence :transition-p test)))
561
(let ((uuid-start (aref sequence position->))) ;; no need to retest
562
(let ((position-end (revision-transition-next (1+ position->) sequence :transition-p (complement test))))
563
(values uuid-start (when position-end (aref sequence position-end)))))))))
564
(let* ((position<= (position-uuid-vector revision-designator sequence)))
566
;; some earlier transition exists
567
(let ((uuid-start (aref sequence position<=)))
568
;; if it fails the test, return the next test/fail bounds
569
;; otherwise, locate its end and try from there
570
(cond ((not (funcall test uuid-start))
571
(try-next-transition (1+ position<=)))
573
(let ((position-end (revision-transition-next (1+ position<=) sequence :transition-p (complement test))))
575
(try-next-transition (1+ position-end))))))))
576
((> (length sequence) 0)
577
;; no earlier transition exists, look for from the first
578
(try-next-transition 0))))))
582
(defun match-statement-visibility-tail (continue map)
583
"if visible at all, return the first location and possible interval bounds"
584
(when (plusp (length map))
585
(let* ((first-set-end (revision-transition-next 0 map :transition-p #'identity))
586
(start-uuid (aref map first-set-end)))
587
(when (insert-uuid-p start-uuid)
588
(loop for end-position = (revision-transition-next (1+ first-set-end) map :transition-p #'delete-uuid-p)
590
for end-uuid = (aref map end-position)
591
when (delete-uuid-p end-uuid)
592
return (funcall continue start-uuid end-uuid))))))
596
(defun match-statement-visibility-after (continue vector start &optional (end start))
597
"return the first interval visible after, but not at, end:
598
- the @location or later must be a delete
599
- after that, there must be a subsequent insert. if there is, it will be after rather than met-by
600
as coincident insert/delete assertions resolve to one delete.
601
return the visible bounds."
603
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds> end vector)
605
(funcall continue stmt-start stmt-end)))))
607
(defun match-statement-visibility-before (continue vector start &optional (end start))
608
"return the last interval visible before, but not at, start
609
- the assertion @location must be delete
610
- there must be a previous insert. if there is, the delete@location must precede the location timestamp
611
return the visible bounds"
612
(declare (ignore end))
613
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds< start vector)
614
(when (and stmt-end (revision-before-p stmt-end start))
615
(funcall continue stmt-start stmt-end))))
618
(defun match-statement-visibility-contains (continue vector start &optional (end start))
619
"visible before start and after end, but not at either
620
return the visible bounds"
622
;; cannot contain unbounded interval
623
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds<= end vector)
624
(when (and stmt-start
625
(revision-before-p stmt-start start)
627
(revision-before-p end stmt-end)))
628
(funcall continue stmt-start stmt-end)))))
630
(defun match-statement-visibility-disjoint (continue vector start &optional (end start))
631
"return some interval visible before or after but not during
632
return the previous or following"
633
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds< start vector)
634
(if (and stmt-end (revision-before-p stmt-end start))
635
(funcall continue stmt-start stmt-end)
637
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds> end vector)
639
(funcall continue stmt-start stmt-end)))))))
642
(defun match-statement-visibility-during (continue vector start &optional (end start))
643
"return the first interval visible after start and before end but neither visible at start nor at end."
644
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds<= start vector)
645
(when (and stmt-start
646
stmt-end ; must be bounded
647
(revision-after-p stmt-start start)
648
(or (null end) (revision-before-p stmt-end end)))
649
(funcall continue stmt-start stmt-end))))
651
(defun match-statement-visibility-equals (continue vector start &optional (end start))
652
"visible with equal bounds
655
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds<= start vector)
656
(when (and stmt-start (revision-coincident-p stmt-start start)
657
stmt-end (revision-coincident-p stmt-end end))
658
(funcall continue stmt-start stmt-end)))))
661
(defun match-statement-visibility-finished-by (continue vector start &optional (end start))
662
"visible before start and up to end
663
yield the visible bounds"
665
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds<= start vector)
666
(when (and stmt-start (revision-before-p stmt-start start)
667
stmt-end (revision-coincident-p stmt-end end))
668
(funcall continue stmt-start stmt-end)))))
670
(defun match-statement-visibility-finishes (continue vector start &optional (end start))
671
"visible after start and up to end
672
yield the visible bounds"
674
(let* ((position<= (position-uuid-vector end vector)))
676
;; some earlier transition exists
677
(let ((stmt-end (aref vector position<=)))
678
;; if it meets the test return the previous test/fail bounds
679
;; otherwise, iff it is previous, try it as end, otherwise locate its start and try before there
680
(when (and (delete-uuid-p stmt-end) (revision-coincident-p end stmt-end))
681
(let ((position-start (revision-transition-previous (1- position<=) vector :transition-p #'insert-uuid-p)))
683
(funcall continue (aref vector position-start) stmt-end)))))))))
686
(defun match-statement-visibility-in (continue vector start &optional (end start))
687
"visible at start or later and at end or earlier, but not both at
688
return the first visible bounds
689
https://www.w3.org/TR/owl-time/#time:intervalIn"
691
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds>= start vector)
692
(when (and stmt-start
694
(if (revision-coincident-p stmt-start start)
695
(revision-before-p stmt-end end)
696
(not (revision-after-p stmt-end end))))
697
(funcall continue stmt-start stmt-end)))))
699
(defun match-statement-visibility-included-by (continue vector start &optional (end start))
700
"visible on or after start and up to at or before end
701
yield the visible bounds"
702
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds>= start vector)
703
(when (and stmt-start stmt-end
704
(or (null end) (not (revision-after-p stmt-end end))))
705
(funcall continue stmt-start stmt-end))))
707
(defun match-statement-visibility-includes (continue vector start &optional (end start))
708
"visible at or before start and up to or after end
709
yield the visible bounds"
711
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds<= start vector)
712
(when (and stmt-start
713
(or (null stmt-end) (not (revision-before-p stmt-end end))))
714
(funcall continue stmt-start stmt-end)))))
717
(defun match-statement-visibility-meets (continue vector start &optional (end start))
719
return the first visible bounds"
720
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds< start vector)
721
(when (and stmt-end (revision-coincident-p stmt-end end))
722
(funcall continue stmt-start stmt-end))))
724
(defun match-statement-visibility-met-by (continue vector start &optional (end start))
726
yield the first visible bounds"
728
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds>= end vector)
729
(when (and stmt-start (revision-coincident-p stmt-start end))
730
(funcall continue stmt-start stmt-end)))))
733
(defun match-statement-visibility-overlapped-by (continue vector start &optional (end start))
734
"visible within bounds and after
735
yield the visible bounds
736
https://www.w3.org/TR/owl-time/#time:intervalOverlappedBy"
738
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds<= end vector)
739
(when (and stmt-start stmt-end
740
(revision-after-p stmt-start start)
741
(revision-before-p stmt-start end)
742
(revision-after-p stmt-end end))
743
(funcall continue stmt-start stmt-end)))))
745
(defun match-statement-visibility-overlaps (continue vector start &optional (end start))
746
"visible within bounds and before
747
yield the visible bounds
748
https://www.w3.org/TR/owl-time/#time:intervalOverlaps"
749
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds<= vector start)
750
(when (and stmt-start stmt-end
751
(revision-before-p stmt-start start)
752
(or (null end) (revision-before-p stmt-end end)))
753
(funcall continue stmt-start stmt-end))))
756
(defun match-statement-visibility-started-by (continue vector start &optional (end start))
757
"visible from start until after end
758
yield the visible bounds"
760
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds>= vector start)
761
(when (and stmt-start (revision-coincident-p stmt-start start)
762
(or (null stmt-end) (revision-after-p stmt-end end)))
763
(funcall continue stmt-start stmt-end)))))
765
(defun match-statement-visibility-starts (continue vector start &optional (end start))
766
"visible from start until before end
767
yield the visible bounds"
768
(multiple-value-bind (stmt-start stmt-end) (search-revision-bounds>= vector start)
769
(when (and stmt-start (revision-coincident-p stmt-start start)
770
(or (null end) (revision-before-p stmt-end end)))
771
(funcall continue stmt-start stmt-end))))
775
#+(or) ;; no longer used
776
(defun test-visibility-map (continue-op map relation)
777
(declare (dynamic-extent continue-op))
778
(destructuring-bind (op . args) relation
780
(|time|:|intervalAfter| (apply #'match-statement-visibility-after continue-op map args))
781
(|time|:|intervalBefore| (apply #'match-statement-visibility-before continue-op map args))
782
(|time|:|intervalContains| (apply #'match-statement-visibility-contains continue-op map args))
783
(|time|:|intervalDisjoint| (apply #'match-statement-visibility-disjoint continue-op map args))
784
(|time|:|intervalDuring| (apply #'match-statement-visibility-during continue-op map args))
785
(|time|:|intervalEquals| (apply #'match-statement-visibility-equals continue-op map args))
786
(|time|:|intervalFinishedBy| (apply #'match-statement-visibility-finished-by continue-op map args))
787
(|time|:|intervalFinishes| (apply #'match-statement-visibility-finishes continue-op map args))
788
(|time|:|intervalIncludedBy| (apply #'match-statement-visibility-included-by continue-op map args))
789
(|time|:|intervalIncludes| (apply #'match-statement-visibility-includes continue-op map args))
790
(|time|:|intervalIn| (apply #'match-statement-visibility-in continue-op map args))
791
(|time|:|intervalMeets| (apply #'match-statement-visibility-meets continue-op map args))
792
(|time|:|intervalMetBy| (apply #'match-statement-visibility-met-by continue-op map args))
793
(|time|:|intervalOverlappedBy| (apply #'match-statement-visibility-overlapped-by continue-op map args))
794
(|time|:|intervalOverlaps| (apply #'match-statement-visibility-overlaps continue-op map args))
795
(|time|:|intervalStartedBy| (apply #'match-statement-visibility-started-by continue-op map args))
796
(|time|:|intervalStarts| (apply #'match-statement-visibility-starts continue-op map args))
797
((spocq.a:|and| spocq.a:|&&|)
798
(destructuring-bind (first &rest rest) args
800
(flet ((continue-op (start end)
801
(declare (ignore start end))
802
(test-visibility-map continue-op map `(spocq.a:|and| ,@rest))))
803
(declare (dynamic-extent #'continue-op))
804
(test-visibility-map #'continue-op map first))
805
(test-visibility-map continue-op map first))))
806
((spocq.a:|or| spocq.a:|\|\||)
807
(flet ((continue-op (start end)
808
(return-from test-visibility-map (funcall continue-op start end))))
809
(declare (dynamic-extent #'continue-op))
810
(loop for expression in args
811
do (test-visibility-map #'continue-op map expression))))
812
((spocq.a:|not| spocq.a:|!|)
813
(flet ((continue-op (start end)
814
(declare (ignore start end))
815
(return-from test-visibility-map nil)))
816
(declare (dynamic-extent #'continue-op))
817
(test-visibility-map #'continue-op map (first args)))
818
;; if it does not invoke the continueation
819
(match-statement-visibility-tail continue-op map)))))
822
each statement is associated with a revision sequence vector which represents the merged state of that statement.
823
the actualy sequence is not stored for each statement.
824
in its stead, the statement's index value is the sequence sha1 hash.
825
the hash value, in turn, locates the actual sequence in the respective repository's revision sequence database.
827
in order to retrieve the statements for a given pattern, the pattern locates and constrains a scan over the statements index values.
828
the abstract process is the same as for an isolated repository.
829
in the replated case, however, each index entry is used, first to check in a transaction cache for a visibility determination.
830
the cache key is the native index entry, rather than the sha1, as the entry remains stable for the transaction duration.
831
if a cache entry is present, that determines the respective statement visibilty.
832
if no cache entry is present, the actual sha1 hash is decoded and used to retrieve the complete revision sequence.
833
this is used to test visibility given the transaction revision - latest or temporally significant.
834
the eventual determination is then cached per sequence sha1 and then determines visibility.
836
in order to insert a statement, the given quad value locates the revision sequence sha1 id.
837
if none is present a unary sequence is re-used or created on demand.
838
if a sha1 id is present, the sha1 is used to check a revision cache for a replacement sha1 id.
839
if the replacement is known, that is written back as the replacement index value.
840
if none is present, the complete revision sequence is retrieved, the new revision is merged into it,
841
the sha1 is computed and cached and written back as the replacement index value.
846
(defgeneric replica-get-revision-sequence (repository %index-sha1 &key if-does-not-exist)
847
(:method ((repository rlmdb::replicable-repository) %revision-sequence-id &key (if-does-not-exist :error))
848
(with-lmdb-values ((%key spocq.i::+rsid-length+ %revision-sequence-id)
849
(%value 0 (cffi:null-pointer)))
850
(let* ((db (repository-revision-sequence-database repository))
851
(return-code (liblmdb:get (lmdb::handle lmdb:*transaction*) (lmdb::handle db) %key %value)))
852
(cond ((zerop return-code)
853
(%decode-revision-sequence (%mdb-val-data %value) (%mdb-val-size %value)))
854
((eql return-code liblmdb:+notfound+)
855
(ecase if-does-not-exist
856
(:error (error "replica-get-revision-sequence: revision-seqience-not-found"))
859
(lmdb::unknown-error return-code)))))))
862
(defgeneric replica-put-revision-sequence-id (repository revision-sequence-id revision-sequence)
863
(:method ((repository rlmdb::replicable-repository) (revision-sequence-id vector) (revision-sequence vector))
864
(cffi:with-foreign-pointer (%sequence-id spocq.i::+rsid-length+)
865
(%encode-revision-sequence-id %sequence-id revision-sequence-id)
867
(format *trace-output* "~%rprsi: ~s [~{~2,'0x~}] ~{~% ~s~}~%"
868
(type-of revision-sequence-id) (coerce revision-sequence-id 'list)
869
(map 'list #'spocq.i::uuid-to-string revision-sequence))
870
(replica-put-revision-sequence-id repository %sequence-id revision-sequence)))
872
(:method ((repository rlmdb::replicable-repository) (%revision-sequence-id SB-SYS:SYSTEM-AREA-POINTER) (revision-sequence vector))
873
(cffi:with-foreign-pointer (%revision-sequence (* (length revision-sequence) spocq.i::+uuid-length+))
874
(%encode-revision-sequence %revision-sequence revision-sequence)
875
(with-lmdb-values ((%key spocq.i::+rsid-length+ %revision-sequence-id)
876
(%value (* (length revision-sequence) 16) %revision-sequence))
877
(let* ((db (repository-revision-sequence-database repository))
878
(return-code (liblmdb:put (lmdb::handle lmdb:*transaction*) (lmdb::handle db) %key %value liblmdb:+NODUPDATA+)))
879
(cond ((zerop return-code)
882
(lmdb::unknown-error return-code))))))))
884
(defvar *operation-uuid*)
885
(defvar *visibility-cache*)
887
(defmethod rlmdb::replicable-repository-update-field ((repository rlmdb::replicable-repository)
888
(solution-field array)
890
"replace each statement's index entry with the id for a revision sequence
891
which has been augmented by the given operation id (= timestamp x state).
892
start with index[0], compute the new sequence id and then replicate that
893
across all indices. if the statement was present, the new sequence merges
894
the new operation id into the old. where the statement is new, the sequence
895
is just the new operation."
896
(lmdb:with-transaction ((lmdb:transaction (lmdb:make-transaction repository :flags 0))
897
:initial-disposition :begin :normal-disposition :commit)
898
;; cache the new revision sequence respective the old when combined with the operation-uuid
899
(let ((*visibility-cache* (make-hash-table :test 'eql)))
900
(flet ((insert-page (page)
901
(assert-argument-type repository-insert-field page
902
(or (simple-array fixnum (* 3)) (simple-array fixnum (* 4))))
903
(spocq.i::trace-algebra replica-update-field (repository-id repository) solution-field (spocq.i::term-value-field page))
904
(ecase (array-dimension solution-field 1)
905
(3 (spocq.i::do-solution-field (subject predicate object) solution-field
906
(when (and (/= subject 0) (/= predicate 0) (/= object 0))
907
(rlmdb::repository-insert-statement repository lmdb:transaction
908
rdfcache:*default-context-number* subject predicate object))))
909
(4 (spocq.i::do-solution-field (subject predicate object graph) page
910
(when (and (/= subject 0) (/= predicate 0) (/= object 0) (/= graph 0))
911
(rlmdb::repository-insert-statement repository lmdb:transaction
912
graph subject predicate object)))))
913
(array-dimension solution-field 0)))
914
(ecase (array-rank solution-field)
915
(1 (loop for solution-field-page across solution-field
916
sum (insert-page solution-field-page)))
917
(2 (insert-page solution-field)))))))
920
(defmethod rlmdb::repository-insert-statement
921
((repository rlmdb:replicable-repository) (transaction rlmdb:transaction) graph subject predicate object)
922
(cffi:with-foreign-objects ((%key-quad '(:struct quad)))
923
(with-lmdb-values ((%key (load-time-value (cffi:foreign-type-size '(:struct quad))) %key-quad)
924
(%value 0 (cffi:null-pointer)))
925
#+(or) (print (list :key (%mdb-val-size %key) (%mdb-val-data %key) :key-quad %key-quad))
926
(labels ((compute-new-rsid (%key)
927
(let ((db (aref (repository-quad-databases repository) 0)))
928
(let ((return-code (liblmdb:get (lmdb::handle transaction) (lmdb::handle db) %key %value)))
929
#+(or) (print (list :cnr return-code))
930
(cond ((zerop return-code)
931
;; merge the revision-op into an existing sequence
932
(assert (= (%mdb-val-size %value) spocq.i::+rsid-length+) ()
933
"repository-update-field: invalid revision sequence id size: ~s"
934
(%mdb-val-size %value))
935
(let* ((%old-rsid (%mdb-val-data %value))
936
(%old-rsid-addr (cffi:pointer-address %old-rsid)) ;; cached based on the prt address
937
(cached-rsid (gethash %old-rsid-addr *visibility-cache*)))
938
(or cached-rsid ;; use the cached hash sequence
939
;; or compute the new one based on the revision operation
940
(let* ((revision-sequence (replica-get-revision-sequence repository %old-rsid))
941
(new-revision-sequence (spocq.i::merge-uuid-visibility *operation-uuid* revision-sequence))
942
(new-rsid (spocq.i::compute-revision-sequence-id new-revision-sequence)))
943
(replica-put-revision-sequence-id repository new-rsid new-revision-sequence)
944
(setf (gethash %old-rsid-addr *visibility-cache*) new-rsid)
946
((eql return-code liblmdb:+notfound+)
947
;; add a new sequence
948
(let ((cached-rsid (gethash 0 *visibility-cache*)))
950
(let* ((revision-sequence (make-array 1 :initial-contents (list *operation-uuid*)))
951
(new-rsid (spocq.i::compute-revision-sequence-id revision-sequence)))
952
(replica-put-revision-sequence-id repository new-rsid revision-sequence)
953
(setf (gethash 0 *visibility-cache*) new-rsid)))))
955
(lmdb::unknown-error return-code))))))
957
(if (< term-number 0)
958
(+ term-number #x100000000)
960
(setf (%quad-context %KEY-quad) (wrap graph)
961
(%quad-subject %KEY-quad) (wrap subject)
962
(%quad-predicate %KEY-quad) (wrap predicate)
963
(%quad-object %KEY-quad) (wrap object))
964
(let* ((new-rsid (compute-new-rsid %key)))
965
(cffi:with-foreign-pointer (%new-rsid spocq.i::+rsid-length+)
966
(%encode-revision-sequence-id %new-rsid new-rsid)
967
(loop for db across (repository-quad-databases repository)
968
do (setf (%mdb-val-size %value) spocq.i::+rsid-length+
969
(%mdb-val-data %value) %new-rsid)
970
; do (print (list db :key (%mdb-val-size %key) (%mdb-val-data %key) :value (%mdb-val-size %value) (%mdb-val-data %value)))
971
do (let ((return-code (liblmdb:put (lmdb::handle transaction) (lmdb::handle db) %key %value 0)))
972
(unless (zerop return-code)
973
(lmdb::unknown-error return-code)))))))
976
(defgeneric rlmdb::repository-accept-field (repository source)
977
(:documentation "Given a function or sequence source, iterate over the statements
978
and insert/delete each as per specified operation.
979
A function source should be an operator for two arguments (operation-continuation statement-continuation).
980
It is invoked in the context of a transaction to provide the operation and the statements.
982
The core operation is as for replicable-repository-update-field")
985
(:method ((repository rlmdb:replicable-repository) (source function))
986
(lmdb:with-transaction ((transaction (lmdb:make-transaction repository :flags 0))
987
:initial-disposition :begin :normal-disposition :commit)
988
;; cache the new revision sequence respective the old when combined with the operation-uuid
989
(let ((visibility-cache (make-hash-table :test 'eql))
991
(x-record (when spocq.i::*transaction* (spocq.i::transaction-record spocq.i::*transaction*))))
992
(cffi:with-foreign-objects ((%key-quad '(:struct rdfcache::quad)))
993
(with-lmdb-values ((%key 16 %key-quad)
994
(%value 0 (cffi:null-pointer)))
995
(labels ((statement-to-key (graph subject predicate object %quad)
996
(setf (%quad-context %quad) (wrap graph)
997
(%quad-subject %quad) (wrap subject)
998
(%quad-predicate %quad) (wrap predicate)
999
(%quad-object %quad) (wrap object)))
1002
((nil) (error "term-id: invalid term: ~s." term))
1003
(|urn:dydra|:|all| spocq.i::*true-all-context-term-number*)
1004
(|urn:dydra|:|default| spocq.i::*true-default-context-term-number*)
1005
(|urn:dydra|:|named| spocq.i::*true-named-context-term-number*)
1006
(|urn:dydra|:|none| spocq.i::*true-none-context-term-number*)
1007
(t (spocq.i::rdfcache-object-term-number x-record term))))
1008
(graph-term-id (term)
1010
((nil) (spocq.i::symbol-term-id |urn:dydra|:|default|))
1011
(t (term-id term))))
1012
(compute-new-rsid (graph subject predicate object)
1013
(let ((db (aref (repository-quad-databases repository) 0)))
1014
(setf (%mdb-val-size %key) 16
1015
(%mdb-val-data %key) %key-quad)
1016
(statement-to-key graph subject predicate object %key-quad)
1017
(let ((return-code (liblmdb:get (lmdb::handle transaction) (lmdb::handle db) %key %value)))
1018
#+(or) (print (list :cnr return-code))
1019
(cond ((zerop return-code)
1020
;; merge the revision-op into an existing sequence
1021
(assert (= (%mdb-val-size %value) spocq.i::+rsid-length+) ()
1022
"repository-update-field: invalid revision sequence id size: ~s"
1023
(%mdb-val-size %value))
1024
(let* ((%old-rsid (%mdb-val-data %value))
1025
(%old-rsid-addr (cffi:pointer-address %old-rsid)) ;; cached based on the prt address
1026
(cached-rsid (gethash %old-rsid-addr visibility-cache)))
1027
(or cached-rsid ;; use the cached hash sequence
1028
;; or compute the new one based on the revision operation
1029
(let* ((revision-sequence (replica-get-revision-sequence repository %old-rsid))
1030
(new-revision-sequence (spocq.i::merge-uuid-visibility operation-uuid revision-sequence))
1031
(new-rsid (spocq.i::compute-revision-sequence-id new-revision-sequence)))
1032
(replica-put-revision-sequence-id repository new-rsid new-revision-sequence)
1033
(setf (gethash %old-rsid-addr visibility-cache) new-rsid)
1035
((eql return-code liblmdb:+notfound+)
1036
;; add a new sequence
1037
(let ((cached-rsid (gethash 0 visibility-cache)))
1039
(let* ((revision-sequence (make-array 1 :initial-contents (list operation-uuid)))
1040
(new-rsid (spocq.i::compute-revision-sequence-id revision-sequence)))
1041
(replica-put-revision-sequence-id repository new-rsid revision-sequence)
1042
(setf (gethash 0 visibility-cache) new-rsid)))))
1044
(lmdb::unknown-error return-code))))))
1046
(if (< term-number 0)
1047
(+ term-number #x100000000)
1049
(insert-statement (statement)
1050
(etypecase statement
1051
(cons (destructuring-bind (subject predicate object graph)
1052
(case (first statement)
1053
((spocq.a:|quad| spocq.a:|triple|) (rest statement))
1056
(insert-statement* (graph-term-id graph) (term-id subject) (term-id predicate) (term-id object))))
1058
(insert-statement* (aref statement 0) (aref statement 1) (aref statement 2) (aref statement 3)))))
1059
(insert-statement* (graph subject predicate object)
1060
(statement-to-key graph subject predicate object %key-quad)
1061
(setf (%mdb-val-size %key) 16
1062
(%mdb-val-data %value) %key-quad)
1063
(let* ((new-rsid (compute-new-rsid graph subject predicate object)))
1064
(cffi:with-foreign-pointer (%new-rsid spocq.i::+rsid-length+)
1065
(%encode-revision-sequence-id %new-rsid new-rsid)
1066
(loop for db across (repository-quad-databases repository)
1067
do (setf (%mdb-val-size %value) spocq.i::+rsid-length+
1068
(%mdb-val-data %value) %new-rsid)
1069
; do (print (list db :key (%mdb-val-size %key) (%mdb-val-data %key) :value (%mdb-val-size %value) (%mdb-val-data %value)))
1071
(let ((return-code (liblmdb:put (lmdb::handle transaction) (lmdb::handle db) %key %value
1072
liblmdb:+NODUPDATA+)))
1073
(unless (zerop return-code)
1074
(lmdb::unknown-error return-code))))))))
1075
(set-operation (new-operation-uuid)
1076
(setf operation-uuid new-operation-uuid)))
1077
(funcall source #'set-operation #'insert-statement))))))))
1080
;; printing repository state
1082
(defgeneric map-revision-sequence-database (operator repository)
1083
(:method (operator (repository rlmdb:replicable-repository))
1084
(lmdb:with-transaction ((transaction (lmdb:make-transaction repository :flags 0))
1085
:initial-disposition :begin :normal-disposition :abort)
1086
(map-revision-sequence-database operator (repository-revision-sequence-database repository))))
1088
(:method (operator (database rlmdb:revision-sequence-database))
1089
(lmdb:ensure-open-database database)
1090
(let* ((cur (lmdb:make-cursor database)))
1091
(cffi:with-foreign-pointer (%sequence-id spocq.i::+rsid-length+)
1092
(%encode-revision-sequence-id %sequence-id (make-array spocq.i::+rsid-length+ :element-type '(unsigned-byte 8) :initial-element 0))
1093
(with-lmdb-value (%key spocq.i::+rsid-length+ %sequence-id)
1094
(lmdb::with-empty-value (%data)
1095
(lmdb:with-cursor (cur)
1096
(labels ((do-rsid (get-op)
1097
(let ((return-code (liblmdb:cursor-get (lmdb::handle cur) %key %data get-op)))
1098
(alexandria:switch (return-code)
1100
(when (= (%mdb-val-size %key) spocq.i::+rsid-length+)
1102
(%decode-revision-sequence-id (%mdb-val-data %key))
1103
(%decode-revision-sequence (%mdb-val-data %data) (%mdb-val-size %data)))))
1107
(lmdb::unknown-error return-code))))))
1109
(loop for op = :+set-range+ then :+next+
1113
finally (return count))))))))))
1116
(defgeneric dump-repository-revision-sequence-index (repository &key stream verbose)
1117
(:method ((repository rlmdb:replicable-repository) &key (stream *standard-output*) verbose)
1118
(declare (ignore verbose))
1119
(flet ((format-entry (rsid revision-sequence)
1120
(format stream "~%[~{~2,'0x~}] (~{~/format-uuid-operation/~^~%~44T~})"
1122
(coerce revision-sequence 'list))
1124
(map-revision-sequence-database #'format-entry repository))))
1127
(defgeneric dump-repository-statement-index (repository &key stream verbose)
1128
(:method ((repository rlmdb:replicable-repository) &key (stream *standard-output*) (verbose nil))
1129
(flet ((report-index-entry (%index-quad %index-rsid)
1130
(let ((rsid (%decode-revision-sequence-id %index-rsid)))
1132
(let ((revision-sequence (replica-get-revision-sequence repository %index-rsid))
1133
(quad (term-number-record-to-quad %index-quad (spocq:make-quad))))
1134
(format stream "~%~%(~s ~s ~s ~s)~%[~{~2,'0x~}] (~{~/format-uuid-operation/~^~%~44T~})"
1135
(spocq:quad-subject quad) (spocq:quad-predicate quad) (spocq:quad-object quad) (spocq:quad-graph quad)
1137
(coerce revision-sequence 'list)))
1138
(format stream "~%~/%format-quad/ [~{~2,'0x~}]"
1140
(coerce rsid 'list)))
1142
(declare (dynamic-extent #'report-index-entry))
1143
(rlmdb:map-repository-statements #'report-index-entry repository #(0 0 0 0)))))
1146
(defgeneric rlmdb::dump-repository (repository &key stream verbose)
1147
(:documentation "dump out the individual database contents for the given repository type:
1148
- plain: the metadata and the spog index
1149
- revisioned: the metadata, revision sequence and spog index
1150
- replicable: the metadata, revision history, revision sequence id index and spog index.")
1152
(:method ((repository-id string) &rest args)
1153
(apply #'rlmdb::dump-repository (spocq.i::repository repository-id) args))
1155
(:method ((repository rlmdb:replicable-repository) &key (stream *standard-output*) (verbose nil))
1156
(let ((*print-pretty* nil))
1157
(format stream "~%metadata~{~% ~s~}" (rlmdb:get-metadata repository))
1158
(format stream "~%revision records~{~% ~s~}" (rlmdb:revision-records repository))
1159
(format stream "~%revision sequence index")
1160
(dump-repository-revision-sequence-index repository :stream stream :verbose verbose)
1161
(format stream "~%spog index")
1162
(dump-repository-statement-index repository :stream stream :verbose verbose))))
1167
(defmethod spocq.i::compute-repository-size ((repository rlmdb:replicable-repository) &key (literal-term-size 0) (verbose nil))
1168
(declare (ignore literal-term-size))
1169
(lmdb:with-transaction ((transaction (lmdb:make-transaction repository #|:flags liblmdb:+rdonly+|#))
1170
:initial-disposition :begin :normal-disposition :abort)
1171
;; no graph database support
1172
(let* ((repository-id (repository-id repository))
1173
(index-database (repository-quad-pattern-index repository #(0 0 0 0)))
1174
(sequence-database (repository-revision-sequence-database repository))
1175
(sequence-id-cache (make-hash-table :test #'equalp))
1176
(revision-sequence-total-count 0)
1177
(revision-sequence-total-bytes 0)
1178
(revision-sequence-referenced-count 0)
1179
(revision-sequence-referenced-bytes 0)
1181
(statement-count-versioned 0)
1183
(index-leaf-bytes 0)
1184
(index-node-bytes 0)
1188
(terms (make-hash-table :test 'equal))
1189
(strings (make-hash-table :test 'equal)))
1190
(flet ((stat-size ()
1191
(let ((process (run-program "/bin/bash" `("/opt/rails/script/repository_size" ,repository-id) :wait t :output :stream)))
1192
(assert process () "no process for size: ~s" repository-id)
1193
(unwind-protect (parse-integer (read-line (run-program-output process)) :junk-allowed t)
1194
(close (run-program-output process))
1195
(run-program-close process))))
1197
(let ((process (run-program "/bin/bash" `("/opt/rails/script/repository_compressed_size" ,repository-id) :wait t :output :stream)))
1198
(assert process () "no process for size: ~s" repository-id)
1199
(unwind-protect (parse-integer (read-line (run-program-output process)) :junk-allowed t)
1200
(close (run-program-output process))
1201
(run-program-close process))))
1202
(size-index-database ()
1203
(labels ((term-size (term-number)
1204
(if (= 4294967295 term-number)
1205
(if (gethash (iri-lexical-form |urn:dydra|:|default|) strings)
1207
(setf (gethash (iri-lexical-form |urn:dydra|:|default|) strings)
1208
(length (iri-lexical-form |urn:dydra|:|default|))))
1209
(destructuring-bind (&key type value-string language-string datatype-string value)
1210
(rlmdb:term-elements term-number)
1211
(declare (ignore type value))
1212
(+ (if value-string (length value-string) 0)
1214
(if (gethash language-string strings) 0
1215
(setf (gethash language-string strings) (length language-string)))
1218
(if (gethash datatype-string strings) 0
1219
(setf (gethash datatype-string strings) (length datatype-string)))
1222
(collect-term (term-number)
1223
(unless (gethash term-number terms)
1224
(setf (gethash term-number terms) (incf term-count))))
1225
(collect-index-entry (%quad sha-1)
1226
(setf sha-1 (copy-seq sha-1))
1227
(collect-term (%quad-context %quad))
1228
(collect-term (%quad-subject %quad))
1229
(collect-term (%quad-predicate %quad))
1230
(collect-term (%quad-object %quad))
1231
(incf statement-count)
1233
(incf index-leaf-bytes (load-time-value (+ (* 4 (CFFI:FOREIGN-TYPE-SIZE :short))
1234
(cffi:foreign-type-size '(:struct quad))))) ;; leaf node
1235
(incf index-leaf-bytes (length sha-1))
1236
(incf (gethash sha-1 sequence-id-cache 0))))
1237
(declare (dynamic-extent #'collect-index-entry))
1238
(map-repository-index #'collect-index-entry index-database)
1239
(loop for term-number being each hash-key of terms
1240
do (incf term-bytes (term-size term-number)))
1241
(setf index-node-bytes (* (load-time-value (+ (* 4 (CFFI:FOREIGN-TYPE-SIZE :short))
1242
(cffi:foreign-type-size '(:struct quad))))
1243
(loop for inner-count = (floor index-count 2) then (floor inner-count 2) until (= inner-count 0) sum inner-count)))
1244
;; multiply by number of indeces
1245
(setf index-node-bytes (* index-node-bytes (length +quad-database-names+)))
1246
(setf index-leaf-bytes (* index-leaf-bytes (length +quad-database-names+)))
1247
(setf index-bytes (+ index-node-bytes index-leaf-bytes))))
1249
(size-sequence-database ()
1250
(lmdb:ensure-open-database sequence-database)
1251
(let* ((cur (lmdb:make-cursor sequence-database)))
1252
(cffi:with-foreign-pointer (%sequence-id spocq.i::+rsid-length+)
1253
(%encode-revision-sequence-id %sequence-id (make-array spocq.i::+rsid-length+ :element-type '(unsigned-byte 8) :initial-element 0))
1254
(with-lmdb-value (%key spocq.i::+rsid-length+ %sequence-id)
1255
(lmdb::with-empty-value (%data)
1256
(lmdb:with-cursor (cur)
1257
(labels ((do-rsid (get-op)
1258
(let ((return-code (liblmdb:cursor-get (lmdb::handle cur) %key %data get-op)))
1259
(alexandria:switch (return-code)
1261
(when (= (%mdb-val-size %key) spocq.i::+rsid-length+)
1262
(let* ((sequence-id (%decode-revision-sequence-id (%mdb-val-data %key)))
1263
(sequence (%decode-revision-sequence (%mdb-val-data %data) (%mdb-val-size %data)))
1264
(bytes (+ (length sequence-id) (* (length sequence) 16))))
1265
(incf revision-sequence-total-count)
1266
(incf revision-sequence-total-bytes bytes)
1267
(cond ((gethash sequence-id sequence-id-cache)
1268
(incf revision-sequence-referenced-count)
1269
(incf revision-sequence-referenced-bytes bytes))
1271
(liblmdb:del (lmdb::handle transaction)
1272
(lmdb::handle sequence-database)
1279
(lmdb::unknown-error return-code))))))
1280
(loop for op = :+set-range+ then :+next+
1281
while (do-rsid op))))))))))
1282
(size-index-database)
1283
(size-sequence-database)
1284
(when verbose (print (list :sequence-id-cache sequence-id-cache)))
1285
(let ((stat-bytes (stat-size))
1286
(stat-compressed-bytes (compressed-size)))
1288
:STATEMENT-COUNT statement-count
1289
:STATEMENT-COUNT-VERSIONED statement-count-versioned
1290
:TERMS (hash-table-count terms)
1291
:INDEX-BYTES index-bytes
1292
:INDEX-LEAF-BYTES index-leaf-bytes
1293
:INDEX-NODE-BYTES index-node-bytes
1294
:TERM-BYTES term-bytes
1295
:STAT-BYTES stat-bytes
1296
:STAT-COMPRESSED-BYTES stat-compressed-bytes
1297
:revision-sequence-total-count revision-sequence-total-count
1298
:revision-sequence-total-bytes revision-sequence-total-bytes
1299
:revision-sequence-referenced-count revision-sequence-referenced-count
1300
:revision-sequence-referenced-bytes revision-sequence-referenced-bytes
1301
:TOTAL-BYTES (+ term-bytes stat-compressed-bytes)))))))
1304
(defmethod map-repository-index (operator (index rlmdb::replicable-quad-database))
1305
"iterate over the entire index."
1306
(lmdb:with-database (index)
1307
(let* ((cur (lmdb:make-cursor index :transaction lmdb:*transaction*))
1308
(test-rsid (make-array spocq.i::+rsid-length+ :element-type 'unsigned-byte))
1309
(quad-pattern #(0 0 0 0)))
1310
;;(let ((%key-quad (cffi:foreign-alloc '(:struct rdfcache::quad))))
1311
(cffi:with-foreign-objects ((%quad-pattern '(:struct rdfcache::quad))
1312
(%key-quad '(:struct rdfcache::quad)))
1313
(lmdb::with-empty-value (%key)
1314
(lmdb::with-empty-value (%value)
1315
(quad-to-quad-record quad-pattern %quad-pattern)
1316
(%copy-quad %quad-pattern %key-quad)
1317
(lmdb:with-cursor (cur)
1318
(let ((%cursor (lmdb::handle cur)))
1319
(labels ((get-quad (get-op)
1322
(setf (%mdb-val-size %key) 16
1323
(%mdb-val-data %key) %key-quad))
1325
(let ((return-code (liblmdb:cursor-get %cursor %key %value get-op)))
1326
(alexandria:switch (return-code)
1328
(call-with-quad-entry %key %value))
1332
(lmdb::unknown-error return-code)))))
1333
(call-with-quad-entry (%k %v)
1334
(assert (= 16 (cffi:pointer-address (cffi:foreign-slot-value %k '(:struct liblmdb:val) 'liblmdb:mv-size)))
1336
"key size is invalid: ~s" (cffi:pointer-address (cffi:foreign-slot-value %k '(:struct liblmdb:val) 'liblmdb:mv-size)))
1337
(let* ((%index-quad (%mdb-val-data %k))
1338
(%index-sha1 (%mdb-val-data %v)))
1339
(funcall operator %index-quad (%decode-revision-sequence-id %index-sha1 test-rsid)))))
1340
(loop for op = :+set-range+ then :+next+
1344
finally (return count)))))))))))
1350
("bear/hour-replicated-patch" :STATEMENT-COUNT 0 :STATEMENT-COUNT-VERSIONED 0
1351
:TERMS 299 :INDEX-BYTES 104304 :INDEX-LEAF-BYTES 67584 :INDEX-NODE-BYTES 36720
1352
:TERM-BYTES 11744 :STAT-BYTES 203403264 :STAT-COMPRESSED-BYTES 139362304
1353
:REVISION-SEQUENCE-TOTAL-COUNT 24901 :REVISION-SEQUENCE-TOTAL-BYTES 7912913
1354
:REVISION-SEQUENCE-REFERENCED-COUNT 4 :REVISION-SEQUENCE-REFERENCED-BYTES 89
1355
:TOTAL-BYTES 139374048)
1356
* (in-package :rlmdb.i)
1358
(compute-repository-size "bear/hour-replicated-patch")
1361
;; just the first two patches
1362
("bear/hour-replicated-patch" :STATEMENT-COUNT 256 :STATEMENT-COUNT-VERSIONED 0
1363
:TERMS 299 :INDEX-BYTES 104304 :INDEX-LEAF-BYTES 67584 :INDEX-NODE-BYTES 36720
1364
:TERM-BYTES 11744 :STAT-BYTES 203403264 :STAT-COMPRESSED-BYTES 139362304
1365
:REVISION-SEQUENCE-TOTAL-COUNT 24901 :REVISION-SEQUENCE-TOTAL-BYTES 119136308
1366
:REVISION-SEQUENCE-REFERENCED-COUNT 4 :REVISION-SEQUENCE-REFERENCED-BYTES 224
1367
:TOTAL-BYTES 139374048)
1370
("bear/hour-replicated-patch" :STATEMENT-COUNT 134978
1371
:STATEMENT-COUNT-VERSIONED 0 :TERMS 123009 :INDEX-BYTES 55070016
1372
:INDEX-LEAF-BYTES 35634192 :INDEX-NODE-BYTES 19435824 :TERM-BYTES 5030976
1373
:STAT-BYTES 341880832 :STAT-COMPRESSED-BYTES 338481152
1374
:REVISION-SEQUENCE-TOTAL-COUNT 49762 :REVISION-SEQUENCE-TOTAL-BYTES 238270408
1375
:REVISION-SEQUENCE-REFERENCED-COUNT 10552 :REVISION-SEQUENCE-REFERENCED-BYTES
1376
783920 :TOTAL-BYTES 343512128)