Update of /project/elephant/cvsroot/elephant/src/elephant In directory clnet:/tmp/cvs-serv26571/src/elephant
Modified Files: collections.lisp migrate.lisp Log Message: Added functionality and test for migrating persistent references inside lisp aggregates: array, list and hash tables
--- /project/elephant/cvsroot/elephant/src/elephant/collections.lisp 2007/03/06 04:15:27 1.12 +++ /project/elephant/cvsroot/elephant/src/elephant/collections.lisp 2007/03/11 03:31:09 1.13 @@ -423,40 +423,40 @@ (dump-btree bt :print-fn print-fn :count count))
(defmethod btree-differ-p ((x btree) (y btree)) - (assert (eq (get-con x) (get-con y))) +;; (assert (eq (get-con x) (get-con y))) (ensure-transaction (:store-controller (get-con x)) - (let ((cx1 (make-cursor x)) - (cy1 (make-cursor y)) - (done nil) - (rv nil) - (mx nil) - (kx nil) - (vx nil) - (my nil) - (ky nil) - (vy nil)) - (cursor-first cx1) - (cursor-first cy1) - (do ((i 0 (1+ i))) - (done nil) - (multiple-value-bind (m k v) (cursor-current cx1) - (setf mx m) - (setf kx k) - (setf vx v)) - (multiple-value-bind (m k v) (cursor-current cy1) - (setf my m) - (setf ky k) - (setf vy v)) - (if (not (and (equal mx my) - (equal kx ky) - (equal vx vy))) - (setf rv (list mx my kx ky vx vy))) - (setf done (and (not mx) (not mx)) - ) - (cursor-next cx1) - (cursor-next cy1) - ) - (cursor-close cx1) - (cursor-close cy1) - rv - ))) + (ensure-transaction (:store-controller (get-con y)) + (let ((cx1 (make-cursor x)) + (cy1 (make-cursor y)) + (done nil) + (rv nil) + (mx nil) + (kx nil) + (vx nil) + (my nil) + (ky nil) + (vy nil)) + (cursor-first cx1) + (cursor-first cy1) + (do ((i 0 (1+ i))) + (done nil) + (multiple-value-bind (m k v) (cursor-current cx1) + (setf mx m) + (setf kx k) + (setf vx v)) + (multiple-value-bind (m k v) (cursor-current cy1) + (setf my m) + (setf ky k) + (setf vy v)) + (if (not (and (equal mx my) + (equal kx ky) + (equal vx vy))) + (setf rv (list mx my kx ky vx vy))) + (setf done (and (not mx) (not mx))) + (cursor-next cx1) + (cursor-next cy1) + ) + (cursor-close cx1) + (cursor-close cy1) + rv + )))) --- /project/elephant/cvsroot/elephant/src/elephant/migrate.lisp 2007/03/09 00:44:35 1.10 +++ /project/elephant/cvsroot/elephant/src/elephant/migrate.lisp 2007/03/11 03:31:09 1.11 @@ -26,8 +26,6 @@
;; LIMITATIONS: ;; - Migrate currently will not handle circular list objects -;; - Migrate does not support arrays or standard objects with nested persistent objects -;; - There are potential problems with graphs and other deep structures ;; ;; - Indexed classes only have their class index copied if you use the ;; top level migration. Objects will be copied without slot data if you @@ -41,19 +39,18 @@ ;; a validation test) the source store should be closed. Any failures ;; in migration should then be easy to catch. ;; -;; - Each call to migration will be good about keeping track of already -;; copied objects to avoid duplication. Duplication _shouldn't_ screw -;; up the semantics, just add storage overhead but is to be avoided. -;; However this information is not saved between calls and there's no -;; other way to do comparisons between objects across stores (different -;; oid namespaces) so user beware of the pitfalls of partial migrations... -;; ;; - Migrate keeps a memory-resident hash of all persistent objects; ;; this is not as bad as it sounds as an object is only an oid reference ;; and a pointer to the store controller it belongs to. However, you ;; may eventually run out of heap space for very large DB's. We can use ;; the old DB to store the mappings if this becomes a problem. ;; +;; - Each top-level call to migration will be good about keeping track +;; of already copied persistent objects. However the hash is not +;; saved between calls and there's no other way to do comparisons +;; between objects across stores (different oid namespaces) so user +;; beware of the pitfalls of partial migrations... +;; ;; - Migration does not maintain OID equivalence so any datastructures which ;; index into those will have to have a way to reconstruct themselves (better ;; to keep the object references themselves rather than oids in general) @@ -69,7 +66,7 @@ ;; to get a destination repository object with all the slots copied over ;; to the target repository which you can then overwrite. To avoid the ;; default persistent slot copying, bind the dynamic variable -;; *inhibit-slot-writes* in your user method using +;; *inhibit-slot-copy* in your user method using ;; (with-inhibited-slot-copy () ...), a convenience macro. ;;
@@ -121,7 +118,9 @@ (store-controller (assert (not (equal dst-spec (controller-spec src))))) (persistent (assert (not (equal dst-spec (dbcn-spc-pst src)))))))))
+;; ;; WHOLE STORE MIGRATION +;;
(defmethod migrate ((dst store-controller) (src store-controller)) "Perform a wholesale repository migration from the root. @@ -184,20 +183,43 @@ (setf (get-value (oid newinst) new) newinst)))) old)))
+;; +;; Utilities for persistent objects +;;
-;; PERSISTENT OBJECTS THAT AREN'T INDICES +(defun reset-migrate-duplicate-detection () + "Reset oid map so that all references to a given object + in the source only point to one copy in the target" + (setf *migrate-copied-oids* (make-hash-table)))
-(defvar *inhibit-slot-copy* nil) +(defun object-was-copied-p (src) + "Test whether a source object has been copied" + (and (subtypep (type-of src) 'persistent) + (gethash (oid src) *migrate-copied-oids*))) + +(defun register-copied-object (src dst) + "When copying a source object, store it in the oid map" + (assert (not (equal (dbcn-spc-pst src) (dbcn-spc-pst dst)))) + (setf (gethash (oid src) *migrate-copied-oids*) dst)) + +(defun retrieve-copied-object (src) + "Get a copied object from the oid map" + (gethash (oid src) *migrate-copied-oids*))
(defmacro with-inhibited-slot-copy ((&key &allow-other-keys) &body body) + "A user macro to support special slot handling in persistent objects" `(let ((*inhibit-slot-copy* t)) - (declare (special *inhibit-slot-copy*) - (dynamic-extent *inhibit-slot-copy*)) + (declare (special *inhibit-slot-copy*)) ,@body))
+;; +;; PERSISTENT OBJECTS +;; + +(defvar *inhibit-slot-copy* nil) + (defmethod migrate ((dst store-controller) (src persistent)) "Migrate a persistent object and apply a binary (lambda (dst src) ...) - function to the new object. Users can override migrate by creating a function that calls the default copy and then does stuff with the slot values. A dynamic variable: *inhibit-slot-copy* can be bound @@ -207,58 +229,38 @@ (retrieve-copied-object src) (copy-persistent-object dst src)))
-;; (defmethod migrate ((dst store-controller) (class persistent-metaclass)) -;; "Migrate classes with indices" -;; (let ((dstcidx (get-value (class-name class) (controller-class-root dst)))) -;; (when (and (indexed class) ;; indexed -;; (not dstcidx) ;; hasn't been copied -;; (%index-cache class)) ;; we have a valid reference -;; (format t "Migrating class~A~%" (class-name class)) -;; (let ((new-cidx (migrate dst (%index-cache class) -;; (setf (get-value (class-name class) (controller-class-root dst)) new-cidx) -;; (setf (%index-cache class) new-cidx))) -;; class) - -(defun reset-migrate-duplicate-detection () - (setf *migrate-copied-oids* (make-hash-table))) - -(defun object-was-copied-p (src) - (and (subtypep (type-of src) 'persistent) - (gethash (oid src) *migrate-copied-oids*))) - -(defun register-copied-object (src dst) - (assert (not (equal (dbcn-spc-pst src) (dbcn-spc-pst dst)))) - (setf (gethash (oid src) *migrate-copied-oids*) dst)) - -(defun retrieve-copied-object (src) - (gethash (oid src) *migrate-copied-oids*)) - (defun copy-persistent-object (dstsc src) "Copy the persistent object reference by making a new one and potentially copy over the slot values as well" (let* ((class (class-of src)) (dst (make-instance (class-of src) :sc dstsc))) (register-copied-object src dst) - (when (and (not *inhibit-slot-copy*) - (not (inhibit-indexed-slot-copy? dstsc class))) - (copy-persistent-slots dstsc (class-of src) src dst)) + (unless (inhibit-indexed-slot-copy? dstsc class) + (copy-persistent-slots dstsc dst (class-of src) src)) dst))
(defun inhibit-indexed-slot-copy? (sc class) - (and (indexed class) - (not (equal (controller-spec sc) - (dbcn-spc-pst (%index-cache class)))))) + "Make sure that we don't copy slots if the user inhibits + or if the class is indexed and has not yet migrated to + the new store - the indexing copy will do this." + (or *inhibit-slot-copy* + (and (indexed class) + (not (equal (controller-spec sc) + (dbcn-spc-pst (%index-cache class)))))))
-(defun copy-persistent-slots (dstsc class src dst) +(defun copy-persistent-slots (dstsc dst class src) "Copy only persistent slots from src to dst" (ensure-transaction (:store-controller dstsc) (loop for slot-def in (persistent-slot-defs class) do (when (slot-boundp-using-class class src slot-def) +;; (format t "Slotname: ~A value: ~A~%" (elephant::slot-definition-name slot-def) +;; (slot-value-using-class class src slot-def)) (let ((value (migrate dstsc (slot-value-using-class class src slot-def)))) (setf (slot-value-using-class class dst slot-def) value))))))
- -;; MIGRATE INDICES (Override normal persistent copies) +;; +;; MIGRATE BTREE INDICES (override default persistent behavior) +;;
(defmethod migrate ((dst store-controller) (src btree)) "Copy an index and it's contents to the target repository" @@ -295,40 +297,46 @@ (setf (get-value newkey dst) newval))) src))
+;; +;; These functions handle standard objects that may contain nested indices or +;; user-defined persistent objects. +;;
-;; SUPPORT LISP COLLECTIONS TO HANDLE NESTED PERSISTENT OBJECTS -;; CLEANLY - -;; If we don't do this, then a nested persistent object may be -;; of the source store's class and fail to copy slots on a write -;; and we'll silently lose data... +(defmethod migrate ((dst store-controller) (src standard-object)) + "If we have persistent objects that are unindexed and ONLY stored in + a standard object slot that is referenced from the root, then it + will only be copied by recursing through the slot substructure just + as the serializer will, but copying any persistent objects found" + (let ((svs (slots-and-values src))) + (loop for i from 0 below (/ (length svs) 2) do + (let ((name (pop svs)) + (value (pop svs))) + (setf (slot-value src name) (migrate dst value))))))
-(defmethod migrate ((dst store-controller) (src hash-table)) - "Copy the hash elements one at a time" - (let ((newhash (make-hash-table - :test (hash-table-test src) - :size (hash-table-size src) - :rehash-size (hash-table-rehash-size src) - :rehash-threshold (hash-table-rehash-threshold src)))) - (maphash (lambda (key value) - (setf (gethash key newhash) - (migrate dst value))) - src)))
(defmethod migrate ((dst store-controller) (src cons)) - "WARNING: This assumes a standard list or tree-of-lists, but doesn't - work for circular lists!" + "WARNING: This doesn't work for circular lists" (cons (migrate dst (car src)) (migrate dst (cdr src))))
-(defmethod migrate ((dst store-controller) (src string)) - "Strings are fine to copy as is" +(defmethod migrate ((dst store-controller) (src array)) + "We only need to handle arrays of type 't' that point to other objects; + fixnum, float, etc arrays don't need to be copied" + (loop for i fixnum from 0 below (array-total-size src) do + (let ((value (row-major-aref src i))) + (setf (row-major-aref src i) + (migrate dst value)))) src)
-(defmethod migrate ((dst store-controller) (src array)) - "NOTE: We need to handle arrays that might contain persistent objects!" - (warn "Arrays containing persistent objects will fail migration!") +(defmethod migrate ((dst store-controller) (src hash-table)) + "Migrate each hash element as the types are non-uniform" + (maphash (lambda (key value) + (setf (gethash key src) + (migrate dst value))) + src) src)
+ +