805597b0bd086a8faea3c2080d89616d3faaa2ce
[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) result-types)
77   (declare (ignore result-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 result-types)
116   (declare (ignore full-set result-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   (handler-case
141       (sqlite:sqlite-finalize (sqlite-result-set-vm result-set))
142     (sqlite:sqlite-error (err)
143       (error 'clsql-simple-error
144              :format-control "Error finalizing SQLite VM: ~A"
145              :format-arguments (list (sqlite:sqlite-error-message err))))))
146
147 (defmethod database-store-next-row (result-set (database sqlite-database) list)
148   (let ((n-col (sqlite-result-set-n-col result-set)))
149     (if (= n-col 0)
150         ;; empty result set
151         nil
152         (let ((row (sqlite-result-set-first-row result-set)))
153           (if (sqlite:null-row-p row)
154               ;; First row already used. fetch another row from DB.
155               (handler-case
156                   (multiple-value-bind (n new-row col-names)
157                       (sqlite:sqlite-step (sqlite-result-set-vm result-set))
158                     (declare (ignore n col-names)
159                              #-clisp (type sqlite:sqlite-row-pointer new-row)
160                              )
161                     (if (sqlite:null-row-p new-row)
162                         (return-from database-store-next-row nil)
163                         (setf row new-row)))
164                 (sqlite:sqlite-error (err)
165                   (error 'clsql-simple-error
166                          :format-control "Error in sqlite-step: ~A"
167                          :format-arguments
168                          (list (sqlite:sqlite-error-message err)))))
169
170               ;; Use the row previously read by database-query-result-set.
171               (setf (sqlite-result-set-first-row result-set)
172                     (sqlite:make-null-row)))
173           (loop for i = 0 then (1+ i)
174                 for rest on list
175                 do (setf (car rest)
176                          (#+clisp aref
177                           #-clisp sqlite:sqlite-aref
178                           row i)))
179           #-clisp (sqlite:sqlite-free-row row)
180           t))))
181
182 ;;; Object listing
183
184 (defmethod database-list-tables ((database sqlite-database) &key owner)
185   (declare (ignore owner))
186   ;; Query is copied from .table command of sqlite comamnd line utility.
187   (remove-if #'(lambda (s)
188                  (and (>= (length s) 11)
189                       (string= (subseq s 0 11) "_clsql_seq_")))
190              (mapcar #'car (database-query
191                             "SELECT name FROM sqlite_master WHERE type='table' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='table' ORDER BY name"
192                             database '()))))
193
194 (defmethod database-list-views ((database sqlite-database)
195                                 &key (owner nil))
196   (declare (ignore owner))
197   (mapcar #'car (database-query
198                  "SELECT name FROM sqlite_master WHERE type='view' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='view' ORDER BY name"
199                  database nil)))
200
201 (defmethod database-list-indexes ((database sqlite-database)
202                                   &key (owner nil))
203   (declare (ignore owner))
204   (mapcar #'car (database-query
205                  "SELECT name FROM sqlite_master WHERE type='index' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='index' ORDER BY name"
206                  database nil)))
207
208 (declaim (inline sqlite-table-info))
209 (defun sqlite-table-info (table database)
210   (database-query (format nil "PRAGMA table_info('~A')" table)
211                           database '()))
212
213 (defmethod database-list-attributes (table (database sqlite-database)
214                                            &key (owner nil))
215   (declare (ignore owner))
216   (mapcar #'(lambda (table-info) (second table-info))
217           (sqlite-table-info table database)))
218
219 (defmethod database-attribute-type (attribute table 
220                                     (database sqlite-database)
221                                     &key (owner nil))
222   (declare (ignore owner))
223   (loop for field-info in (sqlite-table-info table database)
224         when (string= attribute (second field-info))
225         return (third field-info)))
226
227 (defun %sequence-name-to-table-name (sequence-name)
228   (concatenate 'string "_clsql_seq_" (sql-escape sequence-name)))
229
230 (defun %table-name-to-sequence-name (table-name)
231   (and (>= (length table-name) 11)
232        (string= (subseq table-name 0 11) "_clsql_seq_")
233        (subseq table-name 11)))
234
235 (defmethod database-create-sequence (sequence-name
236                                      (database sqlite-database))
237   (let ((table-name (%sequence-name-to-table-name sequence-name)))
238     (database-execute-command
239      (concatenate 'string "CREATE TABLE " table-name
240                   " (id INTEGER PRIMARY KEY)")
241      database)
242     (database-execute-command 
243      (format nil "INSERT INTO ~A VALUES (-1)" table-name)
244      database)))
245
246 (defmethod database-drop-sequence (sequence-name
247                                    (database sqlite-database))
248   (database-execute-command
249    (concatenate 'string "DROP TABLE "
250                 (%sequence-name-to-table-name sequence-name)) 
251    database))
252
253 (defmethod database-list-sequences ((database sqlite-database)
254                                     &key (owner nil))
255   (declare (ignore owner))
256   (mapcan #'(lambda (s)
257               (let ((sn (%table-name-to-sequence-name (car s))))
258                 (and sn (list sn))))
259           (database-query
260            "SELECT name FROM sqlite_master WHERE type='table' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='table' ORDER BY name"
261            database '())))
262
263 (defmethod database-sequence-next (sequence-name (database sqlite-database))
264   (let ((table-name (%sequence-name-to-table-name sequence-name)))
265     (database-execute-command
266      (format nil "UPDATE ~A SET id=(SELECT id FROM ~A)+1"
267              table-name table-name)
268      database)
269     (sqlite:sqlite-last-insert-rowid (sqlite-db database))
270     (parse-integer
271      (caar (database-query (format nil "SELECT id from ~A" table-name)
272                            database nil)))))
273
274 (defmethod database-set-sequence-position (sequence-name
275                                            (position integer)
276                                            (database sqlite-database))
277   (let ((table-name (%sequence-name-to-table-name sequence-name)))
278     (database-execute-command
279      (format nil "UPDATE ~A SET id=~A" table-name position)
280      database)
281     (sqlite:sqlite-last-insert-rowid (sqlite-db database))))
282
283 (defmethod database-sequence-last (sequence-name (database sqlite-database))
284   (declare (ignore sequence-name)))