PageRenderTime 47ms CodeModel.GetById 13ms RepoModel.GetById 0ms app.codeStats 0ms

/src/data/object.lisp

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