r10975: 07 Jul 2006 Kevin Rosenberg <kevin@rosenberg.net>
[clsql.git] / sql / database.lisp
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;;
4 ;;;; $Id$
5 ;;;;
6 ;;;; Base database functions
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-sys)
16
17
18 (defvar *connect-if-exists* :error
19   "Default value for the if-exists keyword argument in calls to
20 CONNECT. Meaningful values are :new, :warn-new, :error, :warn-old
21 and :old.")
22
23 (defvar *connected-databases* nil
24   "List of active database objects.")
25
26 (defun connected-databases ()
27   "Returns the list of active database objects."
28   *connected-databases*)
29
30 (defvar *default-database* nil
31   "Specifies the default database to be used.")
32
33 (defun is-database-open (database)
34   (eql (database-state database) :open))
35
36 (defun find-database (database &key (errorp t) (db-type nil))
37   "Returns the connected databases of type DB-TYPE whose names
38 match the string DATABASE. If DATABASE is a database object, it
39 is returned. If DB-TYPE is nil all databases matching the string
40 DATABASE are considered.  If no matching databases are found and
41 ERRORP is nil then nil is returned. If ERRORP is nil and one or
42 more matching databases are found, then the most recently
43 connected database is returned as a first value and the number of
44 matching databases is returned as a second value. If no, or more
45 than one, matching databases are found and ERRORP is true, an
46 error is signalled."
47   (etypecase database
48     (database
49      (values database 1))
50     (string
51      (let* ((matches (remove-if
52                       #'(lambda (db)
53                           (not (and (string= (database-name db) database)
54                                     (if db-type
55                                         (equal (database-type db) db-type)
56                                         t))))
57                       (connected-databases)))
58             (count (length matches)))
59        (if (or (not errorp) (= count 1))
60            (values (car matches) count)
61            (cerror "Return nil."
62                    'sql-database-error
63                    :message
64                    (format nil "There exists ~A database called ~A."
65                            (if (zerop count) "no" "more than one")
66                            database)))))
67     (null
68      (error "A database must be specified rather than NIL."))))
69
70
71 (defun connect (connection-spec
72                 &key (if-exists *connect-if-exists*)
73                 (make-default t)
74                 (pool nil)
75                 (database-type *default-database-type*))
76   "Connects to a database of the supplied DATABASE-TYPE which
77 defaults to *DEFAULT-DATABASE-TYPE*, using the type-specific
78 connection specification CONNECTION-SPEC. The value of IF-EXISTS,
79 which defaults to *CONNECT-IF-EXISTS*, determines what happens if
80 a connection to the database specified by CONNECTION-SPEC is
81 already established.  A value of :new means create a new
82 connection.  A value of :warn-new means warn the user and create
83 a new connect.  A value of :warn-old means warn the user and use
84 the old connection.  A value of :error means fail, notifying the
85 user.  A value of :old means return the old connection.
86 MAKE-DEFAULT is t by default which means that *DEFAULT-DATABASE*
87 is set to the new connection, otherwise *DEFAULT-DATABASE* is not
88 changed. If POOL is t the connection will be taken from the
89 general pool, if POOL is a CONN-POOL object the connection will
90 be taken from this pool."
91
92   (unless database-type
93     (error 'sql-database-error :message "Must specify a database-type."))
94
95   (when (stringp connection-spec)
96     (setq connection-spec (string-to-list-connection-spec connection-spec)))
97
98   (unless (member database-type *loaded-database-types*)
99     (asdf:operate 'asdf:load-op (ensure-keyword
100                                  (concatenate 'string
101                                               (symbol-name '#:clsql-)
102                                               (symbol-name database-type)))))
103
104   (if pool
105       (let ((conn (acquire-from-pool connection-spec database-type pool)))
106         (when make-default (setq *default-database* conn))
107         conn)
108       (let* ((db-name (database-name-from-spec connection-spec database-type))
109              (old-db (unless (eq if-exists :new)
110                        (find-database db-name :db-type database-type
111                                       :errorp nil)))
112              (result nil))
113         (if old-db
114             (ecase if-exists
115               (:warn-new
116                (setq result
117                      (database-connect connection-spec database-type))
118                (warn 'sql-warning
119                      :message
120                      (format nil
121                              "Created new connection ~A to database ~A~%, although there is an existing connection (~A)."
122                              result (database-name result) old-db)))
123               (:error
124                (restart-case
125                    (error 'sql-connection-error
126                           :message
127                           (format nil "There is an existing connection ~A to database ~A."
128                           old-db
129                           (database-name old-db)))
130                  (create-new ()
131                    :report "Create a new connection."
132                    (setq result
133                          (database-connect connection-spec database-type)))
134                  (use-old ()
135                    :report "Use the existing connection."
136                    (setq result old-db))))
137               (:warn-old
138                (setq result old-db)
139                (warn 'sql-warning
140                      :message
141                      (format nil
142                              "Using existing connection ~A to database ~A."
143                              old-db
144                              (database-name old-db))))
145               (:old
146                (setq result old-db)))
147             (setq result
148                   (database-connect connection-spec database-type)))
149         (when result
150           (setf (slot-value result 'state) :open)
151           (pushnew result *connected-databases*)
152           (when make-default (setq *default-database* result))
153           result))))
154
155
156 (defun disconnect (&key (database *default-database*) (error nil))
157
158   "Closes the connection to DATABASE and resets
159 *DEFAULT-DATABASE* if that database was disconnected. If DATABASE
160 is a database instance, this object is closed. If DATABASE is a
161 string, then a connected database whose name matches DATABASE is
162 sought in the list of connected databases. If no matching
163 database is found and ERROR and DATABASE are both non-nil an
164 error is signaled, otherwise nil is returned. If the database is
165 from a pool it will be released to this pool."
166   (let ((database (find-database database :errorp (and database error))))
167     (when database
168       (if (conn-pool database)
169           (when (release-to-pool database)
170             (setf *connected-databases* (delete database *connected-databases*))
171             (when (eq database *default-database*)
172               (setf *default-database* (car *connected-databases*)))
173             t)
174           (when (database-disconnect database)
175             (setf *connected-databases* (delete database *connected-databases*))
176             (when (eq database *default-database*)
177               (setf *default-database* (car *connected-databases*)))
178             (setf (slot-value database 'state) :closed)
179             t)))))
180
181
182 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
183
184
185 (defmacro check-connection-spec (connection-spec database-type template)
186   "Check the connection specification against the provided template,
187 and signal an sql-user-error if they don't match. This function
188 is called by database backends."
189   `(handler-case
190     (destructuring-bind ,template ,connection-spec
191       (declare (ignore ,@(remove '&optional template)))
192       t)
193     (error ()
194      (error 'sql-user-error
195       :message
196       (format nil
197               "The connection specification ~A~%is invalid for database type ~A.~%The connection specification must conform to ~A"
198               ,connection-spec
199               ,database-type
200               (quote ,template))))))
201
202 (defun reconnect (&key (database *default-database*) (error nil) (force t))
203   "Reconnects DATABASE which defaults to *DEFAULT-DATABASE* to
204 the underlying database management system. On success, t is
205 returned and the variable *DEFAULT-DATABASE* is set to the newly
206 reconnected database. If DATABASE is a database instance, this
207 object is closed. If DATABASE is a string, then a connected
208 database whose name matches DATABASE is sought in the list of
209 connected databases. If no matching database is found and ERROR
210 and DATABASE are both non-nil an error is signaled, otherwise nil
211 is returned. When the current database connection cannot be
212 closed, if FORCE is non-nil, as it is by default, the connection
213 is closed and errors are suppressed. If force is nil and the
214 database connection cannot be closed, an error is signalled."
215   (let ((db (etypecase database
216               (database database)
217               ((or string list)
218                (let ((db (find-database database :errorp nil)))
219                  (when (null db)
220                    (if (and database error)
221                        (error 'sql-connection-error
222                               :message
223                               (format nil "Unable to find database with connection-spec ~A." database))
224                        (return-from reconnect nil)))
225                  db)))))
226
227     (when (is-database-open db)
228       (if force
229           (ignore-errors (disconnect :database db))
230           (disconnect :database db :error nil)))
231
232     (connect (connection-spec db))))
233
234
235 (defun status (&optional full)
236   "Prints information about the currently connected databases to
237 *STANDARD-OUTPUT*. The argument FULL is nil by default and a
238 value of t means that more detailed information about each
239 database is printed."
240   (flet ((get-data ()
241            (let ((data '()))
242              (dolist (db (connected-databases) data)
243                (push
244                 (append
245                  (list (if (equal db *default-database*) "*" "")
246                        (database-name db)
247                        (string-downcase (string (database-type db)))
248                        (cond ((and (command-recording-stream db)
249                                    (result-recording-stream db))
250                               "Both")
251                              ((command-recording-stream db) "Commands")
252                              ((result-recording-stream db) "Results")
253                              (t "nil")))
254                  (when full
255                    (list
256                     (if (conn-pool db) "t" "nil")
257                     (format nil "~A" (length (database-list-tables db)))
258                     (format nil "~A" (length (database-list-views db))))))
259                 data))))
260          (compute-sizes (data)
261            (mapcar #'(lambda (x) (apply #'max (mapcar #'length x)))
262                    (apply #'mapcar (cons #'list data))))
263          (print-separator (size)
264            (format t "~&~A" (make-string size :initial-element #\-))))
265     (format t "~&CLSQL STATUS: ~A~%" (iso-timestring (get-time)))
266     (let ((data (get-data)))
267       (when data
268         (let* ((titles (if full
269                            (list "" "DATABASE" "TYPE" "RECORDING" "POOLED"
270                                  "TABLES" "VIEWS")
271                            (list "" "DATABASE" "TYPE" "RECORDING")))
272                (sizes (compute-sizes (cons titles data)))
273                (total-size (+ (apply #'+ sizes) (* 2 (1- (length titles)))))
274                (control-string (format nil "~~&~~{~{~~~AA  ~}~~}" sizes)))
275           (print-separator total-size)
276           (format t control-string titles)
277           (print-separator total-size)
278           (dolist (d data) (format t control-string d))
279           (print-separator total-size))))
280     (values)))
281
282 (defun create-database (connection-spec &key database-type)
283   "This function creates a database in the database system specified
284 by DATABASE-TYPE."
285   (when (stringp connection-spec)
286     (setq connection-spec (string-to-list-connection-spec connection-spec)))
287   (database-create connection-spec database-type))
288
289 (defun probe-database (connection-spec &key database-type)
290   "This function tests for the existence of a database in the database
291 system specified by DATABASE-TYPE."
292   (when (stringp connection-spec)
293     (setq connection-spec (string-to-list-connection-spec connection-spec)))
294   (database-probe connection-spec database-type))
295
296 (defun destroy-database (connection-spec &key database-type)
297   "This function destroys a database in the database system specified
298 by DATABASE-TYPE."
299   (when (stringp connection-spec)
300     (setq connection-spec (string-to-list-connection-spec connection-spec)))
301   (database-destroy connection-spec database-type))
302
303 (defun list-databases (connection-spec &key database-type)
304   "This function returns a list of databases existing in the database
305 system specified by DATABASE-TYPE."
306   (when (stringp connection-spec)
307     (setq connection-spec (string-to-list-connection-spec connection-spec)))
308   (database-list connection-spec database-type))
309
310 (defmacro with-database ((db-var connection-spec &rest connect-args) &body body)
311   "Evaluate the body in an environment, where DB-VAR is bound to the
312 database connection given by CONNECTION-SPEC and CONNECT-ARGS.  The
313 connection is automatically closed or released to the pool on exit
314 from the body."
315   (let ((result (gensym "result-")))
316     (unless db-var (setf db-var '*default-database*))
317     `(let ((,db-var (connect ,connection-spec ,@connect-args))
318            (,result nil))
319       (unwind-protect
320            (let ((,db-var ,db-var))
321              (setf ,result (progn ,@body)))
322         (disconnect :database ,db-var))
323       ,result)))
324
325
326 (defmacro with-default-database ((database) &rest body)
327   "Perform BODY with DATABASE bound as *default-database*."
328   `(progv '(*default-database*)
329        (list ,database)
330      ,@body))
331