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