r9736: fix bug preventing the :DB-CONSTRAINTS View Class slot option accepting a...
[clsql.git] / db-sqlite / sqlite-sql.lisp
1 ;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
2 ;;;; *************************************************************************
3 ;;;; FILE IDENTIFICATION
4 ;;;;
5 ;;;; Name:     sqlite-sql.lisp
6 ;;;; Purpose:  High-level SQLite interface
7 ;;;; Authors:  Aurelio Bignoli, Kevin Rosenberg, Marcus Pearce
8 ;;;; Created:  Aug 2003
9 ;;;;
10 ;;;; $Id$
11 ;;;;
12 ;;;; This file, part of CLSQL, is Copyright (c) 2003 by Aurelio Bignoli and
13 ;;;; Copyright (c) 2003-2004 by Kevin Rosenberg and Marcus Pearce.
14 ;;;;
15 ;;;; CLSQL users are granted the rights to distribute and use this software
16 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
17 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
18 ;;;; *************************************************************************
19
20 (in-package #:clsql-sqlite)
21
22 (defclass sqlite-database (database)
23   ((sqlite-db :initarg :sqlite-db :accessor sqlite-db)))
24
25 (defmethod database-type ((database sqlite-database))
26   :sqlite)
27
28 (defmethod database-initialize-database-type ((database-type (eql :sqlite)))
29   t)
30
31 (defun check-sqlite-connection-spec (connection-spec)
32   (check-connection-spec connection-spec :sqlite (name)))
33
34 (defmethod database-name-from-spec (connection-spec
35                                     (database-type (eql :sqlite)))
36   (check-sqlite-connection-spec connection-spec)
37   (first connection-spec))
38
39 (defmethod database-connect (connection-spec (database-type (eql :sqlite)))
40   (check-sqlite-connection-spec connection-spec)
41   (handler-case
42       (make-instance 'sqlite-database
43                      :name (database-name-from-spec connection-spec :sqlite)
44                      :database-type :sqlite
45                      :connection-spec connection-spec
46                      :sqlite-db (sqlite:sqlite-open (first connection-spec)))
47     (sqlite:sqlite-error (err)
48       (error 'sql-connection-error
49              :database-type database-type
50              :connection-spec connection-spec
51              :error-id (sqlite:sqlite-error-code err)
52              :message (sqlite:sqlite-error-message err)))))
53
54 (defmethod database-disconnect ((database sqlite-database))
55   (sqlite:sqlite-close (sqlite-db database))
56   (setf (sqlite-db database) nil)
57   t)
58
59 (defmethod database-execute-command (sql-expression (database sqlite-database))
60   (handler-case
61       (multiple-value-bind (data row-n col-n)
62           (sqlite:sqlite-get-table (sqlite-db database) sql-expression)
63         (sqlite:sqlite-free-table data)
64         (unless (= row-n 0)
65           (error 'sql-warning
66                  :format-control
67                  "Result set not empty: ~@(~A~) row~:P, ~@(~A~) column~:P "
68                  :format-arguments (list row-n col-n))))
69     (sqlite:sqlite-error (err)
70       (error 'sql-database-data-error
71              :database database
72              :expression sql-expression
73              :error-id (sqlite:sqlite-error-code err)
74              :message (sqlite:sqlite-error-message err))))
75   t)
76
77 (defstruct sqlite-result-set
78   (vm (sqlite:make-null-vm)
79       :type sqlite:sqlite-vm-pointer)
80   (first-row (sqlite:make-null-row)
81              :type sqlite:sqlite-row-pointer-type)
82   (col-names (sqlite:make-null-row)
83              :type sqlite:sqlite-row-pointer-type)
84   (result-types nil)
85   (n-col 0 :type fixnum))
86
87 (defmethod database-query (query-expression (database sqlite-database) result-types field-names)
88   (declare (optimize (speed 3) (safety 0) (debug 0) (space 0)))
89   (handler-case
90       (multiple-value-bind (result-set n-col)
91           (database-query-result-set query-expression database
92                                      :result-types result-types
93                                      :full-set nil)
94         (do* ((rows nil)
95               (col-names (when field-names
96                            (loop for j from 0 below n-col
97                                  collect (sqlite:sqlite-aref (sqlite-result-set-col-names result-set) j))))
98               (new-row (make-list n-col) (make-list n-col))
99               (row-ok (database-store-next-row result-set database new-row)
100                       (database-store-next-row result-set database new-row)))
101              ((not row-ok)
102               (values (nreverse rows) col-names))
103           (push new-row rows)))
104     (sqlite:sqlite-error (err)
105       (error 'sql-database-data-error
106              :database database
107              :expression query-expression
108              :error-id (sqlite:sqlite-error-code err)
109              :message (sqlite:sqlite-error-message err)))))
110
111 (defmethod database-query-result-set ((query-expression string)
112                                       (database sqlite-database)
113                                       &key result-types full-set)
114   (handler-case
115       (let ((vm (sqlite:sqlite-compile (sqlite-db database)
116                                        query-expression)))
117         ;;; To obtain column number/datatypes we have to read the first row.
118         (multiple-value-bind (n-col cols col-names)
119             (sqlite:sqlite-step vm)
120           (let ((result-set (make-sqlite-result-set
121                              :vm vm
122                              :first-row cols
123                              :n-col n-col
124                              :col-names col-names
125                              :result-types
126                              (canonicalize-result-types
127                               result-types
128                               n-col
129                               col-names))))
130             (if full-set
131                 (values result-set n-col nil)
132                 (values result-set n-col)))))
133     (sqlite:sqlite-error (err)
134       (error 'sql-database-error
135              :database database
136              :expression query-expression
137              :error-id (sqlite:sqlite-error-code err)
138              :message (sqlite:sqlite-error-message err)))))
139
140 (defun canonicalize-result-types (result-types n-col col-names)
141   (when result-types
142     (let ((raw-types (if (eq :auto result-types)
143                          (loop for j from n-col below (* 2 n-col)
144                                collect (ensure-keyword (sqlite:sqlite-aref col-names j)))
145                        result-types)))
146       (loop for type in raw-types
147             collect
148             (case type
149               ((:int :integer :tinyint :long)
150                :int32)
151               (:bigint
152                :int64)
153               ((:float :double)
154                :double)
155               ((:numeric)
156                :number)
157               (otherwise
158                :string))))))
159
160 (defmethod database-dump-result-set (result-set (database sqlite-database))
161   (handler-case
162       (sqlite:sqlite-finalize (sqlite-result-set-vm result-set))
163     (sqlite:sqlite-error (err)
164       (error 'sql-database-error
165              :message
166              (format nil "Error finalizing SQLite VM: ~A"
167                      (sqlite:sqlite-error-message err))))))
168
169 (defmethod database-store-next-row (result-set (database sqlite-database) list)
170   (let ((n-col (sqlite-result-set-n-col result-set))
171         (result-types (sqlite-result-set-result-types result-set)))
172     (if (= n-col 0)
173         ;; empty result set
174         nil
175         (let ((row (sqlite-result-set-first-row result-set)))
176           (if (sqlite:null-row-p row)
177               ;; First row already used. fetch another row from DB.
178               (handler-case
179                   (multiple-value-bind (n new-row col-names)
180                       (sqlite:sqlite-step (sqlite-result-set-vm result-set))
181                     (declare (ignore n col-names)
182                              (type sqlite:sqlite-row-pointer-type new-row))
183                     (if (sqlite:null-row-p new-row)
184                         (return-from database-store-next-row nil)
185                         (setf row new-row)))
186                 (sqlite:sqlite-error (err)
187                   (error 'sql-database-error
188                          :message
189                          (format nil "Error in sqlite-step: ~A"
190                                  (sqlite:sqlite-error-message err)))))
191
192             ;; Use the row previously read by database-query-result-set.
193               (setf (sqlite-result-set-first-row result-set)
194                     (sqlite:make-null-row)))
195           (loop for i = 0 then (1+ i)
196                 for rest on list
197                 do (setf (car rest)
198                          (clsql-uffi:convert-raw-field
199                           (uffi:deref-array
200                            (uffi:deref-pointer row 'sqlite:sqlite-row-pointer) '(:array (* :unsigned-char)) i)
201                           result-types
202                           i)))
203           (sqlite:sqlite-free-row row)
204           t))))
205
206 ;;; Object listing
207
208 (defmethod database-list-tables-and-sequences ((database sqlite-database) &key owner)
209   (declare (ignore owner))
210   ;; Query is copied from .table command of sqlite comamnd line utility.
211   (mapcar #'car (database-query
212                  "SELECT name FROM sqlite_master WHERE type='table' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='table' ORDER BY name"
213                  database nil nil)))
214
215 (defmethod database-list-tables ((database sqlite-database) &key owner)
216   (remove-if #'(lambda (s)
217                  (and (>= (length s) 11)
218                       (string-equal (subseq s 0 11) "_CLSQL_SEQ_")))
219              (database-list-tables-and-sequences database :owner owner)))
220
221 (defmethod database-list-views ((database sqlite-database)
222                                 &key (owner nil))
223   (declare (ignore owner))
224   (mapcar #'car (database-query
225                  "SELECT name FROM sqlite_master WHERE type='view' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='view' ORDER BY name"
226                  database nil nil)))
227
228 (defmethod database-list-indexes ((database sqlite-database)
229                                   &key (owner nil))
230   (declare (ignore owner))
231   (mapcar #'car (database-query
232                  "SELECT name FROM sqlite_master WHERE type='index' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='index' ORDER BY name"
233                  database nil nil)))
234
235 (defmethod database-list-table-indexes (table (database sqlite-database)
236                                         &key (owner nil))
237   (declare (ignore owner))
238   (let ((*print-circle* nil))
239     (mapcar #'car 
240             (database-query
241              (format
242               nil
243               "SELECT name FROM sqlite_master WHERE type='index' AND tbl_name='~A' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='index' AND tbl_name='~A' ORDER BY name"
244               table table)
245              database nil nil))))
246
247 (declaim (inline sqlite-table-info))
248 (defun sqlite-table-info (table database)
249   (database-query (format nil "PRAGMA table_info('~A')" table)
250                   database nil nil))
251
252 (defmethod database-list-attributes (table (database sqlite-database)
253                                            &key (owner nil))
254   (declare (ignore owner))
255   (mapcar #'(lambda (table-info) (second table-info))
256           (sqlite-table-info table database)))
257
258 (defmethod database-attribute-type (attribute table 
259                                     (database sqlite-database)
260                                     &key (owner nil))
261   (declare (ignore owner))
262   (loop for field-info in (sqlite-table-info table database)
263       when (string= attribute (second field-info))
264       return 
265         (let* ((raw-type (third field-info))
266                (start-length (position #\( raw-type))
267                (type (if start-length
268                          (subseq raw-type 0 start-length)
269                        raw-type))
270                (length (if start-length
271                            (parse-integer (subseq raw-type (1+ start-length))
272                                           :junk-allowed t)
273                          nil)))
274           (values (when type (ensure-keyword type)) 
275                   length
276                   nil
277                   (if (string-equal (fourth field-info) "0")
278                       1 0)))))
279
280 (defmethod database-create (connection-spec (type (eql :sqlite)))
281   (declare (ignore connection-spec))
282   ;; databases are created automatically by SQLite
283   t)
284
285 (defmethod database-destroy (connection-spec (type (eql :sqlite)))
286   (destructuring-bind (name) connection-spec
287     (if (probe-file name)
288         (delete-file name)
289         nil)))
290
291 (defmethod database-probe (connection-spec (type (eql :sqlite)))
292   (destructuring-bind (name) connection-spec
293     ;; TODO: Add a test that this file is a real sqlite database
294     (or (string-equal ":memory:" name)
295         (and (probe-file name) t))))
296
297 ;;; Database capabilities
298
299 (defmethod db-type-has-boolean-where? ((db-type (eql :sqlite)))
300   nil)
301
302
303