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 |
( |
|
|
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