r8873: better generic function
[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 and 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 ;;;; 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                      :sqlite-db (sqlite:sqlite-open (first connection-spec)))
45     (sqlite:sqlite-error (err)
46       (error 'clsql-connect-error
47              :database-type database-type
48              :connection-spec connection-spec
49              :errno (sqlite:sqlite-error-code err)
50              :error (sqlite:sqlite-error-message err)))))
51
52 (defmethod database-disconnect ((database sqlite-database))
53   (sqlite:sqlite-close (sqlite-db database))
54   (setf (sqlite-db database) nil)
55   t)
56
57 (defmethod database-execute-command (sql-expression (database sqlite-database))
58   (handler-case
59       (multiple-value-bind (data row-n col-n)
60           (sqlite:sqlite-get-table (sqlite-db database) sql-expression)
61         #+clisp (declare (ignore data))
62         #-clisp (sqlite:sqlite-free-table data)
63         (unless (= row-n 0)
64           (error 'clsql-simple-warning
65                  :format-control
66                  "Result set not empty: ~@(~A~) row~:P, ~@(~A~) column~:P "
67                  :format-arguments (list row-n col-n))))
68     (sqlite:sqlite-error (err)
69       (error 'clsql-sql-error
70              :database database
71              :expression sql-expression
72              :errno (sqlite:sqlite-error-code err)
73              :error (sqlite:sqlite-error-message err))))
74   t)
75
76 (defmethod database-query (query-expression (database sqlite-database) types)
77   (declare (ignore types))              ; SQLite is typeless!
78   (handler-case
79       (multiple-value-bind (data row-n col-n)
80           (sqlite:sqlite-get-table (sqlite-db database) query-expression)
81         #-clisp (declare (type sqlite:sqlite-row-pointer data))
82         (if (= row-n 0)
83             nil
84             (prog1
85                 ;; The first col-n elements are column names.
86                 (loop for i from col-n below (* (1+ row-n) col-n) by col-n
87                       collect (loop for j from 0 below col-n
88                                     collect
89                                     (#+clisp aref
90                                      #-clisp sqlite:sqlite-aref
91                                              data (+ i j))))
92                 #-clisp (sqlite:sqlite-free-table data))
93               ))
94     (sqlite:sqlite-error (err)
95       (error 'clsql-sql-error
96              :database database
97              :expression query-expression
98              :errno (sqlite:sqlite-error-code err)
99              :error (sqlite:sqlite-error-message err)))))
100
101 #-clisp
102 (defstruct sqlite-result-set
103   (vm (sqlite:make-null-vm)
104       :type sqlite:sqlite-vm-pointer)
105   (first-row (sqlite:make-null-row)
106              :type sqlite:sqlite-row-pointer)
107   (n-col 0 :type fixnum))
108 #+clisp
109 (defstruct sqlite-result-set
110   (vm nil)
111   (first-row nil)
112   (n-col 0 :type fixnum))
113
114 (defmethod database-query-result-set
115     ((query-expression string) (database sqlite-database) &key full-set types)
116   (declare (ignore full-set types))
117   (handler-case
118       (let* ((vm (sqlite:sqlite-compile (sqlite-db database)
119                                         query-expression))
120              (result-set (make-sqlite-result-set :vm vm)))
121         #-clisp (declare (type sqlite:sqlite-vm-pointer vm))
122
123         ;;; To obtain column number we have to read the first row.
124         (multiple-value-bind (n-col cols col-names)
125             (sqlite:sqlite-step vm)
126           (declare (ignore col-names)
127                    #-clisp (type sqlite:sqlite-row-pointer cols)
128                    )
129           (setf (sqlite-result-set-first-row result-set) cols
130                 (sqlite-result-set-n-col result-set) n-col)
131           (values result-set n-col nil)))
132     (sqlite:sqlite-error (err)
133       (error 'clsql-sql-error
134              :database database
135              :expression query-expression
136              :errno (sqlite:sqlite-error-code err)
137              :error (sqlite:sqlite-error-message err)))))
138
139 (defmethod database-dump-result-set (result-set (database sqlite-database))
140   (declare (ignore database))
141   (handler-case
142       (sqlite:sqlite-finalize (sqlite-result-set-vm result-set))
143     (sqlite:sqlite-error (err)
144       (error 'clsql-simple-error
145              :format-control "Error finalizing SQLite VM: ~A"
146              :format-arguments (list (sqlite:sqlite-error-message err))))))
147
148 (defmethod database-store-next-row (result-set (database sqlite-database) list)
149   (let ((n-col (sqlite-result-set-n-col result-set)))
150     (if (= n-col 0)
151         ;; empty result set
152         nil
153         (let ((row (sqlite-result-set-first-row result-set)))
154           (if (sqlite:null-row-p row)
155               ;; First row already used. fetch another row from DB.
156               (handler-case
157                   (multiple-value-bind (n new-row col-names)
158                       (sqlite:sqlite-step (sqlite-result-set-vm result-set))
159                     (declare (ignore n col-names)
160                              #-clisp (type sqlite:sqlite-row-pointer new-row)
161                              )
162                     (if (sqlite:null-row-p new-row)
163                         (return-from database-store-next-row nil)
164                         (setf row new-row)))
165                 (sqlite:sqlite-error (err)
166                   (error 'clsql-simple-error
167                          :format-control "Error in sqlite-step: ~A"
168                          :format-arguments
169                          (list (sqlite:sqlite-error-message err)))))
170
171               ;; Use the row previously read by database-query-result-set.
172               (setf (sqlite-result-set-first-row result-set)
173                     (sqlite:make-null-row)))
174           (loop for i = 0 then (1+ i)
175                 for rest on list
176                 do (setf (car rest)
177                          (#+clisp aref
178                           #-clisp sqlite:sqlite-aref
179                           row i)))
180           #-clisp (sqlite:sqlite-free-row row)
181           t))))
182
183 ;;; Object listing
184
185 (defmethod database-list-tables ((database sqlite-database) &key owner)
186   (declare (ignore owner))
187   ;; Query is copied from .table command of sqlite comamnd line utility.
188   (remove-if #'(lambda (s)
189                  (and (>= (length s) 10)
190                       (string= (subseq s 0 10) "_clsql_seq_")))
191              (mapcar #'car (database-query
192                             "SELECT name FROM sqlite_master WHERE type='table' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='table' ORDER BY name"
193                             database '()))))
194
195 (defmethod database-list-views ((database sqlite-database)
196                                 &key (owner nil))
197   (declare (ignore owner))
198   (mapcar #'car (database-query
199                  "SELECT name FROM sqlite_master WHERE type='view' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='view' ORDER BY name"
200                  database nil)))
201
202 (defmethod database-list-indexes ((database sqlite-database)
203                                   &key (owner nil))
204   (declare (ignore owner))
205   (mapcar #'car (database-query
206                  "SELECT name FROM sqlite_master WHERE type='index' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='index' ORDER BY name"
207                  database nil)))
208
209 (declaim (inline sqlite-table-info))
210 (defun sqlite-table-info (table database)
211   (database-query (format nil "PRAGMA table_info('~A')" table)
212                           database '()))
213
214 (defmethod database-list-attributes (table (database sqlite-database)
215                                            &key (owner nil))
216   (declare (ignore owner))
217   (mapcar #'(lambda (table-info) (second table-info))
218           (sqlite-table-info table database)))
219
220 (defmethod database-attribute-type (attribute table 
221                                     (database sqlite-database)
222                                     &key (owner nil))
223   (declare (ignore owner))
224   (loop for field-info in (sqlite-table-info table database)
225         when (string= attribute (second field-info))
226         return (third field-info)))
227
228 (defun %sequence-name-to-table-name (sequence-name)
229   (concatenate 'string "_clsql_seq_" (sql-escape sequence-name)))
230
231 (defun %table-name-to-sequence-name (table-name)
232   (and (>= (length table-name) 10)
233        (string= (subseq table-name 0 10) "_clsql_seq_")
234        (subseq table-name 10)))
235
236 (defmethod database-create-sequence (sequence-name
237                                      (database sqlite-database))
238   (let ((table-name (%sequence-name-to-table-name sequence-name)))
239     (database-execute-command
240      (concatenate 'string "CREATE TABLE " table-name
241                   " (id INTEGER PRIMARY KEY)")
242      database)
243     (database-execute-command 
244      (format nil "INSERT INTO ~A VALUES (-1)" table-name)
245      database)))
246
247 (defmethod database-drop-sequence (sequence-name
248                                    (database sqlite-database))
249   (database-execute-command
250    (concatenate 'string "DROP TABLE "
251                 (%sequence-name-to-table-name sequence-name)) 
252    database))
253
254 (defmethod database-list-sequences ((database sqlite-database)
255                                     &key (owner nil))
256   (declare (ignore owner))
257   (mapcan #'(lambda (s)
258               (let ((sn (%table-name-to-sequence-name (car s))))
259                 (and sn (list sn))))
260           (database-query
261            "SELECT name FROM sqlite_master WHERE type='table' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='table' ORDER BY name"
262            database '())))
263
264 (defmethod database-sequence-next (sequence-name (database sqlite-database))
265   (let ((table-name (%sequence-name-to-table-name sequence-name)))
266     (database-execute-command
267      (format nil "UPDATE ~A SET id=(SELECT id FROM ~A)+1"
268              table-name table-name)
269      database)
270     (sqlite:sqlite-last-insert-rowid (sqlite-db database))
271     (parse-integer
272      (caar (database-query (format nil "SELECT id from ~A" table-name)
273                            database nil)))))
274
275 (defmethod database-set-sequence-position (sequence-name
276                                            (position integer)
277                                            (database sqlite-database))
278   (let ((table-name (%sequence-name-to-table-name sequence-name)))
279     (database-execute-command
280      (format nil "UPDATE ~A SET id=~A" table-name position)
281      database)
282     (sqlite:sqlite-last-insert-rowid (sqlite-db database))))
283
284 (defmethod database-sequence-last (sequence-name (database sqlite-database))
285   (declare (ignore sequence-name database)))