Remove CVS $Id$ keyword
[clsql.git] / db-sqlite3 / sqlite3-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 SQLite3 interface
7 ;;;; Authors:  Aurelio Bignoli
8 ;;;; Created:  Oct 2004
9 ;;;;
10 ;;;; This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli
11 ;;;;
12 ;;;; CLSQL users are granted the rights to distribute and use this software
13 ;;;; as governed by the terms of the Lisp Lesser GNU Public License
14 ;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
15 ;;;; *************************************************************************
16
17 (in-package #:clsql-sqlite3)
18
19 (defclass sqlite3-database (database)
20   ((sqlite3-db :initarg :sqlite3-db :accessor sqlite3-db)))
21
22 (defmethod database-type ((database sqlite3-database))
23   :sqlite3)
24
25 (defmethod database-initialize-database-type ((database-type (eql :sqlite3)))
26   t)
27
28 (defun check-sqlite3-connection-spec (connection-spec)
29   (check-connection-spec connection-spec :sqlite3 (name &optional init-foreign-func)))
30
31 (defmethod database-name-from-spec (connection-spec
32                                     (database-type (eql :sqlite3)))
33   (check-sqlite3-connection-spec connection-spec)
34   (first connection-spec))
35
36 (defmethod database-connect (connection-spec (database-type (eql :sqlite3)))
37   (check-sqlite3-connection-spec connection-spec)
38     (handler-case
39         (let ((db (sqlite3:sqlite3-open (first connection-spec)))
40               (init-foreign-func (second connection-spec)))
41           (declare (type sqlite3:sqlite3-db-type db))
42           (when init-foreign-func
43             (handler-case
44                 (funcall init-foreign-func db)
45               (condition (c)
46                 (progn
47                   (sqlite3:sqlite3-close db)
48                   (error c)))))
49           (make-instance 'sqlite3-database
50                          :name (database-name-from-spec connection-spec :sqlite3)
51                          :database-type :sqlite3
52                          :connection-spec connection-spec
53                          :sqlite3-db db))
54       (sqlite3:sqlite3-error (err)
55         (error 'sql-connection-error
56                :database-type database-type
57                :connection-spec connection-spec
58                :error-id (sqlite3:sqlite3-error-code err)
59                :message (sqlite3:sqlite3-error-message err)))))
60
61 (defmethod database-disconnect ((database sqlite3-database))
62   (sqlite3:sqlite3-close (sqlite3-db database))
63   (setf (sqlite3-db database) nil)
64   t)
65
66 (defmethod database-execute-command (sql-expression (database sqlite3-database))
67   (handler-case
68       (let ((stmt (sqlite3:sqlite3-prepare (sqlite3-db database) sql-expression)))
69         (declare (type sqlite3:sqlite3-stmt-type stmt))
70         (when stmt
71           (unwind-protect
72                (sqlite3:sqlite3-step stmt)
73             (sqlite3:sqlite3-finalize stmt))))
74     (sqlite3:sqlite3-error (err)
75       (error 'sql-database-data-error
76              :database database
77              :expression sql-expression
78              :error-id (sqlite3:sqlite3-error-code err)
79              :message (sqlite3:sqlite3-error-message err))))
80   t)
81
82 (defstruct sqlite3-result-set
83   (stmt sqlite3:null-stmt
84         :type sqlite3:sqlite3-stmt-type)
85   (n-col 0 :type fixnum)
86   (col-names '())
87   (result-types '()))
88
89 (declaim (ftype (function (sqlite3:sqlite3-stmt-type fixnum t) list) get-result-types))
90 (defun get-result-types (stmt n-col result-types)
91   (declare (type sqlite3:sqlite3-stmt-type stmt) (type fixnum n-col))
92   (if (eq :auto result-types)
93       (loop for n from 0 below n-col
94             collect (let ((column-type (sqlite3:sqlite3-column-type stmt n)))
95                       (cond
96                         ((= column-type sqlite3:SQLITE-INTEGER) :int64)
97                         ((= column-type sqlite3:SQLITE-FLOAT) :double)
98                         ((= column-type sqlite3:SQLITE-TEXT) :string)
99                         ((= column-type sqlite3:SQLITE-BLOB) :blob)
100                         ((= column-type sqlite3:SQLITE-NULL) :string)
101                         (t :string))))
102       (loop for type in result-types
103             collect (case type
104                       ((:int :integer :tinyint) :int32)
105                       (:long #+(or x86-64 64bit) :int64 #-(or x86-64 64bit) :int32)
106                       (:bigint :int64)
107                       ((:float :double) :double)
108                       ((:numeric) :number)
109                       (otherwise :string)))))
110
111 (defmethod database-query-result-set ((query-expression string)
112                                       (database sqlite3-database)
113                                       &key result-types full-set)
114   (let ((stmt sqlite3:null-stmt))
115     (declare (type sqlite3:sqlite3-stmt-type stmt))
116     (handler-case
117         (progn
118           (setf stmt (sqlite3:sqlite3-prepare (sqlite3-db database)
119                                               query-expression))
120           (let* ((n-col (if (sqlite3:sqlite3-step stmt)
121                             ;; Non empty result set.
122                             (sqlite3:sqlite3-column-count stmt)
123                             ;; Empty result set.
124                             0))
125                  (result-set (make-sqlite3-result-set
126                               :stmt stmt
127                               :n-col n-col
128                               :col-names (loop for n from 0 below n-col
129                                                collect (sqlite3:sqlite3-column-name stmt n))
130                               :result-types (when (> n-col 0)
131                                               (get-result-types stmt n-col result-types)))))
132             (if full-set
133                 (values result-set n-col nil)
134                 (values result-set n-col))))
135     (sqlite3:sqlite3-error (err)
136         (progn
137           (unless (eq stmt sqlite3:null-stmt)
138             (ignore-errors
139               (sqlite3:sqlite3-finalize stmt)))
140           (error 'sql-database-data-error
141                  :database database
142                  :expression query-expression
143                  :error-id (sqlite3:sqlite3-error-code err)
144                  :message (sqlite3:sqlite3-error-message err)))))))
145
146 (defmethod database-dump-result-set (result-set (database sqlite3-database))
147   (handler-case
148       (sqlite3:sqlite3-finalize (sqlite3-result-set-stmt result-set))
149     (sqlite3:sqlite3-error (err)
150       (error 'sql-database-error
151              :message
152              (format nil "Error finalizing SQLite3 statement: ~A"
153                      (sqlite3:sqlite3-error-message err))))))
154
155 (defmethod database-store-next-row (result-set (database sqlite3-database) list)
156   (let ((n-col (sqlite3-result-set-n-col result-set)))
157     (if (= n-col 0)
158         ;; empty result set.
159         nil
160         ;; Non-empty set.
161         (let ((stmt (sqlite3-result-set-stmt result-set)))
162           (declare (type sqlite3:sqlite3-stmt-type stmt))
163           ;; Store row in list.
164           (loop for i = 0 then (1+ i)
165                 for rest on list
166                 for types = (sqlite3-result-set-result-types result-set) then (rest types)
167                 do (setf (car rest)
168                          (if (eq (first types) :blob)
169                              (clsql-uffi:convert-raw-field
170                               (sqlite3:sqlite3-column-blob stmt i)
171                               types 0
172                               (sqlite3:sqlite3-column-bytes stmt i))
173                              (clsql-uffi:convert-raw-field
174                               (sqlite3:sqlite3-column-text stmt i)
175                               types 0))))
176           ;; Advance result set cursor.
177           (handler-case
178               (unless (sqlite3:sqlite3-step stmt)
179                 (setf (sqlite3-result-set-n-col result-set) 0))
180             (sqlite3:sqlite3-error (err)
181               (error 'sql-database-error
182                      :message (format nil "Error in sqlite3-step: ~A"
183                                       (sqlite3:sqlite3-error-message err)))))
184           t))))
185
186
187 (defmethod database-query (query-expression (database sqlite3-database) result-types field-names)
188   (declare (optimize (speed 3) (safety 0) (debug 0) (space 0)))
189   (handler-case
190       (let ((stmt (sqlite3:sqlite3-prepare (sqlite3-db database)
191                                            query-expression))
192             (rows '())
193             (col-names '()))
194         (declare (type sqlite3:sqlite3-stmt-type stmt))
195         (unwind-protect
196              (when (sqlite3:sqlite3-step stmt)
197                (let ((n-col (sqlite3:sqlite3-column-count stmt)))
198                  (flet ((extract-row-data ()
199                           (loop for i from 0 below n-col
200                                 for types = (get-result-types stmt n-col result-types) then (rest types)
201                                 collect (if (eq (first types) :blob)
202                                             (clsql-uffi:convert-raw-field
203                                              (sqlite3:sqlite3-column-blob stmt i)
204                                              types 0
205                                              (sqlite3:sqlite3-column-bytes stmt i))
206                                             (clsql-uffi:convert-raw-field
207                                              (sqlite3:sqlite3-column-text stmt i)
208                                              types 0)))))
209                    (when field-names
210                      (setf col-names (loop for n from 0 below n-col
211                                            collect (sqlite3:sqlite3-column-name stmt n))))
212                    (push (extract-row-data) rows)
213                    (do* () (nil)
214                      (if (sqlite3:sqlite3-step stmt)
215                          (push (extract-row-data) rows)
216                          (return))))))
217                (sqlite3:sqlite3-finalize stmt))
218         (values (nreverse rows) col-names))
219     (sqlite3:sqlite3-error (err)
220       (error 'sql-database-data-error
221              :database database
222              :expression query-expression
223              :error-id (sqlite3:sqlite3-error-code err)
224              :message (sqlite3:sqlite3-error-message err)))))
225
226 ;;; Object listing
227
228 (defmethod database-list-tables-and-sequences ((database sqlite3-database) &key owner)
229   (declare (ignore owner))
230   ;; Query is copied from .table command of sqlite3 command line utility.
231   (mapcar #'car (database-query
232                  "SELECT name FROM sqlite_master WHERE type='table' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='table' ORDER BY name"
233                  database nil nil)))
234
235 (defmethod database-list-tables ((database sqlite3-database) &key owner)
236   (remove-if #'(lambda (s)
237                  (and (>= (length s) 11)
238                       (string-equal (subseq s 0 11) "_CLSQL_SEQ_")))
239              (database-list-tables-and-sequences database :owner owner)))
240
241 (defmethod database-list-views ((database sqlite3-database)
242                                 &key (owner nil))
243   (declare (ignore owner))
244   (mapcar #'car (database-query
245                  "SELECT name FROM sqlite_master WHERE type='view' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='view' ORDER BY name"
246                  database nil nil)))
247
248 (defmethod database-list-indexes ((database sqlite3-database)
249                                   &key (owner nil))
250   (declare (ignore owner))
251   (mapcar #'car (database-query
252                  "SELECT name FROM sqlite_master WHERE type='index' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='index' ORDER BY name"
253                  database nil nil)))
254
255 (defmethod database-list-table-indexes (table (database sqlite3-database)
256                                         &key (owner nil))
257   (declare (ignore owner))
258   (let ((*print-circle* nil))
259     (mapcar #'car
260             (database-query
261              (format
262               nil
263               "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"
264               table table)
265              database nil nil))))
266
267 (declaim (inline sqlite3-table-info))
268 (defun sqlite3-table-info (table database)
269   (database-query (format nil "PRAGMA table_info('~A')" table)
270                   database nil nil))
271
272 (defmethod database-list-attributes (table (database sqlite3-database)
273                                            &key (owner nil))
274   (declare (ignore owner))
275   (mapcar #'(lambda (table-info) (second table-info))
276           (sqlite3-table-info table database)))
277
278 (defmethod database-attribute-type (attribute table
279                                     (database sqlite3-database)
280                                     &key (owner nil))
281   (declare (ignore owner))
282
283   (loop for field-info in (sqlite3-table-info table database)
284       when (string= attribute (second field-info))
285       return
286         (let* ((raw-type (third field-info))
287                (start-length (position #\( raw-type))
288                (type (string-trim clsql-sys::+whitespace-chars+
289                                   (if start-length
290                                       (subseq raw-type 0 start-length)
291                                       raw-type)))
292                (length (if start-length
293                            (parse-integer (subseq raw-type (1+ start-length))
294                                           :junk-allowed t)
295                          nil)))
296           (values (when type (ensure-keyword type))
297                   length
298                   nil
299                   (if (string-equal (fourth field-info) "0")
300                       1 0)))))
301
302 (defmethod database-create (connection-spec (type (eql :sqlite3)))
303   (declare (ignore connection-spec))
304   ;; databases are created automatically by Sqlite3
305   t)
306
307 (defmethod database-destroy (connection-spec (type (eql :sqlite3)))
308   (destructuring-bind (name) connection-spec
309     (if (probe-file name)
310         (delete-file name)
311         nil)))
312
313 (defmethod database-probe (connection-spec (type (eql :sqlite3)))
314   (destructuring-bind (name) connection-spec
315     ;; TODO: Add a test that this file is a real sqlite3 database
316     (or (string-equal ":memory:" name)
317         (and (probe-file name) t))))
318
319 ;;; Database capabilities
320
321 (defmethod db-type-has-boolean-where? ((db-type (eql :sqlite3)))
322   nil)