1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
6 ;;;; Base database functions
8 ;;;; This file is part of CLSQL.
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 ;;;; *************************************************************************
15 (in-package #:clsql-sys)
17 (setf (documentation 'database-name 'function)
18 "Returns the name of a database.")
22 (defvar *connect-if-exists* :error
23 "Default value for the if-exists keyword argument in calls to
24 CONNECT. Meaningful values are :new, :warn-new, :error, :warn-old
27 (defvar *connected-databases* nil
28 "List of active database objects.")
30 (defun connected-databases ()
31 "Returns the list of active database objects."
32 *connected-databases*)
34 (defvar *default-database* nil
35 "Specifies the default database to be used.")
37 (defun is-database-open (database)
38 (eql (database-state database) :open))
40 (defun find-database (database &key (errorp t) (db-type nil))
41 "Returns the connected databases of type DB-TYPE whose names
42 match the string DATABASE. If DATABASE is a database object, it
43 is returned. If DB-TYPE is nil all databases matching the string
44 DATABASE are considered. If no matching databases are found and
45 ERRORP is nil then nil is returned. If ERRORP is nil and one or
46 more matching databases are found, then the most recently
47 connected database is returned as a first value and the number of
48 matching databases is returned as a second value. If no, or more
49 than one, matching databases are found and ERRORP is true, an
55 (let* ((matches (remove-if
57 (not (and (string= (database-name db) database)
59 (equal (database-type db) db-type)
61 (connected-databases)))
62 (count (length matches)))
63 (if (or (not errorp) (= count 1))
64 (values (car matches) count)
68 (format nil "There exists ~A database called ~A."
69 (if (zerop count) "no" "more than one")
73 (defun connect (connection-spec
74 &key (if-exists *connect-if-exists*)
77 (database-type *default-database-type*))
78 "Connects to a database of the supplied DATABASE-TYPE which
79 defaults to *DEFAULT-DATABASE-TYPE*, using the type-specific
80 connection specification CONNECTION-SPEC. The value of IF-EXISTS,
81 which defaults to *CONNECT-IF-EXISTS*, determines what happens if
82 a connection to the database specified by CONNECTION-SPEC is
83 already established. A value of :new means create a new
84 connection. A value of :warn-new means warn the user and create
85 a new connect. A value of :warn-old means warn the user and use
86 the old connection. A value of :error means fail, notifying the
87 user. A value of :old means return the old connection.
88 MAKE-DEFAULT is t by default which means that *DEFAULT-DATABASE*
89 is set to the new connection, otherwise *DEFAULT-DATABASE* is not
90 changed. If POOL is t the connection will be taken from the
91 general pool, if POOL is a CONN-POOL object the connection will
92 be taken from this pool."
95 (error 'sql-database-error :message "Must specify a database-type."))
97 (when (stringp connection-spec)
98 (setq connection-spec (string-to-list-connection-spec connection-spec)))
100 (unless (member database-type *loaded-database-types*)
101 (asdf:operate 'asdf:load-op (ensure-keyword
103 (symbol-name '#:clsql-)
104 (symbol-name database-type)))))
107 (acquire-from-pool connection-spec database-type pool)
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
117 (database-connect connection-spec database-type))
121 "Created new connection ~A to database ~A~%, although there is an existing connection (~A)."
122 result (database-name result) old-db)))
125 (error 'sql-connection-error
127 "There is an existing connection ~A to database ~A."
129 (database-name old-db))
131 :report "Create a new connection."
133 (database-connect connection-spec database-type)))
135 :report "Use the existing connection."
136 (setq result old-db))))
142 "Using existing connection ~A to database ~A."
144 (database-name old-db))))
146 (setq result old-db)))
148 (database-connect connection-spec database-type)))
150 (setf (slot-value result 'state) :open)
151 (pushnew result *connected-databases*)
152 (when make-default (setq *default-database* result))
156 (defun disconnect (&key (database *default-database*) (error nil))
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))))
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*)))
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)
182 ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
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."
190 (destructuring-bind ,template ,connection-spec
191 (declare (ignore ,@(remove '&optional template)))
194 (error 'sql-user-error
197 "The connection specification ~A~%is invalid for database type ~A.~%The connection specification must conform to ~A"
200 (quote ,template))))))
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
218 (let ((db (find-database database :errorp nil)))
220 (if (and database error)
221 (error 'clsql-generic-error
223 (format nil "Unable to find database with connection-spec ~A." database))
224 (return-from reconnect nil)))
227 (when (is-database-open db)
229 (ignore-errors (disconnect :database db))
230 (disconnect :database db :error nil)))
232 (connect (connection-spec db))))
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."
242 (dolist (db (connected-databases) data)
245 (list (if (equal db *default-database*) "*" "")
247 (string-downcase (string (database-type db)))
248 (cond ((and (command-recording-stream db)
249 (result-recording-stream db))
251 ((command-recording-stream db) "Commands")
252 ((result-recording-stream db) "Results")
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))))))
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)))
268 (let* ((titles (if full
269 (list "" "DATABASE" "TYPE" "RECORDING" "POOLED"
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))))
282 (defun create-database (connection-spec &key database-type)
283 (when (stringp connection-spec)
284 (setq connection-spec (string-to-list-connection-spec connection-spec)))
285 (database-create connection-spec database-type))
287 (defun probe-database (connection-spec &key database-type)
288 (when (stringp connection-spec)
289 (setq connection-spec (string-to-list-connection-spec connection-spec)))
290 (database-probe connection-spec database-type))
292 (defun destroy-database (connection-spec &key database-type)
293 (when (stringp connection-spec)
294 (setq connection-spec (string-to-list-connection-spec connection-spec)))
295 (database-destroy connection-spec database-type))
297 (defun list-databases (connection-spec &key database-type)
298 (when (stringp connection-spec)
299 (setq connection-spec (string-to-list-connection-spec connection-spec)))
300 (database-list connection-spec database-type))
302 (defmacro with-database ((db-var connection-spec &rest connect-args) &body body)
303 "Evaluate the body in an environment, where `db-var' is bound to the
304 database connection given by `connection-spec' and `connect-args'.
305 The connection is automatically closed or released to the pool on exit from the body."
306 (let ((result (gensym "result-")))
307 (unless db-var (setf db-var '*default-database*))
308 `(let ((,db-var (connect ,connection-spec ,@connect-args))
311 (let ((,db-var ,db-var))
312 (setf ,result (progn ,@body)))
313 (disconnect :database ,db-var))
317 (defmacro with-default-database ((database) &rest body)
318 "Perform BODY with DATABASE bound as *default-database*."
319 `(progv '(*default-database*)