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