r9186: add attribute caching, improve inititialize-database-type
[clsql.git] / db-sqlite / sqlite-sql.lisp
index 9b67f380b25cc7f9a1f35a9b3e69e23eedc2e0a0..703eb94928ded58f5e9a54162530c1cfa5782384 100644 (file)
@@ -41,6 +41,8 @@
   (handler-case
       (make-instance 'sqlite-database
                     :name (database-name-from-spec connection-spec :sqlite)
+                    :database-type :sqlite
+                    :connection-spec connection-spec
                     :sqlite-db (sqlite:sqlite-open (first connection-spec)))
     (sqlite:sqlite-error (err)
       (error 'clsql-connect-error
             :error (sqlite:sqlite-error-message err))))
   t)
 
-(defmethod database-query (query-expression (database sqlite-database) result-types)
+(defmethod database-query (query-expression (database sqlite-database) result-types field-names)
   (declare (ignore result-types))              ; SQLite is typeless!
   (handler-case
       (multiple-value-bind (data row-n col-n)
          (sqlite:sqlite-get-table (sqlite-db database) query-expression)
        #-clisp (declare (type sqlite:sqlite-row-pointer-type data))
-       (if (= row-n 0)
-           nil
-           (prog1
-               ;; The first col-n elements are column names.
-               (loop for i from col-n below (* (1+ row-n) col-n) by col-n
-                     collect (loop for j from 0 below col-n
-                                   collect
-                                   (#+clisp aref
-                                    #-clisp sqlite:sqlite-aref
-                                            data (+ i j))))
-               #-clisp (sqlite:sqlite-free-table data))
-             ))
+       (let ((rows
+              (when (plusp row-n)
+                (loop for i from col-n below (* (1+ row-n) col-n) by col-n
+                    collect (loop for j from 0 below col-n
+                                collect
+                                  (#+clisp aref
+                                           #-clisp sqlite:sqlite-aref
+                                           data (+ i j))))))
+             (names
+              (when field-names
+                (loop for j from 0 below col-n
+                    collect (#+clisp aref
+                                     #-clisp sqlite:sqlite-aref
+                                     data j)))))
+         #-clisp (sqlite:sqlite-free-table data)
+         (values rows names)))
     (sqlite:sqlite-error (err)
-      (error 'clsql-sql-error
-            :database database
-            :expression query-expression
-            :errno (sqlite:sqlite-error-code err)
-            :error (sqlite:sqlite-error-message err)))))
+                         (error 'clsql-sql-error
+                                :database database
+                                :expression query-expression
+                                :errno (sqlite:sqlite-error-code err)
+                                :error (sqlite:sqlite-error-message err)))))
 
 #-clisp
 (defstruct sqlite-result-set
   ;; Query is copied from .table command of sqlite comamnd line utility.
   (remove-if #'(lambda (s)
                  (and (>= (length s) 11)
-                      (string= (subseq s 0 11) "_clsql_seq_")))
+                      (string-equal (subseq s 0 11) "_CLSQL_SEQ_")))
              (mapcar #'car (database-query
                             "SELECT name FROM sqlite_master WHERE type='table' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='table' ORDER BY name"
-                            database '()))))
+                            database nil nil))))
 
 (defmethod database-list-views ((database sqlite-database)
                                 &key (owner nil))
   (declare (ignore owner))
   (mapcar #'car (database-query
                  "SELECT name FROM sqlite_master WHERE type='view' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='view' ORDER BY name"
-                 database nil)))
+                 database nil nil)))
 
 (defmethod database-list-indexes ((database sqlite-database)
                                   &key (owner nil))
   (declare (ignore owner))
   (mapcar #'car (database-query
                  "SELECT name FROM sqlite_master WHERE type='index' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='index' ORDER BY name"
-                 database nil)))
+                 database nil nil)))
+
+(defmethod database-list-table-indexes (table (database sqlite-database)
+                                       &key (owner nil))
+  (declare (ignore owner))
+  (let ((*print-circle* nil))
+    (mapcar #'car 
+           (database-query
+            (format
+             nil
+             "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"
+             table table)
+            database nil nil))))
 
 (declaim (inline sqlite-table-info))
 (defun sqlite-table-info (table database)
   (database-query (format nil "PRAGMA table_info('~A')" table)
-                         database '()))
+                 database nil nil))
 
 (defmethod database-list-attributes (table (database sqlite-database)
                                            &key (owner nil))
                                     &key (owner nil))
   (declare (ignore owner))
   (loop for field-info in (sqlite-table-info table database)
-       when (string= attribute (second field-info))
-       return (third field-info)))
+      when (string= attribute (second field-info))
+      return 
+       (let* ((raw-type (third field-info))
+              (start-length (position #\( raw-type))
+              (type (if start-length
+                        (subseq raw-type 0 start-length)
+                      raw-type))
+              (length (if start-length
+                          (parse-integer (subseq raw-type (1+ start-length))
+                                         :junk-allowed t)
+                        nil)))
+         (values (when type (ensure-keyword type)) 
+                 length
+                 nil
+                 (if (string-equal (fourth field-info) "0")
+                     1 0)))))
 
 (defun %sequence-name-to-table-name (sequence-name)
-  (concatenate 'string "_clsql_seq_" (sql-escape sequence-name)))
+  (concatenate 'string "_CLSQL_SEQ_" (sql-escape sequence-name)))
 
 (defun %table-name-to-sequence-name (table-name)
   (and (>= (length table-name) 11)
-       (string= (subseq table-name 0 11) "_clsql_seq_")
+       (string= (subseq table-name 0 11) "_CLSQL_SEQ_")
        (subseq table-name 11)))
 
+
 (defmethod database-create-sequence (sequence-name
                                     (database sqlite-database))
   (let ((table-name (%sequence-name-to-table-name sequence-name)))
     (database-execute-command
      (concatenate 'string "CREATE TABLE " table-name
-                 " (id INTEGER PRIMARY KEY)")
+                 " (last_value integer PRIMARY KEY, increment_by integer, min_value integer, is_called char(1))")
      database)
     (database-execute-command 
-     (format nil "INSERT INTO ~A VALUES (-1)" table-name)
+     (concatenate 'string "INSERT INTO " table-name
+                 " VALUES (1,1,1,'f')")
      database)))
 
 (defmethod database-drop-sequence (sequence-name
                                   (database sqlite-database))
   (database-execute-command
-   (concatenate 'string "DROP TABLE "
-               (%sequence-name-to-table-name sequence-name)) 
+   (concatenate 'string "DROP TABLE " (%sequence-name-to-table-name sequence-name)) 
    database))
 
 (defmethod database-list-sequences ((database sqlite-database)
                 (and sn (list sn))))
           (database-query
            "SELECT name FROM sqlite_master WHERE type='table' UNION ALL SELECT name FROM sqlite_temp_master WHERE type='table' ORDER BY name"
-           database '())))
+           database nil nil)))
 
 (defmethod database-sequence-next (sequence-name (database sqlite-database))
-  (let ((table-name (%sequence-name-to-table-name sequence-name)))
-    (database-execute-command
-     (format nil "UPDATE ~A SET id=(SELECT id FROM ~A)+1"
-            table-name table-name)
-     database)
-    (sqlite:sqlite-last-insert-rowid (sqlite-db database))
-    (parse-integer
-     (caar (database-query (format nil "SELECT id from ~A" table-name)
-                           database nil)))))
+  (without-interrupts
+   (let* ((table-name (%sequence-name-to-table-name sequence-name))
+         (tuple
+          (car (database-query 
+                (concatenate 'string "SELECT last_value,is_called FROM " 
+                             table-name)
+                database :auto nil))))
+     (cond
+       ((char-equal (schar (second tuple) 0) #\f)
+       (database-execute-command
+        (format nil "UPDATE ~A SET is_called='t'" table-name)
+        database)
+       (parse-integer (car tuple)))
+       (t
+       (let ((new-pos (1+ (parse-integer (car tuple)))))
+        (database-execute-command
+         (format nil "UPDATE ~A SET last_value=~D" table-name new-pos)
+         database)
+        new-pos))))))
+            
+(defmethod database-sequence-last (sequence-name (database sqlite-database))
+  (without-interrupts
+    (parse-integer 
+     (caar (database-query 
+           (concatenate 'string "SELECT last_value FROM " 
+                        (%sequence-name-to-table-name sequence-name))
+           database :auto nil)))))
 
 (defmethod database-set-sequence-position (sequence-name
                                            (position integer)
                                            (database sqlite-database))
-  (let ((table-name (%sequence-name-to-table-name sequence-name)))
-    (database-execute-command
-     (format nil "UPDATE ~A SET id=~A" table-name position)
-     database)
-    (sqlite:sqlite-last-insert-rowid (sqlite-db database))))
-
-(defmethod database-sequence-last (sequence-name (database sqlite-database))
-  (declare (ignore sequence-name)))
+  (database-execute-command
+   (format nil "UPDATE ~A SET last_value=~A,is_called='t'" 
+          (%sequence-name-to-table-name sequence-name)
+           position)
+   database)
+  position)
 
 (defmethod database-create (connection-spec (type (eql :sqlite)))
   (declare (ignore connection-spec))
 
 (defmethod database-probe (connection-spec (type (eql :sqlite)))
   (destructuring-bind (name) connection-spec
-    ;; TODO: Add a test that this file is a real sqlite database 
-    (and (probe-file name) t)))
+    ;; TODO: Add a test that this file is a real sqlite database
+    (or (string-equal ":memory:" name)
+       (and (probe-file name) t))))
+
+;;; Database capabilities
+
+(defmethod db-type-has-boolean-where? ((db-type (eql :sqlite)))
+  nil)
+