root/trunk/bknr/datastore/src/data/object.lisp

Revision 4109, 36.9 kB (checked in by hans, 20 hours ago)

add print-store-object macro, fix dependencies

  • Property svn:eol-style set to native
  • Property svn:keywords set to author date id revision
Line 
1 ;;; MOP based object subsystem for the BKNR datastore
2
3 ;; Internal slots should have a different slot descriptor class, (setf
4 ;; slot-value-using-class) should only be defined for
5 ;; application-defined slots, not internal slots (like ID, maybe
6 ;; others).
7
8 ;; get-internal-real-time, get-internal-run-time, get-universal-time
9 ;; need to be shadowed and disallowed.
10
11 (in-package :bknr.datastore)
12
13 (define-condition inconsistent-slot-persistence-definition (store-error)
14   ((class :initarg :class)
15    (slot-name :initarg :slot-name))
16   (:report (lambda (e stream)
17              (with-slots (slot-name class) e
18                (format stream "Slot ~A in class ~A declared as both transient and persistent"
19                        slot-name class)))))
20
21 (define-condition object-subsystem-not-found-in-store (store-error)
22   ((store :initarg :store))
23   (:report (lambda (e stream)
24              (with-slots (store) e
25                (format stream "Could not find a store-object-subsystem in the current store ~A" store)))))
26
27 (define-condition persistent-slot-modified-outside-of-transaction (store-error)
28   ((slot-name :initarg :slot-name)
29    (object :initarg :object))
30   (:report (lambda (e stream)
31              (with-slots (slot-name object) e
32                (format stream "Attempt to modify persistent slot ~A of ~A outside of a transaction"
33                        slot-name object)))))
34
35 (defclass store-object-subsystem ()
36   ((next-object-id :initform 0
37                    :accessor next-object-id
38                    :documentation "Next object ID to assign to a new object")))
39
40 (defun store-object-subsystem ()
41   (let ((subsystem (find-if (alexandria:rcurry #'typep 'store-object-subsystem)
42                             (store-subsystems *store*))))
43     (unless subsystem
44       (error 'object-subsystem-not-found-in-store :store *store*))
45     subsystem))
46
47 (eval-when (:compile-toplevel :load-toplevel :execute)
48   (finalize-inheritance
49    (defclass persistent-class (indexed-class)
50      ())))
51
52 (defmethod validate-superclass ((sub persistent-class) (super indexed-class))
53   t)
54
55 (defvar *suppress-schema-warnings* nil)
56
57 (deftransaction update-instances-for-changed-class (class)
58   (let ((instance-count (length (class-instances class))))
59     (when (plusp instance-count)
60       (unless *suppress-schema-warnings*
61         (report-progress "~&; updating ~A instances of ~A for class changes~%"
62                          instance-count class))
63       (mapc #'reinitialize-instance (class-instances class)))))
64
65 (defmethod reinitialize-instance :after ((class persistent-class) &key)
66   (when (and (boundp '*store*) *store*)
67     (update-instances-for-changed-class (class-name class))
68     (unless *suppress-schema-warnings*
69       (report-progress "~&; class ~A has been changed. To ensure correct schema ~
70                               evolution, please snapshot your datastore.~%"
71                        (class-name class)))))
72
73 (defclass persistent-direct-slot-definition (index-direct-slot-definition)
74   ((relaxed-object-reference :initarg :relaxed-object-reference
75                              :initform nil)
76    (transient :initarg :transient
77               :initform nil)))
78
79 (defclass persistent-effective-slot-definition (index-effective-slot-definition)
80   ((relaxed-object-reference :initarg :relaxed-object-reference
81                              :initform nil)
82    (transient :initarg :transient
83               :initform nil)))
84
85
86 (defgeneric transient-slot-p (slotd)
87   (:method ((slotd t))
88     t)
89   (:method ((slotd persistent-direct-slot-definition))
90     (slot-value slotd 'transient))
91   (:method ((slotd persistent-effective-slot-definition))
92     (slot-value slotd 'transient)))
93
94 (defgeneric relaxed-object-reference-slot-p (slotd)
95   (:method ((slotd t))
96     nil)
97   (:method ((slotd persistent-effective-slot-definition))
98     (slot-value slotd 'relaxed-object-reference))
99   (:documentation "Return whether the given slot definition specifies
100 that the slot is relaxed.  If a relaxed slot holds a pointer to
101 another persistent object and the pointed-to object is deleted, slot
102 reads will return nil."))
103
104 (defun undo-set-slot (object slot-name value)
105   (if (eq value 'unbound)
106       (slot-makunbound object slot-name)
107       (setf (slot-value object slot-name) value)))
108
109 (defmethod (setf slot-value-using-class) :before ((newval t)
110                                                   (class persistent-class)
111                                                   object
112                                                   (slotd persistent-effective-slot-definition))
113   (unless (transient-slot-p slotd)
114     (let ((slot-name (slot-definition-name slotd)))
115       (unless (or (in-transaction-p)
116                   (member slot-name '(last-change id)))
117         (error 'persistent-slot-modified-outside-of-transaction :slot-name slot-name :object object))
118       (when (in-anonymous-transaction-p)
119         (push (list #'undo-set-slot
120                     object
121                     (slot-definition-name slotd)
122                     (if (slot-boundp object (slot-definition-name slotd))
123                         (slot-value object (slot-definition-name slotd))
124                         'unbound))
125               (anonymous-transaction-undo-log *current-transaction*)))
126       (when (and (not (eq :restore (store-state *store*)))
127                  (not (member slot-name '(last-change id))))
128         (setf (slot-value object 'last-change) (current-transaction-timestamp))))))
129
130 (defmethod (setf slot-value-using-class) :after (newval
131                                                  (class persistent-class)
132                                                  object
133                                                  (slotd persistent-effective-slot-definition))
134   (when (and (not (transient-slot-p slotd))
135              (in-anonymous-transaction-p)
136              (not (member (slot-definition-name slotd) '(last-change id))))
137     (encode (make-instance 'transaction
138                            :timestamp (transaction-timestamp *current-transaction*)
139                            :function-symbol 'tx-change-slot-values
140                            :args (list object (slot-definition-name slotd) newval))
141             (anonymous-transaction-log-buffer *current-transaction*))))
142
143 (define-condition transient-slot-cannot-have-initarg (store-error)
144   ((class :initarg :class)
145    (slot-name :initarg :slot-name))
146   (:documentation "A transient slot may not have an :initarg
147   specified, as initialize-instance is only used for persistent
148   initialization.")
149   (:report (lambda (e stream)
150              (with-slots (class slot-name) e
151                (format stream "The transient slot ~A in class ~A was defined as having an initarg, which is not supported"
152                        slot-name (class-name class))))))
153
154 (defmethod direct-slot-definition-class ((class persistent-class) &key initargs transient name)
155   ;; It might be better to do the error checking in an
156   ;; initialize-instance method of persistent-direct-slot-definition
157   (when (and initargs transient)
158     (error 'transient-slot-cannot-have-initarg :class class :slot-name name))
159   'persistent-direct-slot-definition)
160
161 (defmethod effective-slot-definition-class ((class persistent-class) &key)
162   'persistent-effective-slot-definition)
163
164 (defmethod compute-effective-slot-definition :around ((class persistent-class) name direct-slots)
165   (unless (or (every #'transient-slot-p direct-slots)
166               (notany #'transient-slot-p direct-slots))
167     (error 'inconsistent-slot-persistence-definition :class class :slot-name name))
168   (let ((effective-slot-definition (call-next-method)))
169     (when (typep effective-slot-definition 'persistent-effective-slot-definition)
170       (with-slots (relaxed-object-reference transient) effective-slot-definition
171         (setf relaxed-object-reference (some #'relaxed-object-reference-slot-p direct-slots)
172               transient (slot-value (first direct-slots) 'transient))))
173     effective-slot-definition))
174
175 (defmethod class-persistent-slots ((class standard-class))
176   (remove-if #'transient-slot-p (class-slots class)))
177
178 (defclass store-object ()
179   ((id :initarg :id
180        :reader store-object-id
181        :type integer
182        :index-type unique-index
183        :index-initargs (:test #'eql)
184        :index-reader store-object-with-id :index-values all-store-objects
185        :index-mapvalues map-store-objects)
186    (last-change :initform (get-universal-time)
187                 :initarg :last-change))
188   (:metaclass persistent-class)
189   (:class-indices (all-class :index-type class-skip-index
190                              :index-subclasses t
191                              :index-initargs (:index-superclasses t)
192                              :index-keys all-store-classes
193                              :index-reader store-objects-with-class
194                              :slots (id))))
195
196 (defun class-instances (class)
197   (find-class class)                 ; make sure that the class exists
198   (store-objects-with-class class))
199
200 (deftransaction store-object-touch (object)
201   "Update the LAST-CHANGE slot to reflect the current transaction timestamp."
202   (setf (slot-value object 'last-change) (current-transaction-timestamp)))
203
204 (defgeneric store-object-last-change (object depth)
205   (:documentation "Return the last change time of the OBJECT.  DEPTH
206   determines how deep the object graph will be traversed.")
207
208   (:method ((object t) (depth integer))
209     0)
210
211   (:method ((object store-object) (depth (eql 0)))
212     (slot-value object 'last-change))
213
214   (:method ((object store-object) depth)
215     (let ((last-change (slot-value object 'last-change)))
216       (dolist (slotd (class-slots (class-of object)))
217         (let* ((slot-name (slot-definition-name slotd))
218                (child (and (slot-boundp object slot-name)
219                            (slot-value object slot-name))))
220           (setf last-change
221                 (cond
222                   ((null child)
223                    last-change)
224                   ((typep child 'store-object)
225                    (max last-change (store-object-last-change child (1- depth))))
226                   ((listp child)
227                    (reduce #'max child
228                            :key (alexandria:rcurry 'store-object-last-change (1- depth))
229                            :initial-value last-change))
230                   (t
231                    last-change)))))
232       last-change)))
233
234 #+allegro
235 (aclmop::finalize-inheritance (find-class 'store-object))
236
237 (defmethod initialize-instance :around ((object store-object) &rest initargs &key)
238   (setf (slot-value object 'id) (allocate-next-object-id))
239   (cond
240     ((not (in-transaction-p))
241      (with-store-guard ()
242        (let ((transaction (make-instance 'transaction
243                                          :function-symbol 'make-instance
244                                          :timestamp (get-universal-time)
245                                          :args (cons (class-name (class-of object))
246                                                      (append (list :id (slot-value object 'id))
247                                                              initargs)))))
248          (with-statistics-log (*transaction-statistics* (transaction-function-symbol transaction))
249            (with-transaction-log (transaction)
250              (call-next-method))))))
251     ((in-anonymous-transaction-p)
252      (encode (make-instance 'transaction
253                             :function-symbol 'make-instance
254                             :timestamp (transaction-timestamp *current-transaction*)
255                             :args (cons (class-name (class-of object)) initargs))
256              (anonymous-transaction-log-buffer *current-transaction*))
257      (call-next-method))
258     (t
259      (call-next-method))))
260
261 (defvar *allocate-object-id-lock* (bt:make-lock "Persistent Object ID Creation"))
262
263 (defun allocate-next-object-id ()
264   (mp-with-lock-held (*allocate-object-id-lock*)
265     (let ((id (next-object-id (store-object-subsystem))))
266       (incf (next-object-id (store-object-subsystem)))
267       id)))
268    
269 (defun initialize-transient-slots (object)
270   (dolist (slotd (class-slots (class-of object)))
271     (when (and (typep slotd 'persistent-effective-slot-definition)
272                (transient-slot-p slotd)
273                (slot-definition-initfunction slotd))
274       (setf (slot-value object (slot-definition-name slotd))
275             (funcall (slot-definition-initfunction slotd))))))
276  
277 (defmethod initialize-instance :after ((object store-object) &key)
278   ;; This is called only when initially creating the (persistent)
279   ;; instance, not during restore.  During restore, the
280   ;; INITIALIZE-TRANSIENT-INSTANCE function is called for all
281   ;; persistent objects after the snapshot has been read, but before
282   ;; running the transaction log.
283   (initialize-transient-instance object))
284
285 (defmacro print-store-object ((object stream &key type) &body body)
286   ;; variable capture accepted here.
287   `(print-unreadable-object (,object ,stream :type ,type)
288      (format stream "ID: ~D " (store-object-id ,object))
289      ,@body))
290
291 (defmethod print-object ((object store-object) stream)
292   (print-unreadable-object (object stream :type t)
293     (format stream "ID: ~D" (store-object-id object))))
294
295 (defmethod print-object :around ((object store-object) stream)
296   (if (object-destroyed-p object)
297       (print-unreadable-object (object stream :type t)
298         (princ "DESTROYED" stream))
299       (call-next-method)))
300
301 (defmethod change-class :before ((object store-object) class &rest args)
302   (declare (ignore class args))
303   (when (not (in-transaction-p))
304     (error "Can't change class of persistent object ~A using change-class ~
305             outside of transaction, please use PERSISTENT-CHANGE-CLASS instead" object)))
306
307 (defun tx-persistent-change-class (object class-name &rest args)
308   (warn "TX-PERSISTENT-CHANGE-CLASS does not maintain class indices, ~
309          please snapshot and restore to recover indices")
310   (apply #'change-class object (find-class class-name) args))
311
312 (defun persistent-change-class (object class &rest args)
313   (execute (make-instance 'transaction :function-symbol 'tx-persistent-change-class
314                           :timestamp (get-universal-time)
315                           :args (append (list object (if (symbolp class) class (class-name class))) args))))
316
317 (defgeneric initialize-transient-instance (store-object)
318   (:documentation
319    "Initializes the transient aspects of a persistent object. This
320 method is called after a persistent object has been initialized, also
321 when the object is loaded from a snapshot, but before reading the
322 transaction log."))
323
324 (defmethod initialize-transient-instance ((object store-object)))
325
326 (defmethod store-object-persistent-slots ((object store-object))
327   (mapcar #'slot-definition-name (class-persistent-slots (class-of object))))
328
329 (defmethod store-object-relaxed-object-reference-p ((object store-object) slot-name)
330   (let ((slot (find slot-name (class-slots (class-of object)) :key #'slot-definition-name)))
331     (when slot
332       (relaxed-object-reference-slot-p slot))))
333
334 (defmacro define-persistent-class (class (&rest superclasses) slots &rest class-options)
335   (let ((superclasses (or superclasses '(store-object)))
336         (metaclass (cadr (assoc :metaclass class-options))))
337     (when (and metaclass
338                (not (validate-superclass (find-class metaclass)
339                                          (find-class 'persistent-class))))
340       (error "Can not define a persistent class with metaclass ~A." metaclass))
341     `(define-bknr-class ,class ,superclasses ,slots
342                         ,@(unless metaclass '((:metaclass persistent-class)))
343                         ,@class-options)))
344
345 (defmacro defpersistent-class (class (&rest superclasses) slots &rest class-options)
346   (let ((superclasses (or superclasses '(store-object)))
347         (metaclass (cadr (assoc :metaclass class-options))))
348     (when (and metaclass
349                (not (validate-superclass (find-class metaclass)
350                                          (find-class 'persistent-class))))
351       (error "Can not define a persistent class with metaclass ~A." metaclass))
352     `(eval-when (:compile-toplevel :load-toplevel :execute)
353        (defclass ,class ,superclasses ,slots
354          ,@(unless metaclass '((:metaclass persistent-class)))
355          ,@class-options))))
356
357 ;;; binary snapshot
358
359 (defvar *current-object-slot* nil)
360 (defvar *current-slot-relaxed-p* nil)
361
362 (defun encode-layout (id class slots stream)
363   (%write-tag #\L stream)
364   (%encode-integer id stream)
365   (%encode-symbol (class-name class) stream)
366   (%encode-integer (length slots) stream)
367   (dolist (slot slots)
368     (%encode-symbol slot stream)))
369
370 (defun %encode-set-slots (slots object stream)
371   (dolist (slot slots)
372     (let ((*current-object-slot* (list object slot))
373           (*current-slot-relaxed-p* (store-object-relaxed-object-reference-p object slot)))
374       (encode (if (slot-boundp object slot)
375                   (slot-value object slot)
376                   'unbound)
377               stream))))
378
379 (defun encode-create-object (class-layouts object stream)
380   (let* ((class (class-of object))
381          (layout (gethash class class-layouts)))
382     (unless layout
383       (setf layout
384             (cons (hash-table-count class-layouts)
385                   ;; XXX layout muss konstant sein
386                   (sort (remove 'id (store-object-persistent-slots object))
387                         #'string< :key #'symbol-name)))
388       (encode-layout (car layout) class (cdr layout) stream)
389       (setf (gethash class class-layouts) layout))
390     (destructuring-bind (layout-id &rest slots) layout
391       (declare (ignore slots))
392       (%write-tag #\O stream)
393       (%encode-integer layout-id stream)
394       (%encode-integer (store-object-id object) stream))))
395
396 (defun encode-set-slots (class-layouts object stream)
397   (destructuring-bind (layout-id &rest slots)
398       (gethash (class-of object) class-layouts)
399     (%write-tag #\S stream)
400     (%encode-integer layout-id stream)
401     (%encode-integer (store-object-id object) stream)
402     (%encode-set-slots slots object stream)))
403
404 (defun find-class-with-interactive-renaming (class-name)
405   (loop until (or (null class-name)
406                   (find-class class-name nil))
407      do (progn
408           (format *query-io* "Class ~A not found, enter new class or enter ~
409                               NIL to ignore objects of this class: "
410                   class-name)
411           (finish-output *query-io*)
412           (setq class-name (read *query-io*))))
413   (and class-name
414        (find-class class-name)))
415
416 (defun find-slot-name-with-interactive-rename (class slot-name)
417   (loop until (find slot-name (class-slots class) :key #'slot-definition-name)
418      do (format *query-io* "Slot ~S not found in class ~S, enter new slot name: "
419                 slot-name (class-name class))
420      do (setq slot-name (read *query-io*))
421      finally (return slot-name)))
422
423 (defvar *slot-name-map*)
424
425 (defun rename-slot (class slot-name)
426   (or (caddr (find (list (class-name class) slot-name) *slot-name-map*
427                    :key #'(lambda (entry) (subseq entry 0 2)) :test #'equal))
428       (find (symbol-name slot-name)
429             (mapcar #'slot-definition-name (class-slots class)) :key #'symbol-name :test #'equal)))
430
431 (defgeneric convert-slot-value-while-restoring (object slot-name value)
432   (:documentation "Generic function to be called to convert a slot's
433   value from a previous snapshot layout.  OBJECT is the object that is
434   being restored, SLOT-NAME is the name of the slot in the old schema,
435   VALUE is the value of the slot in the old schema.")
436   (:method (object slot-name value)
437     (setf (slot-value object slot-name) value)))
438
439 (defun find-slot-name-with-automatic-rename (class slot-name)
440   (if (find slot-name (class-slots class) :key #'slot-definition-name)
441       slot-name
442       (restart-case
443           (let ((new-slot-name (rename-slot class slot-name)))
444             (cond
445               (new-slot-name
446                (warn "slot ~S not found in class ~S, automatically renamed to ~S"
447                      slot-name (class-name class) new-slot-name)
448                new-slot-name)
449               (t
450                (error "can't find a slot in class ~A which matches the name ~A used in the store snapshot"
451                       (class-name class) slot-name))))
452         (convert-values ()
453           :report "Convert slot values using CONVERT-SLOT-VALUE-WHILE-RESTORING"
454           (cons 'convert-slot-values slot-name))
455         (ignore-slot ()
456           :report "Ignore slot, discarding values found in the snapshot file"
457           nil))))
458
459 (defun find-class-slots-with-interactive-renaming (class slot-names)
460   #+(or)
461   (format t "; verifying class layout for class ~A~%; slots:~{ ~S~}~%" (class-name class)
462           (mapcar #'slot-definition-name (class-slots class)))
463   (loop for slot-name in slot-names
464      collect (find-slot-name-with-automatic-rename class slot-name)))
465
466 (defun snapshot-read-layout (stream layouts)
467   (let* ((id (%decode-integer stream))
468          (class-name (%decode-symbol stream :usage "class"))
469          (nslots (%decode-integer stream))
470          (class (find-class-with-interactive-renaming class-name))
471          (slot-names (loop repeat nslots collect (%decode-symbol stream
472                                                                  :intern (not (null class))
473                                                                  :usage "slot")))
474          (slots (if class
475                     (find-class-slots-with-interactive-renaming class slot-names)
476                     slot-names)))
477     (setf (gethash id layouts)
478           (cons class slots))))
479
480 (defun %read-slots (stream object slots)
481   "Read the OBJECT from STREAM.  The individual slots of the object
482 are expected in the order of the list SLOTS.  If the OBJECT is NIL,
483 the slots are read from the snapshot and ignored."
484   (declare (optimize (speed 3)))
485   (dolist (slot-name slots)
486     (let ((value (decode stream)))
487       (cond
488         ((consp slot-name)
489          (assert (eq 'convert-slot-values (car slot-name)))
490          (convert-slot-value-while-restoring object (cdr slot-name) value))
491         ((null slot-name)
492          ;; ignore value
493          )
494         (t
495          (restart-case
496              (let ((*current-object-slot* (list object slot-name))
497                    (*current-slot-relaxed-p* (or (null object)
498                                                  (store-object-relaxed-object-reference-p object slot-name))))
499                (when object
500                  (let ((bknr.indices::*indices-remove-p* nil))
501                    (if (eq value 'unbound)
502                        (slot-makunbound object slot-name)
503                        (convert-slot-value-while-restoring object slot-name value)))))
504            (set-slot-nil ()
505              :report "Set slot to NIL."
506              (setf (slot-value object slot-name) nil))
507            (make-slot-unbound ()
508              :report "Make slot unbound."
509              (slot-makunbound object slot-name))))))))
510
511 (defun snapshot-read-object (stream layouts)
512   (declare (optimize (speed 3)))
513   (with-simple-restart (skip-object "Skip the object.")
514     (let* ((layout-id (%decode-integer stream))
515            (object-id (%decode-integer stream))
516            (class (first (gethash layout-id layouts))))
517       ;; If the class is NIL, it was not found in the currently
518       ;; running Lisp image and objects of this class will be ignored.
519       (when class
520         (let ((object (allocate-instance class)))
521           (setf (slot-value object 'id) object-id
522                 (next-object-id (store-object-subsystem)) (max (1+ object-id)
523                                                                (next-object-id (store-object-subsystem))))
524           (dolist (index (class-slot-indices class 'id))
525             (index-add index object)))))))
526
527 (defun snapshot-read-slots (stream layouts)
528   (let* ((layout-id (%decode-integer stream))
529          (object-id (%decode-integer stream))
530          (object (store-object-with-id object-id)))
531     (restart-case
532         (%read-slots stream object (cdr (gethash layout-id layouts)))
533       (skip-object-initialization ()
534         :report "Skip object initialization.")
535       (delete-object ()
536         :report "Delete the object."
537         (delete-object object)))))
538
539 (defmethod encode-object ((object store-object) stream)
540   (if (object-destroyed-p object)
541       (let* ((*indexed-class-override* t)
542              (id (store-object-id object))
543              (container (first *current-object-slot*))
544              (slot (second *current-object-slot*)))
545
546         ;; if we are not encoding slot values, something has gone
547         ;; wrong with the indices
548         (unless (and container slot)
549           (warn "Encoding destroyed object with ID ~A." id)
550           (%write-tag #\o stream)
551           (%encode-integer id stream)
552           (return-from encode-object))
553
554         (if *current-slot-relaxed-p*
555             ;; the slot can contain references to deleted objects, just warn
556             (progn
557               (warn "Encoding reference to destroyed object with ID ~A from slot ~A of object ~A with ID ~A."
558                     id slot (type-of container) (store-object-id container))
559               (%write-tag #\o stream)
560               (%encode-integer id stream))
561             ;; the slot can't contain references to deleted objects, throw an error
562             (error "Encoding reference to destroyed object with ID ~A from slot ~A of object ~A with ID ~A."
563                    id slot (type-of container) (store-object-id container))))
564
565       ;; Go ahead and serialize the object reference
566       (progn (%write-tag #\o stream)
567              (%encode-integer (store-object-id object) stream))))
568
569 (defmethod decode-object ((tag (eql #\o)) stream)
570   (let ((*current-object-slot* nil))
571     (%decode-store-object stream)))
572
573 (define-condition invalid-reference (warning)
574   ((id :initarg :id))
575   (:report (lambda (e stream)
576              (format stream "internal inconsistency during restore - store object with ID ~A could not be found"
577                      (slot-value e 'id)))))
578
579 (defun %decode-store-object (stream)
580   ;; This is actually called in two contexts, when a slot-value is to
581   ;; be filled with a reference to a store object and when a list of
582   ;; store objects is read from the transaction log (%decode-list).
583   ;; In the former case, references two deleted objects are accepted
584   ;; when the slot pointing to the object is marked as being a
585   ;; "relaxed-object-reference", in the latter case, no such
586   ;; information is available.  To ensure maximum restorability of
587   ;; transaction logs, object references stored in lists are always
588   ;; considered to be relaxed references, which means that references
589   ;; to deleted objects are restored as NIL.  Applications must be
590   ;; prepared to cope with NIL entries in such object lists (usually
591   ;; lists in slots).
592   (let* ((id (%decode-integer stream))
593          (object (or (store-object-with-id id)
594                      (warn 'invalid-reference :id id)))
595          (container (first *current-object-slot*))
596          (slot-name (second *current-object-slot*)))
597     (cond (object object)
598
599           ((or *current-slot-relaxed-p* (not container))
600            (if container
601                (warn "Reference to inexistent object with id ~A in relaxed slot ~A of object ~
602                       with class ~A with ID ~A."
603                      id slot-name (type-of container) (store-object-id container))
604                (warn "Reference to inexistent object with id ~A from unnamed container, returning NIL." id))
605
606            ;; Possibly determine new "current object id"
607            (when (>= id (next-object-id (store-object-subsystem)))
608              (setf (next-object-id (store-object-subsystem)) (1+ id)))
609            nil)
610
611           (t (error "Reference to inexistent object with id ~A from slot ~A of object ~A with ID ~A."
612                     id slot-name (type-of container)
613                     (if container (store-object-id container) "unknown object"))))))
614
615 (defun encode-current-object-id (stream)
616   (%write-tag #\I stream)
617   (%encode-integer (next-object-id (store-object-subsystem)) stream))
618
619 (defmethod snapshot-subsystem ((store store) (subsystem store-object-subsystem))
620   (let ((snapshot (store-subsystem-snapshot-pathname store subsystem)))
621     (with-open-file (s snapshot
622                        :direction :output
623                        :element-type '(unsigned-byte 8)
624                        :if-does-not-exist :create
625                        :if-exists :supersede)
626       (let ((class-layouts (make-hash-table)))
627         (with-transaction (:prepare-for-snapshot)
628           (map-store-objects #'prepare-for-snapshot))
629         (encode-current-object-id s)
630         (map-store-objects (lambda (object) (when (subtypep (type-of object) 'store-object)
631                                               (encode-create-object class-layouts object s))))
632         (map-store-objects (lambda (object) (when (subtypep (type-of object) 'store-object)
633                                               (encode-set-slots class-layouts object s))))
634         t))))
635
636 (defmethod close-subsystem ((store store) (subsystem store-object-subsystem))
637   (dolist (class-name (all-store-classes))
638     (clear-class-indices (find-class class-name))))
639
640 (defmethod restore-subsystem ((store store) (subsystem store-object-subsystem) &key until)
641   ;; XXX check that until > snapshot time
642   (declare (ignore until))
643   (let ((snapshot (store-subsystem-snapshot-pathname store subsystem)))
644     ;; not all indices that should be cleared are cleared. maybe
645     ;; check on first instatiation of a class?
646     (dolist (class-name (cons 'store-object (all-store-classes)))
647       (clear-class-indices (find-class class-name)))
648     (setf (next-object-id subsystem) 0)
649     (when (probe-file snapshot)
650       (report-progress "~&; loading snapshot file ~A~%" snapshot)
651       (with-open-file (s snapshot
652                          :element-type '(unsigned-byte 8)
653                          :direction :input)
654         (let ((class-layouts (make-hash-table))
655               (created-objects 0)
656               (read-slots 0)
657               (error t)
658               (*slot-name-map* nil))
659           (unwind-protect
660                (progn
661                  (with-simple-restart
662                      (finalize-object-subsystem "Finalize the object subsystem.")
663                    (loop
664                       (when (and (plusp created-objects)
665                                  (zerop (mod created-objects 10000)))
666                         #+nil (format t "Snapshot position ~A~%" (file-position s))
667                         (report-progress "~A objects created.~%" created-objects)
668                         (force-output))
669                       (when (and (plusp read-slots)
670                                  (zerop (mod read-slots 10000)))
671                         (report-progress "~A of ~A objects initialized.~%" read-slots created-objects)
672                         (force-output))
673                       (let ((char (%read-tag s nil nil)))
674                         (unless (member char '(#\I #\L #\O #\S nil))
675                           (error "unknown char ~A at offset ~A~%" char (file-position s)))
676                         (ecase char
677                           ((nil) (return))
678                           (#\I (setf (next-object-id (store-object-subsystem)) (%decode-integer s)))
679                           (#\L (snapshot-read-layout s class-layouts))
680                           (#\O (snapshot-read-object s class-layouts) (incf created-objects))
681                           (#\S (snapshot-read-slots s class-layouts) (incf read-slots))))))
682                  (map-store-objects #'initialize-transient-slots)
683                  (map-store-objects #'initialize-transient-instance)
684                  (setf error nil))
685             (when error
686               (maphash #'(lambda (key val)
687                            (declare (ignore key))
688                            (let ((class-name (car val)))
689                              (report-progress "clearing indices for class ~A~%" (class-name class-name))
690                              (clear-class-indices class-name)))
691                        class-layouts))))))))
692
693 (defun tx-delete-object (id)
694   (destroy-object (store-object-with-id id)))
695
696 (defun delete-object (object)
697   (if (and (in-transaction-p)
698            (not (in-anonymous-transaction-p)))
699       (destroy-object object)
700       (execute (make-instance 'transaction :function-symbol 'tx-delete-object
701                               :timestamp (get-universal-time)
702                               :args (list (store-object-id object))))))
703
704 (defun tx-delete-objects (&rest object-ids)
705   (mapc #'(lambda (id) (destroy-object (store-object-with-id id))) object-ids))
706
707 (defun delete-objects (&rest objects)
708   (if (in-transaction-p)
709       (mapc #'destroy-object objects)
710       (execute (make-instance 'transaction :function-symbol 'tx-delete-objects
711                               :timestamp (get-universal-time)
712                               :args (mapcar #'store-object-id objects)))))
713
714 (defgeneric cascade-delete-p (object referencing-object)
715   (:method (object referencing-object)
716     (declare (ignore object referencing-object))
717     nil)
718   (:documentation "return non-nil if the REFERENCING-OBJECT should be deleted when the OBJECT is deleted"))
719
720 (defun partition-list (list predicate)
721   "Return two list values, the first containing all elements from LIST
722 that satisfy PREDICATE, the second those that don't"
723   (let (do dont)
724     (dolist (element list)
725       (if (funcall predicate element)
726           (push element do)
727           (push element dont)))
728     (values do dont)))
729
730 (defun cascading-delete-object (object)
731   "Delete the OBJECT and all objects that reference it and that are eligible to cascading deletes, as indicated by
732 the result of calling CASCADE-DELETE-P.  Generate error if there are references to the objects that are not eligible
733 to cascading deletes."
734   (multiple-value-bind (cascading-delete-refs
735                         remaining-refs)
736       (partition-list (find-refs object) (alexandria:curry #'cascade-delete-p object))
737     (when remaining-refs
738       (error "Cannot delete object ~A because there are references ~
739               to this object in the system, please consult a system administrator!"
740              object))
741     (apply #'delete-objects object cascading-delete-refs)))
742
743 (defun tx-change-slot-values (object &rest slots-and-values)
744   "Called by the MOP to change a persistent slot's value."
745   (unless (in-transaction-p)
746     (error 'not-in-transaction))
747   (when object
748     (loop for (slot value) on slots-and-values by #'cddr
749        do (setf (slot-value object slot) value))))
750
751 (defun change-slot-values (object &rest slots-and-values)
752   "This function is the deprecated way to set slots of persistent
753    objects."
754   (warn "CHANGE-SLOT-VALUES is deprecated - use WITH-TRANSACTION and standard accessors!")
755   (execute (make-instance 'transaction
756                           :function-symbol 'tx-change-slot-values
757                           :timestamp (get-universal-time)
758                           :args (list* object slots-and-values))))
759
760 (defgeneric prepare-for-snapshot (object)
761   (:method ((object store-object))
762     nil)
763   (:documentation "Called for every store object before a snapshot is
764   written."))
765
766 (defun find-store-object (id-or-name &key (class 'store-object) query-function key-slot-name)
767   "Mock up implementation of find-store-object API as in the old datastore.
768 Note: QUERY-FUNCTION will only be used if ID-OR-NAME is neither an integer nor a
769 string designating an integer."
770   (unless id-or-name
771     (error "can't search a store object with null key"))
772   (when (stringp id-or-name)
773     (multiple-value-bind (value end) (parse-integer id-or-name :junk-allowed t)
774       (when (and value
775                  (eql end (length id-or-name)))
776         (setq id-or-name value))))
777   (let ((result (cond
778                   ((numberp id-or-name)
779                    (store-object-with-id id-or-name))
780                   (t
781                    (cond
782                      (query-function
783                       (funcall query-function id-or-name))
784                      ((eq class 't)
785                       (error "can't search for store object by name without class specified"))
786                      (t
787                       (let ((index (bknr.indices::class-slot-index (find-class class) key-slot-name)))
788                         (when index
789                           (index-get index id-or-name)))))))))
790     (unless (or (null result)
791                 (typep result class))
792       (error "Object ~A is not of wanted type ~A." result class))
793     result))
794
795 (deftransaction store-object-add-keywords (object slot keywords)
796   (setf (slot-value object slot)
797         (union (slot-value object slot)
798                keywords)))
799
800 (deftransaction store-object-remove-keywords (object slot keywords)
801   (setf (slot-value object slot)
802         (set-difference (slot-value object slot) keywords)))
803
804 (deftransaction store-object-set-keywords (object slot keywords)
805   (setf (slot-value object slot) keywords))
806
807 (defmethod find-refs ((object store-object))
808   "Find references to the given OBJECT in all store-objects, traversing both single valued and list valued slots."
809   (remove-if-not
810    (lambda (candidate)
811      (find-if (lambda (slotd)
812                 (and (slot-boundp candidate (slot-definition-name slotd))
813                      (let ((slot-value (slot-value candidate (slot-definition-name slotd))))
814                        (or (eq object slot-value)
815                            (and (alexandria:proper-list-p slot-value)
816                                 (find object slot-value))))))
817               (class-slots (class-of candidate))))
818    (class-instances 'store-object)))
819
820 (pushnew :mop-store cl:*features*)
Note: See TracBrowser for help on using the browser.