##// END OF EJS Templates
Fix transactions for objects fetched from the db (not created in this image)
someseven -
r5:94e6ef46 tip default
parent child Browse files
Show More
@@ -1,71 +1,73 b''
1 1
2 2 (in-package mito-transactions.dev)
3 3
4 4 ;;;; Cache for `mito` objects with the main purpose of avoiding
5 5 ;;;; several instances of the same dao which are not `eq` to each
6 6 ;;;; other. Cached objects are only removed when they are GC'ed so
7 7 ;;;; next time they will have other identity but it's not a problem
8 8 ;;;; beause there would be no previous objects to EQ against.
9 9
10 10 (defmethod mito.dao.mixin:make-dao-instance ((obj cached-dao-class) &key &allow-other-keys)
11 11 "This method is called on creation of dao instances loaded from the DB. "
12 12 (declare (ignore obj))
13 13 (maybe-lock
14 14 (let ((*ignore-transaction* t))
15 (do-cache (call-next-method)))))
15 (let ((obj (call-next-method)))
16 (setf (value-backup obj) nil)
17 (do-cache obj)))))
16 18
17 19 (defmethod mito:insert-dao ((obj cached-dao))
18 20 (maybe-lock
19 21 (call-next-method)
20 22 (setf (gethash (mito:object-id obj) (class-cache obj)) obj)
21 23 obj))
22 24
23 25 (defmethod mito:delete-dao ((obj cached-dao))
24 26 (maybe-lock
25 27 (remhash (mito:object-id obj) (class-cache (class-of obj)))
26 28 (call-next-method)))
27 29
28 30 (defmethod mito:delete-by-values ((class cached-dao-class) &rest fields-and-values)
29 31 (maybe-lock
30 32 ;; We need their IDs to remove from the cache
31 33 (let ((objs (apply #'mito:retrieve-dao class fields-and-values)))
32 34 (call-next-method)
33 35 (dolist (obj objs)
34 36 (remhash (mito:object-id obj) (class-cache (class-of obj))))
35 37 (values))))
36 38
37 39 (defgeneric class-cache (class)
38 40 (:method ((class cached-dao-class))
39 41 (slot-value class 'cache))
40 42 (:method ((obj cached-dao))
41 43 (class-cache (class-of obj)))
42 44 (:method ((classname symbol))
43 45 (class-cache (find-class classname))))
44 46
45 47 (defun get-dao (class id)
46 48 "Fetch an object directly from the cache, only check the db if it's not cached."
47 49 (maybe-lock
48 50 (or (gethash id (class-cache class))
49 51 (do-cache (first (mito:select-dao class (sxql:where (:= :id id))))))))
50 52
51 53 (defun get-dao-from-cache-only (class id)
52 54 "Fetch an object directly from the cache, return NIL if it's not cached."
53 55 (maybe-lock
54 56 (gethash id (class-cache class))))
55 57
56 58 (defun do-cache (obj)
57 59 (when obj
58 60 (let ((cache (class-cache obj)))
59 61 (sif (gethash (mito:object-id obj) cache)
60 62 (copy-fields it obj)
61 63 (setf it obj)))))
62 64
63 65 (defun copy-fields (to from)
64 66 (when (local-time:timestamp> (mito:object-updated-at from)
65 67 (mito:object-updated-at to))
66 68 (let ((*ignore-transaction* t)
67 69 (slot-names (mapcar #'c2mop:slot-definition-name
68 70 (mito.class.table:table-column-slots (class-of from)))))
69 71 (dolist (slot-name slot-names)
70 72 (setf (slot-value to slot-name) (slot-value from slot-name)))))
71 73 to)
@@ -1,80 +1,86 b''
1 1
2 2 (in-package :cl-user)
3 3 (defpackage mito-transactions.tests
4 4 (:use :cl #:mito-transactions #:parachute))
5 5
6 6 (in-package mito-transactions.tests)
7 7
8 8 (setf mito:*auto-migration-mode* t)
9 9 (setf mito:*migration-keep-temp-tables* nil)
10 10
11 11 (deftable test-object ()
12 12 ((slot :col-type (or :null :integer) :initarg :slot :initform 42)
13 13 (ghost-slot :ghost t))
14 14 (:conc-name :||))
15 15
16 16 (defmethod print-object ((o test-object) stream)
17 17 (print-unreadable-object (o stream :type t :identity t)
18 18 (format stream "~A" (mito:object-id o))))
19 19
20 20 (mito:connect-toplevel :sqlite3 :database-name (asdf:system-relative-pathname :mito-transactions "test.db"))
21 21 (mito:ensure-table-exists 'test-object)
22 22
23 23 (define-test suite)
24 24
25 25 (define-test setup-db
26 26 (delete-by-values 'test-object))
27 27
28 28 (define-test (suite cache-select)
29 29 :depends-on (setup-db)
30 30 ;; Create an object
31 31 (let ((obj (make-instance 'test-object)))
32 32 ;; Check that it's the same object after any select function
33 33 (is eq obj (get-dao 'test-object (object-id obj)))
34 34 (is eq obj (find-dao 'test-object 'slot 42))
35 35 (is eq obj (first (retrieve-dao 'test-object 'slot 42)))
36 36 (is eq obj (first (select-dao 'test-object (sxql:where (:= :slot 42)))))))
37 37
38 38 (define-test (suite cache-delete)
39 39 :depends-on (setup-db cache-select)
40 40 ;; Delete the object and check that it's removed from the cache too
41 41 (let ((obj (make-instance 'test-object)))
42 42 (delete-dao obj)
43 43 (is eq nil (get-dao-from-cache-only 'test-object (object-id obj)) "DELETE-DAO didn't remove object from cache")
44 44 (is eq nil (get-dao 'test-object (object-id obj)) "DELETE-DAO didn't remove object from db"))
45 45 ;; Repeat for mito:delete-by-values
46 46 (let ((obj (make-instance 'test-object)))
47 47 (delete-by-values 'test-object 'slot 42)
48 48 (is eq nil (get-dao-from-cache-only 'test-object (object-id obj)) "DELETE-BY-VALUES didn't remove objects from cache")
49 49 (is eq nil (get-dao 'test-object (object-id obj)) "DELETE-BY-VALUES didn't remove objects from db")))
50 50
51 51 (define-test (suite transactions)
52 52 :depends-on (setup-db cache-select)
53 53 (let ((obj (make-instance 'test-object :slot 1)))
54 54 (with-transaction ()
55 55 (setf (slot obj) 2)
56 56 (setf (slot obj) 3)
57 57 (commit)
58 58 (true nil "Transaction continued after COMMIT"))
59 59 (is = 3 (slot obj) "COMMIT didn't save slot value")
60 60 (with-transaction ()
61 61 (setf (slot obj) 4)
62 62 (setf (slot obj) 5)
63 63 (rollback)
64 64 (true nil "Transaction continued after ROLLBACK"))
65 65 (is = 3 (slot obj) "ROLLBACK didn't restore the initial slot value")
66 66 (with-transaction ()
67 67 (setf (slot obj) 1)
68 68 (with-transaction ()
69 69 (setf (slot obj) 2)
70 70 (rollback)
71 71 (true nil "Inner transaction continued after ROLLBACK"))
72 72 (true nil "Outer transaction continued after ROLLBACK"))
73 73 (is = 3 (slot obj) "ROLLBACK from a nested transaction didn't restore the initial slot value")
74 74 (with-transaction ()
75 75 (slot-makunbound obj 'slot))
76 76 (false (slot-boundp obj 'slot) "Transaction failed to unbind a slot")
77 77 (with-transaction ()
78 78 (setf (slot obj) 42)
79 79 (rollback))
80 (false (slot-boundp obj 'slot) "Transaction rollback failed to unbind a slot")))
80 (false (slot-boundp obj 'slot) "Transaction rollback failed to unbind a slot")
81 (remhash (mito:object-id obj) (mito-transactions.dev::class-cache 'test-object)))
82 (let (obj)
83 (with-transaction ()
84 (setf obj (find-dao 'test-object))
85 (setf (slot obj) 2))
86 (is = 2 (slot obj))))
General Comments 0
You need to be logged in to leave comments. Login now