| 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*) |
|---|