4f953d013212fc5d63d9dd02026745c7bc8ecb8d
[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 & Kevin Rosenberg
8 ;;;; Created:  Oct 2004
9 ;;;;
10 ;;;; This file, part of CLSQL, is Copyright (c) 2004-2010 by Aurelio Bignoli & Kevin Rosenberg
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   (princ-to-string (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                               (car types)
172                               :length (sqlite3:sqlite3-column-bytes stmt i)
173                               :encoding (encoding database))
174                              (clsql-uffi:convert-raw-field
175                               (sqlite3:sqlite3-column-text stmt i)
176                               (car types)
177                               :encoding (encoding database)))))
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                                              (car types)
207                                              :length (sqlite3:sqlite3-column-bytes stmt i)
208                                              :encoding (or (encoding database)
209                                                            :utf-8))
210                                             (clsql-uffi:convert-raw-field
211                                              (sqlite3:sqlite3-column-text stmt i)
212                                              (car types)
213                                              :encoding (or (encoding database)
214                                                            :utf-8))))))
215                    (when field-names
216                      (setf col-names (loop for n from 0 below n-col
217                                            collect (sqlite3:sqlite3-column-name stmt n))))
218                    (push (extract-row-data) rows)
219                    (do* () (nil)
220                      (if (sqlite3:sqlite3-step stmt)
221                          (push (extract-row-data) rows)
222                          (return))))))
223                (sqlite3:sqlite3-finalize stmt))
224         (values (nreverse rows) col-names))
225     (sqlite3:sqlite3-error (err)
226       (error 'sql-database-data-error
227              :database database
228              :expression query-expression
229              :error-id (sqlite3:sqlite3-error-code err)
230              :message (sqlite3:sqlite3-error-message err)))))
231
232 ;;; Object listing
233
234 (defmethod database-list-tables-and-sequences ((database sqlite3-database) &key owner)
235   (declare (ignore owner))
236   ;; Query is copied from .table command of sqlite3 command line utility.
237   (mapcar #'car (database-query
238                  "SELECT name FROM sqlite_master WHERE type='table' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='table' ORDER BY name"
239                  database nil nil)))
240
241 (defmethod database-list-tables ((database sqlite3-database) &key owner)
242   (remove-if #'(lambda (s)
243                  (and (>= (length s) 11)
244                       (string-equal (subseq s 0 11) "_CLSQL_SEQ_")))
245              (database-list-tables-and-sequences database :owner owner)))
246
247 (defmethod database-list-views ((database sqlite3-database)
248                                 &key (owner nil))
249   (declare (ignore owner))
250   (mapcar #'car (database-query
251                  "SELECT name FROM sqlite_master WHERE type='view' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='view' ORDER BY name"
252                  database nil nil)))
253
254 (defmethod database-list-indexes ((database sqlite3-database)
255                                   &key (owner nil))
256   (declare (ignore owner))
257   (mapcar #'car (database-query
258                  "SELECT name FROM sqlite_master WHERE type='index' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='index' ORDER BY name"
259                  database nil nil)))
260
261 (defmethod database-list-table-indexes (table (database sqlite3-database)
262                                         &key (owner nil))
263   (declare (ignore owner))
264   (let ((*print-circle* nil))
265     (mapcar #'car
266             (database-query
267              (format
268               nil
269               "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"
270               table table)
271              database nil nil))))
272
273 (declaim (inline sqlite3-table-info))
274 (defun sqlite3-table-info (table database)
275   (let ((sql (format nil "PRAGMA table_info('~A')"
276                      (clsql-sys::unescaped-database-identifier table))))
277     (database-query sql database nil nil)))
278
279 (defmethod database-list-attributes (table (database sqlite3-database)
280                                            &key (owner nil))
281   (declare (ignore owner))
282   (mapcar #'(lambda (table-info) (second table-info))
283           (sqlite3-table-info table database)))
284
285 (defmethod database-attribute-type (attribute table
286                                     (database sqlite3-database)
287                                     &key (owner nil))
288   (declare (ignore owner))
289   
290   (loop for field-info in (sqlite3-table-info table database)
291       when (string= (clsql-sys::unescaped-database-identifier attribute)
292                     (second field-info))
293       return
294         (let* ((raw-type (third field-info))
295                (start-length (position #\( raw-type))
296                (type (string-trim clsql-sys::+whitespace-chars+
297                                   (if start-length
298                                       (subseq raw-type 0 start-length)
299                                       raw-type)))
300                (length (if start-length
301                            (parse-integer (subseq raw-type (1+ start-length))
302                                           :junk-allowed t)
303                          nil)))
304           (values (when type (ensure-keyword type))
305                   length
306                   nil
307                   (if (string-equal (fourth field-info) "0")
308                       1 0)))))
309
310 (defmethod database-last-auto-increment-id ((database sqlite3-database) table column)
311   (declare (ignore table column))
312   (car (query "SELECT LAST_INSERT_ROWID();"
313               :flatp t :field-names nil
314               :database database)))
315
316 (defmethod database-create (connection-spec (type (eql :sqlite3)))
317   (declare (ignore connection-spec))
318   ;; databases are created automatically by Sqlite3
319   t)
320
321 (defmethod database-destroy (connection-spec (type (eql :sqlite3)))
322   (destructuring-bind (name) connection-spec
323     (if (probe-file name)
324         (delete-file name)
325         nil)))
326
327 (defmethod database-probe (connection-spec (type (eql :sqlite3)))
328   (destructuring-bind (name) connection-spec
329     ;; TODO: Add a test that this file is a real sqlite3 database
330     (or (string-equal ":memory:" name)
331         (and (probe-file name) t))))
332
333 (defmethod database-get-type-specifier ((type (eql 'integer))
334                                         args database
335                                         (db-type (eql :sqlite3)))
336   (declare (ignore database))
337   (if args
338       (format nil "INTEGER(~A)" (car args))
339       "INTEGER"))
340
341 (defmethod database-get-type-specifier ((type (eql 'integer))
342                                         args database
343                                         (db-type (eql :sqlite3)))
344   (declare (ignore database))
345   (if args
346       (format nil "INTEGER(~A)" (car args))
347       "INTEGER"))
348
349 ;;; Database capabilities
350
351 (defmethod db-type-has-boolean-where? ((db-type (eql :sqlite3)))
352   nil)
353
354 (defmethod db-type-has-auto-increment? ((db-type (eql :sqlite3)))
355   t)