r1698: *** empty log message ***
[clsql.git] / sql / sql.cl
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
4 ;;;;
5 ;;;; Name:          sql.cl
6 ;;;; Purpose:       High-level SQL interface
7 ;;;; Programmers:   Kevin M. Rosenberg based on
8 ;;;;                 Original code by Pierre R. Mai 
9 ;;;; Date Started:  Feb 2002
10 ;;;;
11 ;;;; $Id: sql.cl,v 1.10 2002/03/29 08:23:38 kevin Exp $
12 ;;;;
13 ;;;; This file, part of CLSQL, is Copyright (c) 2002 by Kevin M. Rosenberg
14 ;;;; and Copyright (c) 1999-2001 by Pierre R. Mai
15 ;;;;
16 ;;;; CLSQL users are granted the rights to distribute and use this software
17 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
18 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
19 ;;;; *************************************************************************
20
21 (declaim (optimize (debug 3) (speed 3) (safety 1) (compilation-speed 0)))
22 (in-package :clsql-sys)
23
24 ;;; Modified by KMR 
25 ;;; - to use CMUCL-COMPAT library 
26 ;;; - fix format strings in error messages 
27 ;;; - use field types
28
29
30 ;;; Simple implementation of SQL along the lines of Harlequin's Common SQL
31
32
33 ;;; Database Types
34
35 (defvar *loaded-database-types* nil
36   "Contains a list of database types which have been defined/loaded.")
37
38 (defun reload-database-types ()
39   "Reloads any foreign code for the loaded database types after a dump."
40   (mapc #'database-type-load-foreign *loaded-database-types*))
41
42 (defvar *default-database-type* nil
43   "Specifies the default type of database.  Currently only :mysql is
44 supported.")
45
46 (defvar *initialized-database-types* nil
47   "Contains a list of database types which have been initialized by calls
48 to initialize-database-type.")
49
50 (defun initialize-database-type (&key (database-type *default-database-type*))
51   "Initialize the given database-type, if it is not already
52 initialized, as indicated by `*initialized-database-types*'."
53   (if (member database-type *initialized-database-types*)
54       t
55       (when (database-initialize-database-type database-type)
56         (push database-type *initialized-database-types*)
57         t)))
58
59
60 ;;; Database handling
61
62 (defvar *connect-if-exists* :error
63   "Default value for the if-exists parameter of connect calls.")
64
65 (defvar *connected-databases* nil
66   "List of active database objects.")
67
68 (defun connected-databases ()
69   "Return the list of active database objects."
70   *connected-databases*)
71
72 (defvar *default-database* nil
73   "Specifies the default database to be used.")
74
75
76
77 (defun find-database (database &optional (errorp t))
78   (etypecase database
79     (database
80      ;; Return the database object itself
81      database)
82     (string
83      (or (find database (connected-databases)
84                :key #'database-name
85                :test #'string=)
86          (when errorp
87            (cerror "Return nil."
88                    'clsql-simple-error
89                    :format-control "There exists no database called ~A."
90                    :format-arguments (list database)))))))
91
92 (defun connect (connection-spec
93                 &key (if-exists *connect-if-exists*)
94                 (database-type *default-database-type*))
95   "Connects to a database of the given database-type, using the type-specific
96 connection-spec.  if-exists is currently ignored."
97   (let* ((db-name (database-name-from-spec connection-spec database-type))
98          (old-db (find-database db-name nil))
99          (result nil))
100     (if old-db
101         (case if-exists
102           (:new
103            (setq result
104                  (database-connect connection-spec database-type)))
105           (:warn-new
106            (setq result
107                  (database-connect connection-spec database-type))
108            (warn 'clsql-exists-warning :old-db old-db :new-db result))
109           (:error
110            (restart-case
111                (error 'clsql-exists-error :old-db old-db)
112              (create-new ()
113                :report "Create a new connection."
114                (setq result
115                      (database-connect connection-spec database-type)))
116              (use-old ()
117                :report "Use the existing connection."
118                (setq result old-db))))
119           (:warn-old
120            (setq result old-db)
121            (warn 'clsql-exists-warning :old-db old-db :new-db old-db))
122           (:old
123            (setq result old-db)))
124         (setq result
125               (database-connect connection-spec database-type)))
126     (when result
127       (pushnew result *connected-databases*)
128       (setq *default-database* result)
129       result)))
130
131
132
133 (defun disconnect (&key (database *default-database*))
134   "Closes the connection to database. Resets *default-database* if that
135 database was disconnected and only one other connection exists."
136   (when (database-disconnect database)
137     (setq *connected-databases* (delete database *connected-databases*))
138     (when (eq database *default-database*)
139       (setq *default-database* (car *connected-databases*)))
140     (change-class database 'closed-database)
141     t))
142
143
144
145 ;;; Basic operations on databases
146
147 (defmethod query (query-expression &key (database *default-database*)  
148                   types)
149   "Execute the SQL query expression query-expression on the given database.
150 Returns a list of lists of values of the result of that expression."
151   (database-query query-expression database types))
152
153
154
155 (defmethod execute-command (sql-expression &key (database *default-database*))
156   "Execute the SQL command expression sql-expression on the given database.
157 Returns true on success or nil on failure."
158   (database-execute-command sql-expression database))
159
160
161
162 (defun map-query (output-type-spec function query-expression
163                   &key (database *default-database*)
164                   (types nil))
165   "Map the function over all tuples that are returned by the query in
166 query-expression.  The results of the function are collected as
167 specified in output-type-spec and returned like in MAP."
168   ;; DANGER Will Robinson: Parts of the code for implementing
169   ;; map-query (including the code below and the helper functions
170   ;; called) are highly CMU CL specific.
171   ;; KMR -- these have been replaced with cross-platform instructions above
172   (macrolet ((type-specifier-atom (type)
173                `(if (atom ,type) ,type (car ,type))))
174     (case (type-specifier-atom output-type-spec)
175       ((nil) 
176        (map-query-for-effect function query-expression database types))
177       (list 
178        (map-query-to-list function query-expression database types))
179       ((simple-vector simple-string vector string array simple-array
180         bit-vector simple-bit-vector base-string
181         simple-base-string)
182        (map-query-to-simple output-type-spec function query-expression database types))
183       (t
184        (funcall #'map-query (cmucl-compat:result-type-or-lose output-type-spec t)
185               function query-expression :database database :types types)))))
186
187 (defun map-query-for-effect (function query-expression database types)
188   (multiple-value-bind (result-set columns)
189       (database-query-result-set query-expression database :full-set nil
190                                  :types types)
191     (when result-set
192       (unwind-protect
193            (do ((row (make-list columns)))
194                ((not (database-store-next-row result-set database row))
195                 nil)
196              (apply function row))
197         (database-dump-result-set result-set database)))))
198                      
199 (defun map-query-to-list (function query-expression database types)
200   (multiple-value-bind (result-set columns)
201       (database-query-result-set query-expression database :full-set nil
202                                  :types types)
203     (when result-set
204       (unwind-protect
205            (let ((result (list nil)))
206              (do ((row (make-list columns))
207                   (current-cons result (cdr current-cons)))
208                  ((not (database-store-next-row result-set database row))
209                   (cdr result))
210                (rplacd current-cons (list (apply function row)))))
211         (database-dump-result-set result-set database)))))
212
213
214 (defun map-query-to-simple (output-type-spec function query-expression database types)
215   (multiple-value-bind (result-set columns rows)
216       (database-query-result-set query-expression database :full-set t
217                                  :types types)
218     (when result-set
219       (unwind-protect
220            (if rows
221                ;; We know the row count in advance, so we allocate once
222                (do ((result
223                      (cmucl-compat:make-sequence-of-type output-type-spec rows))
224                     (row (make-list columns))
225                     (index 0 (1+ index)))
226                    ((not (database-store-next-row result-set database row))
227                     result)
228                  (declare (fixnum index))
229                  (setf (aref result index)
230                        (apply function row)))
231                ;; Database can't report row count in advance, so we have
232                ;; to grow and shrink our vector dynamically
233                (do ((result
234                      (cmucl-compat:make-sequence-of-type output-type-spec 100))
235                     (allocated-length 100)
236                     (row (make-list columns))
237                     (index 0 (1+ index)))
238                    ((not (database-store-next-row result-set database row))
239                     (cmucl-compat:shrink-vector result index))
240                  (declare (fixnum allocated-length index))
241                  (when (>= index allocated-length)
242                    (setq allocated-length (* allocated-length 2)
243                          result (adjust-array result allocated-length)))
244                  (setf (aref result index)
245                        (apply function row))))
246         (database-dump-result-set result-set database)))))
247
248 (defmacro do-query (((&rest args) query-expression
249                      &key (database '*default-database*)
250                      (types nil))
251                     &body body)
252   (let ((result-set (gensym))
253         (columns (gensym))
254         (row (gensym))
255         (db (gensym)))
256     `(let ((,db ,database))
257        (multiple-value-bind (,result-set ,columns)
258            (database-query-result-set ,query-expression ,db
259                                       :full-set nil :types ,types)
260          (when ,result-set
261            (unwind-protect
262                 (do ((,row (make-list ,columns)))
263                     ((not (database-store-next-row ,result-set ,db ,row))
264                      nil)
265                   (destructuring-bind ,args ,row
266                     ,@body))
267              (database-dump-result-set ,result-set ,db)))))))
268
269
270