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