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