r8821: integrate usql support
[clsql.git] / base / transaction.lisp
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;;
4 ;;;; $Id: transactions.lisp 7061 2003-09-07 06:34:45Z kevin $
5
6 (in-package #:clsql-base-sys)
7
8 (defclass transaction ()
9   ((commit-hooks :initform () :accessor commit-hooks)
10    (rollback-hooks :initform () :accessor rollback-hooks)
11    (status :initform nil :accessor transaction-status))) ; nil or :committed
12
13 (defun commit-transaction (database)
14   (when (and (transaction database)
15              (not (transaction-status (transaction database))))
16     (setf (transaction-status (transaction database)) :committed)))
17
18 (defun add-transaction-commit-hook (database commit-hook)
19   (when (transaction database)
20     (push commit-hook (commit-hooks (transaction database)))))
21
22 (defun add-transaction-rollback-hook (database rollback-hook)
23   (when (transaction database)
24     (push rollback-hook (rollback-hooks (transaction database)))))
25
26 (defmethod database-start-transaction ((database closed-database))
27   (signal-closed-database-error database))
28
29 (defmethod database-start-transaction (database)
30   (unless database (error 'clsql-nodb-error))
31   (unless (transaction database)
32     (setf (transaction database) (make-instance 'transaction)))
33   (when (= (incf (transaction-level database) 1))
34     (let ((transaction (transaction database)))
35       (setf (commit-hooks transaction) nil
36             (rollback-hooks transaction) nil
37             (transaction-status transaction) nil)
38       (execute-command "BEGIN" :database database))))
39
40 (defmethod database-commit-transaction ((database closed-database))
41   (signal-closed-database-error database))
42
43 (defmethod database-commit-transaction (database)
44     (if (> (transaction-level database) 0)
45         (when (zerop (decf (transaction-level database)))
46           (execute-command "COMMIT" :database database)
47           (map nil #'funcall (commit-hooks (transaction database))))
48         (warn 'clsql-simple-warning
49               :format-control "Cannot commit transaction against ~A because there is no transaction in progress."
50               :format-arguments (list database))))
51
52 (defmethod database-abort-transaction ((database closed-database))
53   (signal-closed-database-error database))
54
55 (defmethod database-abort-transaction (database)
56     (if (> (transaction-level database) 0)
57         (when (zerop (decf (transaction-level database)))
58           (unwind-protect 
59                (execute-command "ROLLBACK" :database database)
60             (map nil #'funcall (rollback-hooks (transaction database)))))
61         (warn 'clsql-simple-warning
62               :format-control "Cannot abort transaction against ~A because there is no transaction in progress."
63               :format-arguments (list database))))
64
65
66 (defmacro with-transaction ((&key (database *default-database*)) &rest body)
67   "Executes BODY within a transaction for DATABASE (which defaults to
68 *DEFAULT-DATABASE*). The transaction is committed if the body finishes
69 successfully (without aborting or throwing), otherwise the database is
70 rolled back."
71   (let ((db (gensym "db-")))
72     `(let ((,db ,database))
73       (unwind-protect
74            (progn
75              (database-start-transaction ,db)
76              ,@body
77              (commit-transaction ,db))
78         (if (eq (transaction-status (transaction ,db)) :committed)
79             (database-commit-transaction ,db)
80             (database-abort-transaction ,db))))))
81
82 (defun commit (&key (database *default-database*))
83   "Commits changes made to DATABASE which defaults to *DEFAULT-DATABASE*."
84   (database-commit-transaction database))
85
86 (defun rollback (&key (database *default-database*))
87   "Rolls back changes made in DATABASE, which defaults to
88 *DEFAULT-DATABASE* since the last commit, that is changes made since
89 the last commit are not recorded."
90   (database-abort-transaction database))
91
92 (defun start-transaction (&key (database *default-database*))
93   "Starts a transaction block on DATABASE which defaults to
94 *default-database* and which continues until ROLLBACK or COMMIT are
95 called."
96   (unless (in-transaction-p :database database)
97     (database-start-transaction database)))
98
99 (defun in-transaction-p (&key (database *default-database*))
100   "A predicate to test whether we are currently within the scope of a
101 transaction in DATABASE."
102   (and database (transaction database) (= (transaction-level database) 1)))