r8942: add :query to sql recording, support describe-table
[clsql.git] / base / basic-sql.lisp
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;;
4 ;;;; $Id$
5 ;;;;
6 ;;;; Base SQL 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-base-sys)
16
17 ;;; Query
18
19 (defgeneric query (query-expression &key database result-types flatp)
20   (:documentation
21    "Execute the SQL query expression QUERY-EXPRESSION on the given
22 DATABASE which defaults to *default-database*. RESULT-TYPES is a list
23 of symbols such as :string and :integer, one for each field in the
24 query, which are used to specify the types to return. The FLATP
25 argument, which has a default value of nil, specifies if full
26 bracketed results should be returned for each matched entry. If FLATP
27 is nil, the results are returned as a list of lists. If FLATP is t,
28 the results are returned as elements of a list, only if there is only
29 one result per row. Returns a list of lists of values of the result of
30 that expression and a list of field names selected in sql-exp."))
31
32 (defmethod query ((query-expression string) &key (database *default-database*)
33                   (result-types nil) (flatp nil))
34   (record-sql-action query-expression :query database)
35   (let* ((res (database-query query-expression database result-types))
36          (res (if (and flatp (= (length
37                                  (slot-value query-expression 'selections))
38                                 1))
39                   (mapcar #'car res)
40                   res)))
41     (record-sql-action res :result database)
42     res))
43
44 ;;; Execute
45
46 (defgeneric execute-command (expression &key database)
47   (:documentation
48    "Executes the SQL command specified by EXPRESSION for the database
49 specified by DATABASE, which has a default value of
50 *DEFAULT-DATABASE*. The argument EXPRESSION may be any SQL statement
51 other than a query. To run a stored procedure, pass an appropriate
52 string. The call to the procedure needs to be wrapped in a BEGIN END
53 pair."))
54
55 (defmethod execute-command ((sql-expression string)
56                             &key (database *default-database*))
57   (record-sql-action sql-expression :command database)
58   (let ((res (database-execute-command sql-expression database)))
59     (record-sql-action res :result database))
60   (values))
61
62
63 (defun describe-table (table &key (database *default-database*))
64   "Return list of 2-element lists containing table name and type."
65   (database-describe-table database table))
66
67 (defmacro do-query (((&rest args) query-expression
68                      &key (database '*default-database*) (result-types nil))
69                     &body body)
70   "Repeatedly executes BODY within a binding of ARGS on the attributes
71 of each record resulting from QUERY. The return value is determined by
72 the result of executing BODY. The default value of DATABASE is
73 *DEFAULT-DATABASE*."
74   (let ((result-set (gensym))
75         (columns (gensym))
76         (row (gensym))
77         (db (gensym)))
78     `(let ((,db ,database))
79       (multiple-value-bind (,result-set ,columns)
80           (database-query-result-set ,query-expression ,db
81                                      :full-set nil :result-types ,result-types)
82         (when ,result-set
83           (unwind-protect
84                (do ((,row (make-list ,columns)))
85                    ((not (database-store-next-row ,result-set ,db ,row))
86                     nil)
87                  (destructuring-bind ,args ,row
88                    ,@body))
89             (database-dump-result-set ,result-set ,db)))))))
90
91 (defun map-query (output-type-spec function query-expression
92                   &key (database *default-database*)
93                   (result-types nil))
94   "Map the function over all tuples that are returned by the query in
95 query-expression.  The results of the function are collected as
96 specified in output-type-spec and returned like in MAP."
97   (macrolet ((type-specifier-atom (type)
98                `(if (atom ,type) ,type (car ,type))))
99     (case (type-specifier-atom output-type-spec)
100       ((nil) 
101        (map-query-for-effect function query-expression database result-types))
102       (list 
103        (map-query-to-list function query-expression database result-types))
104       ((simple-vector simple-string vector string array simple-array
105         bit-vector simple-bit-vector base-string
106         simple-base-string)
107        (map-query-to-simple output-type-spec function query-expression database result-types))
108       (t
109        (funcall #'map-query (cmucl-compat:result-type-or-lose output-type-spec t)
110               function query-expression :database database :result-types result-types)))))
111
112 (defun map-query-for-effect (function query-expression database result-types)
113   (multiple-value-bind (result-set columns)
114       (database-query-result-set query-expression database :full-set nil
115                                  :result-types result-types)
116     (when result-set
117       (unwind-protect
118            (do ((row (make-list columns)))
119                ((not (database-store-next-row result-set database row))
120                 nil)
121              (apply function row))
122         (database-dump-result-set result-set database)))))
123                      
124 (defun map-query-to-list (function query-expression database result-types)
125   (multiple-value-bind (result-set columns)
126       (database-query-result-set query-expression database :full-set nil
127                                  :result-types result-types)
128     (when result-set
129       (unwind-protect
130            (let ((result (list nil)))
131              (do ((row (make-list columns))
132                   (current-cons result (cdr current-cons)))
133                  ((not (database-store-next-row result-set database row))
134                   (cdr result))
135                (rplacd current-cons (list (apply function row)))))
136         (database-dump-result-set result-set database)))))
137
138
139 (defun map-query-to-simple (output-type-spec function query-expression database result-types)
140   (multiple-value-bind (result-set columns rows)
141       (database-query-result-set query-expression database :full-set t
142                                  :result-types result-types)
143     (when result-set
144       (unwind-protect
145            (if rows
146                ;; We know the row count in advance, so we allocate once
147                (do ((result
148                      (cmucl-compat:make-sequence-of-type output-type-spec rows))
149                     (row (make-list columns))
150                     (index 0 (1+ index)))
151                    ((not (database-store-next-row result-set database row))
152                     result)
153                  (declare (fixnum index))
154                  (setf (aref result index)
155                        (apply function row)))
156                ;; Database can't report row count in advance, so we have
157                ;; to grow and shrink our vector dynamically
158                (do ((result
159                      (cmucl-compat:make-sequence-of-type output-type-spec 100))
160                     (allocated-length 100)
161                     (row (make-list columns))
162                     (index 0 (1+ index)))
163                    ((not (database-store-next-row result-set database row))
164                     (cmucl-compat:shrink-vector result index))
165                  (declare (fixnum allocated-length index))
166                  (when (>= index allocated-length)
167                    (setq allocated-length (* allocated-length 2)
168                          result (adjust-array result allocated-length)))
169                  (setf (aref result index)
170                        (apply function row))))
171         (database-dump-result-set result-set database)))))
172
173
174