r10128: Add SQLite3 backend
authorKevin M. Rosenberg <kevin@rosenberg.net>
Sun, 31 Oct 2004 23:59:26 +0000 (23:59 +0000)
committerKevin M. Rosenberg <kevin@rosenberg.net>
Sun, 31 Oct 2004 23:59:26 +0000 (23:59 +0000)
ChangeLog
clsql-sqlite3.asd [new file with mode: 0644]
db-sqlite3/init-func-sample/Makefile [new file with mode: 0644]
db-sqlite3/init-func-sample/example.lisp [new file with mode: 0644]
db-sqlite3/init-func-sample/iso-8859-15-coll.c [new file with mode: 0644]
db-sqlite3/sqlite3-api.lisp [new file with mode: 0644]
db-sqlite3/sqlite3-loader.lisp [new file with mode: 0644]
db-sqlite3/sqlite3-package.lisp [new file with mode: 0644]
db-sqlite3/sqlite3-sql.lisp [new file with mode: 0644]
tests/test-init.lisp
tests/utils.lisp

index 7af023f92785e99b5e4256eedef7c6e2ad330752..912680ebbbb26af7b0b40245f10fabe654a9ce90 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+31 Oct 2004 Kevin Rosenberg <kevin@rosenberg.net>
+       * clsql-sqlite3, db-sqlite3/*: NEW BACKEND
+       contributed by Aurelio Bignoli
+
 23 Oct 2004 Kevin Rosenberg <kevin@rosenberg.net>
        * sql/oodml.lisp: Commit patch from Walter Pelis
        to use an object's database for a select on its slot.
diff --git a/clsql-sqlite3.asd b/clsql-sqlite3.asd
new file mode 100644 (file)
index 0000000..4795362
--- /dev/null
@@ -0,0 +1,38 @@
+;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
+;;;; *************************************************************************
+;;;; FILE IDENTIFICATION
+;;;;
+;;;; Name:          clsql-sqlite.asd
+;;;; Purpose:       ASDF file for CLSQL SQLite3 backend
+;;;; Programmer:    Aurelio Bignoli
+;;;; Date Started:  Oct 2004
+;;;;
+;;;; $Id$
+;;;;
+;;;; This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli
+;;;;
+;;;; CLSQL users are granted the rights to distribute and use this software
+;;;; as governed by the terms of the Lisp Lesser GNU Public License
+;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
+;;;; *************************************************************************
+
+(defpackage #:clsql-sqlite3-system (:use #:asdf #:cl))
+(in-package #:clsql-sqlite3-system)
+
+(defsystem clsql-sqlite3
+  :name "cl-sql-sqlite3"
+  :author "Aurelio Bignoli <aurelio@bignoli.it>"
+  :maintainer "Aurelio Bignoli"
+  :licence "Lessor Lisp General Public License"
+  :description "Common Lisp Sqlite3 Driver"
+  :long-description "cl-sql-sqlite3 package provides a database driver to SQLite Versione 3 database library."
+
+
+  :depends-on (clsql clsql-uffi)
+  :components
+  ((:module :db-sqlite3
+           :components
+           ((:file "sqlite3-package")
+            (:file "sqlite3-loader" :depends-on ("sqlite3-package"))
+            (:file  "sqlite3-api" :depends-on ("sqlite3-loader"))
+            (:file "sqlite3-sql" :depends-on ("sqlite3-api"))))))
diff --git a/db-sqlite3/init-func-sample/Makefile b/db-sqlite3/init-func-sample/Makefile
new file mode 100644 (file)
index 0000000..de1cf29
--- /dev/null
@@ -0,0 +1,19 @@
+# FILE IDENTIFICATION
+#
+#  Name:         Makefile
+#  Purpose:      Makefile for SQLite 3 init function example.
+#  Programer:    Aurelio Bignoli
+#  Date Started: Oct 2004
+#
+#  CVS Id:   $Id$
+#
+# This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli
+#
+# CLSQL users are granted the rights to distribute and use this software
+# as governed by the terms of the Lisp Lesser GNU Public License
+# (http://opensource.franz.com/preamble.html), also known as the LLGPL.
+
+sqlite3-utils.so:      iso-8859-15-coll.c Makefile
+       gcc -c -fPIC iso-8859-15-coll.c -o iso-8859-15-coll.o
+       gcc -shared iso-8859-15-coll.o -o sqlite3-utils.so -l sqlite3
+
diff --git a/db-sqlite3/init-func-sample/example.lisp b/db-sqlite3/init-func-sample/example.lisp
new file mode 100644 (file)
index 0000000..1a81645
--- /dev/null
@@ -0,0 +1,70 @@
+;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
+;;;; *************************************************************************
+;;;; FILE IDENTIFICATION
+;;;;
+;;;; Name:     example.lisp
+;;;; Purpose:  Sample code for SQLite 3 initialization functions
+;;;; Authors:  Aurelio Bignoli
+;;;; Created:  Oct 2004
+;;;;
+;;;; $Id$
+;;;;
+;;;; This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli
+;;;;
+;;;; CLSQL users are granted the rights to distribute and use this software
+;;;; as governed by the terms of the Lisp Lesser GNU Public License
+;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
+;;;; *************************************************************************
+
+;;;; Load CLSQL.
+(asdf:oos 'asdf:load-op :clsql-sqlite3)
+
+;;;; Load sqlite3-utils.so library. See Makefile for library creation.
+(unless (uffi:load-foreign-library "/usr/lib/clsql/sqlite3-utils.so"
+                                  :module "sqlite3-utils"
+                                  :supporting-libraries '("c"))
+  (error "Unable to load foreign library"))
+
+;;;; Define the foreign function to be used as init function.
+(uffi:def-function 
+    ("create_iso_8859_15_ci_collation" create-coll)
+    ((db sqlite3:sqlite3-db))
+  :returning :int
+  :module "sqlite3-utils")
+
+;;;; Create the DB using create-coll as init function.
+(defparameter db-name "init-func-test.db")
+(clsql:destroy-database (list db-name) :database-type :sqlite3)
+(clsql:connect (list db-name #'create-coll) :database-type :sqlite3)
+
+;;;; Create a table. Field f2 uses the newly defined collating
+;;;; sequence.
+(clsql:execute-command 
+ "CREATE TABLE t1 (f1 CHAR(1), f2 CHAR(1) COLLATE ISO_8859_15_CI)")
+
+;;;; Populate the table.
+(clsql:execute-command "INSERT INTO t1 VALUES ('à', 'à')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('a', 'a')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('A', 'A')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('é', 'é')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('e', 'e')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('E', 'E')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('ì', 'ì')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('i', 'i')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('I', 'I')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('ò', 'ò')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('o', 'o')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('O', 'O')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('ù', 'ù')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('u', 'u')")
+(clsql:execute-command "INSERT INTO t1 VALUES ('U', 'U')")
+
+;;;; Perform some SELECTs.
+(format t "~&SELECT * FROM t1 ==> ~A~%"(clsql:query "SELECT * FROM t1"))
+(format t "~&SELECT * FROM t1 ORDER BY f1 ==> ~A~%" 
+       (clsql:query "SELECT * FROM t1 ORDER BY f1"))
+(format t "~&SELECT * FROM t1 ORDER BY f2 ==> ~A~%" 
+       (clsql:query "SELECT * FROM t1 ORDER BY f2"))
+
+;;;; Disconnect from database.
+(clsql:disconnect)
\ No newline at end of file
diff --git a/db-sqlite3/init-func-sample/iso-8859-15-coll.c b/db-sqlite3/init-func-sample/iso-8859-15-coll.c
new file mode 100644 (file)
index 0000000..0e7fea1
--- /dev/null
@@ -0,0 +1,79 @@
+/****************************************************************************
+ * FILE IDENTIFICATION
+ *
+ *   Name:          iso-8859-15-coll.c
+ *   Purpose:       SQLite 3 initialization function for  
+ *                  ISO-8859-15 collating sequence.
+ *   Programmer:    Aurelio Bignoli
+ *   Date Started:  Oct 2004
+ *
+ * $Id$
+ *
+ * This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli
+ *
+ * CLSQL users are granted the rights to distribute and use this software
+ * as governed by the terms of the Lisp Lesser GNU Public License
+ * (http://opensource.franz.com/preamble.html), also known as the LLGPL.
+ ***************************************************************************/
+
+
+/* Collating sequence name. CI = Case Insensitive */
+#define ISO_8859_15_CI_NAME "ISO_8859_15_CI"
+
+/* Conversion table. */
+const unsigned char iso_8859_15_ci [] = {
+  /* 0 */ 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, 
+  /* 1 */ 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, 
+  /* 2 */ 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, 
+  /* 3 */ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, 
+  /* 4 */ 0x40, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 
+  /* 5 */ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, 
+  /* 6 */ 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, 
+  /* 7 */ 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, 
+  /* 8 */ 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x73, 0x8B, 0x6F, 0x8D, 0x7A, 0x79, 
+  /* 9 */ 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x96, 0x97, 0x98, 0x99, 0x73, 0x9B, 0x6F, 0x9D, 0x7A, 0x79, 
+  /* A */ 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0x73, 0xA7, 0x73, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, 
+  /* B */ 0xB0, 0xB1, 0xB2, 0xB3, 0x7A, 0xB5, 0xB6, 0xB7, 0x7A, 0xB9, 0xBA, 0xBB, 0x6F, 0xBD, 0x79, 0xBF, 
+  /* C */ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x65, 0x63, 0x65, 0x65, 0x65, 0x65, 0x69, 0x69, 0x69, 0x69, 
+  /* D */ 0x64, 0x6E, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0xD7, 0x6F, 0x75, 0x75, 0x75, 0x75, 0x79, 0xDE, 0x73, 
+  /* E */ 0x61, 0x61, 0x61, 0x61, 0x61, 0x61, 0x65, 0x63, 0x65, 0x65, 0x65, 0x65, 0x69, 0x69, 0x69, 0x69,
+  /* F */ 0x64, 0x6E, 0x6F, 0x6F, 0x6F, 0x6F, 0x6F, 0xF7, 0x6F, 0x75, 0x75, 0x75, 0x75, 0x79, 0xFE, 0x73
+};
+
+/*
+ * A modified version of sqlite3StrNICmp in sqlite/src/util.c
+ */
+int iso_8859_15_ci_StrCmp(const char *zLeft, const char *zRight, int N){
+  register unsigned char *a, *b;
+  a = (unsigned char *)zLeft;
+  b = (unsigned char *)zRight;
+  while( N-- > 0 && *a!=0 && iso_8859_15_ci[*a]==iso_8859_15_ci[*b]){ a++; b++; }
+  return N<0 ? 0 : iso_8859_15_ci[*a] - iso_8859_15_ci[*b];
+}
+
+/*
+ * A modified version of nocaseCollatinFunc in sqlite/src/main.c.
+ */
+int iso_8859_15_ci_CollatingFunc(
+  void *NotUsed,
+  int nKey1, const void *pKey1,
+  int nKey2, const void *pKey2
+){
+  int r = iso_8859_15_ci_StrCmp(
+      (const char *)pKey1, (const char *)pKey2, (nKey1<nKey2)?nKey1:nKey2);
+  if( 0==r ){
+    r = nKey1-nKey2;
+  }
+  return r;
+}
+/* 
+ * Set the ISO_8859_15_CI collating sequence for a db.
+ */
+#include "sqlite3.h"
+
+int create_iso_8859_15_ci_collation (sqlite3 *db)
+{
+  return sqlite3_create_collation (db, ISO_8859_15_CI_NAME, SQLITE_UTF8, 0,
+                                  iso_8859_15_ci_CollatingFunc);
+}
diff --git a/db-sqlite3/sqlite3-api.lisp b/db-sqlite3/sqlite3-api.lisp
new file mode 100644 (file)
index 0000000..22d1966
--- /dev/null
@@ -0,0 +1,365 @@
+;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
+;;;; *************************************************************************
+;;;; FILE IDENTIFICATION
+;;;;
+;;;; Name:     sqlite3-api.lisp
+;;;; Purpose:  Low-level SQLite3 interface using UFFI
+;;;; Authors:  Aurelio Bignoli
+;;;; Created:  Oct 2004
+;;;;
+;;;; $Id$
+;;;;
+;;;; This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli
+;;;;
+;;;; CLSQL users are granted the rights to distribute and use this software
+;;;; as governed by the terms of the Lisp Lesser GNU Public License
+;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
+;;;; *************************************************************************
+
+(in-package #:cl-user)
+
+(defpackage #:sqlite3
+  (:use #:common-lisp #:uffi)
+    (:export
+           ;;; Conditions
+           #:sqlite3-error
+           #:sqlite3-error-code
+           #:sqlite3-error-message
+          
+          ;;; API functions.
+           #:sqlite3-open
+          #:sqlite3-close
+
+          #:sqlite3-prepare
+          #:sqlite3-step
+          #:sqlite3-finalize
+
+          #:sqlite3-column-count
+          #:sqlite3-column-name
+          #:sqlite3-column-type
+          #:sqlite3-column-text
+          #:sqlite3-column-bytes
+          #:sqlite3-column-blob
+
+          ;;; Types.
+          #:sqlite3-db
+          #:sqlite3-db-type
+          #:sqlite3-stmt-type
+          #:unsigned-char-ptr-type
+          #:null-stmt
+
+          ;;; Columnt types.
+          #:SQLITE-INTEGER
+          #:SQLITE-FLOAT
+          #:SQLITE-TEXT
+          #:SQLITE-BLOB
+          #:SQLITE-NULL))
+
+(in-package #:sqlite3)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Return values for sqlite_exec() and sqlite_step()
+;;;;
+(defconstant SQLITE-OK           0   "Successful result")
+(defconstant SQLITE-ERROR        1   "SQL error or missing database")
+(defconstant SQLITE-INTERNAL     2   "An internal logic error in SQLite")
+(defconstant SQLITE-PERM         3   "Access permission denied")
+(defconstant SQLITE-ABORT        4   "Callback routine requested an abort")
+(defconstant SQLITE-BUSY         5   "The database file is locked")
+(defconstant SQLITE-LOCKED       6   "A table in the database is locked")
+(defconstant SQLITE-NOMEM        7   "A malloc() failed")
+(defconstant SQLITE-READONLY     8   "Attempt to write a readonly database")
+(defconstant SQLITE-INTERRUPT    9   "Operation terminated by sqlite3_interrupt()")
+(defconstant SQLITE-IOERR       10   "Some kind of disk I/O error occurred")
+(defconstant SQLITE-CORRUPT     11   "The database disk image is malformed")
+(defconstant SQLITE-NOTFOUND    12   "(Internal Only) Table or record not found")
+(defconstant SQLITE-FULL        13   "Insertion failed because database is full")
+(defconstant SQLITE-CANTOPEN    14   "Unable to open the database file")
+(defconstant SQLITE-PROTOCOL    15   "Database lock protocol error")
+(defconstant SQLITE-EMPTY       16   "Database is empty")
+(defconstant SQLITE-SCHEMA      17   "The database schema changed")
+(defconstant SQLITE-TOOBIG      18   "Too much data for one row of a table")
+(defconstant SQLITE-CONSTRAINT  19   "Abort due to contraint violation")
+(defconstant SQLITE-MISMATCH    20   "Data type mismatch")
+(defconstant SQLITE-MISUSE      21   "Library used incorrectly")
+(defconstant SQLITE-NOLFS       22   "Uses OS features not supported on host")
+(defconstant SQLITE-AUTH        23   "Authorization denied")
+(defconstant SQLITE-FORMAT      24   "Auxiliary database format error")
+(defconstant SQLITE-RANGE       25   "2nd parameter to sqlite3_bind out of range")
+(defconstant SQLITE-NOTADB      26   "File opened that is not a database file")
+(defconstant SQLITE-ROW         100  "sqlite3_step() has another row ready")
+(defconstant SQLITE-DONE        101  "sqlite3_step() has finished executing")
+
+(defparameter error-codes 
+  (list
+   (cons SQLITE-OK "not an error")
+   (cons SQLITE-ERROR "SQL logic error or missing database")
+   (cons SQLITE-INTERNAL "internal SQLite implementation flaw")
+   (cons SQLITE-PERM "access permission denied")
+   (cons SQLITE-ABORT "callback requested query abort")
+   (cons SQLITE-BUSY "database is locked")
+   (cons SQLITE-LOCKED "database table is locked")
+   (cons SQLITE-NOMEM "out of memory")
+   (cons SQLITE-READONLY "attempt to write a readonly database")
+   (cons SQLITE-INTERRUPT "interrupted")
+   (cons SQLITE-IOERR "disk I/O error")
+   (cons SQLITE-CORRUPT "database disk image is malformed")
+   (cons SQLITE-NOTFOUND "table or record not found")
+   (cons SQLITE-FULL "database is full")
+   (cons SQLITE-CANTOPEN "unable to open database file")
+   (cons SQLITE-PROTOCOL "database locking protocol failure")
+   (cons SQLITE-EMPTY "table contains no data")
+   (cons SQLITE-SCHEMA "database schema has changed")
+   (cons SQLITE-TOOBIG "too much data for one table row")
+   (cons SQLITE-CONSTRAINT "constraint failed")
+   (cons SQLITE-MISMATCH "datatype mismatch")
+   (cons SQLITE-MISUSE "library routine called out of sequence")
+   (cons SQLITE-NOLFS "kernel lacks large file support")
+   (cons SQLITE-AUTH "authorization denied")
+   (cons SQLITE-FORMAT "auxiliary database format error")
+   (cons SQLITE-RANGE "bind index out of range")
+   (cons SQLITE-NOTADB "file is encrypted or is not a database"))
+  "Association list of error messages.")
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Column types.
+;;;;
+(defconstant SQLITE-INTEGER  1)
+(defconstant SQLITE-FLOAT    2)
+(defconstant SQLITE-TEXT     3)
+(defconstant SQLITE-BLOB     4)
+(defconstant SQLITE-NULL     5)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Foreign types definitions.
+;;;;
+(def-foreign-type sqlite3-db :pointer-void)
+(def-foreign-type sqlite3-stmt :pointer-void)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Lisp types definitions.
+;;;;
+(def-type sqlite3-db-type sqlite3-db)
+(def-type sqlite3-db-ptr-type (* sqlite3-db))
+(def-type sqlite3-stmt-type sqlite3-stmt)
+(def-type sqlite3-stmt-ptr-type (* sqlite3-stmt))
+(def-type unsigned-char-ptr-type (* :unsigned-char))
+
+(defparameter null-stmt (make-null-pointer :void))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;
+;;; Hash tables for db and statement pointers.
+;;;
+(defvar *db-pointers* (make-hash-table))
+(defvar *stmt-pointers* (make-hash-table))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Conditions.
+;;;;
+(define-condition sqlite3-error ()
+  ((message :initarg :message :reader sqlite3-error-message :initform "")
+   (code :initarg :code :reader sqlite3-error-code))
+  (:report (lambda (condition stream)
+            (format stream "Sqlite3 error [~A]: ~A"
+                    (sqlite3-error-code condition) 
+                    (sqlite3-error-message condition)))))
+
+(defmethod signal-sqlite3-error (db)
+  (let ((condition
+        (make-condition 'sqlite3-error
+                        :code (sqlite3-errcode db)
+                        :message (convert-from-cstring (sqlite3-errmsg db)))))
+    (unless (signal condition)
+      (invoke-debugger condition))))
+
+(defmethod signal-sqlite3-error ((code number))
+  (let ((condition
+        (make-condition 'sqlite3-error
+                        :code code
+                        :message (let ((s (cdr (assoc code error-codes))))
+                                   (if s
+                                       s
+                                       "unknown error")))))
+    (unless (signal condition)
+      (invoke-debugger condition))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; Library functions.
+;;;;
+(defmacro def-sqlite3-function (name args &key (returning :void))
+  `(def-function ,name ,args
+    :module "sqlite3"
+    :returning ,returning))
+
+(declaim (inline %errcode))
+(def-sqlite3-function
+    "sqlite3_errcode"
+    ((db sqlite3-db))
+  :returning :int)
+
+(declaim (inline %errmsg))
+(def-sqlite3-function
+    "sqlite3_errmsg"
+    ((db sqlite3-db))
+  :returning :cstring)
+
+(declaim (inline %open))
+(def-sqlite3-function
+    ("sqlite3_open" %open)
+    ((dbname :cstring)
+     (db (* sqlite3-db)))
+  :returning :int)
+
+(declaim (inline %close))
+(def-sqlite3-function
+    ("sqlite3_close" %close)
+    ((db sqlite3-db))
+  :returning :int)
+
+(declaim (inline %prepare))
+(def-sqlite3-function
+    ("sqlite3_prepare" %prepare)
+    ((db sqlite3-db)
+     (sql :cstring)
+     (len :int)
+     (stmt (* sqlite3-stmt))
+     (sql-tail (* (* :unsigned-char))))
+  :returning :int)
+
+(declaim (inline %step))
+(def-sqlite3-function
+    ("sqlite3_step" %step)
+    ((stmt sqlite3-stmt))
+  :returning :int)
+
+(declaim (inline %finalize))
+(def-sqlite3-function
+    ("sqlite3_finalize" %finalize)
+    ((stmt sqlite3-stmt))
+  :returning :int)
+
+(declaim (inline sqlite3-column-count))
+(def-sqlite3-function 
+    "sqlite3_column_count"
+    ((stmt sqlite3-stmt))
+  :returning :int)
+
+(declaim (inline %column-name))
+(def-sqlite3-function 
+    ("sqlite3_column_name" %column-name)
+    ((stmt sqlite3-stmt)
+     (n-col :int))
+  :returning :cstring)
+
+(declaim (inline sqlite3-column-type))
+(def-sqlite3-function 
+    "sqlite3_column_type"
+    ((stmt sqlite3-stmt)
+     (n-col :int))
+  :returning :int)
+
+(declaim (inline sqlite3-column-text))
+(def-sqlite3-function 
+    "sqlite3_column_text"
+    ((stmt sqlite3-stmt)
+     (n-col :int))
+  :returning (* :unsigned-char))
+
+(declaim (inline sqlite3-column-bytes))
+(def-sqlite3-function 
+    "sqlite3_column_bytes"
+    ((stmt sqlite3-stmt)
+     (n-col :int))
+  :returning :int)
+
+(declaim (inline sqlite3-column-blob))
+(def-sqlite3-function 
+    "sqlite3_column_blob"
+    ((stmt sqlite3-stmt)
+     (n-col :int))
+  :returning :pointer-void)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;;;
+;;;; wrapper functions.
+;;;;
+(defun sqlite3-open (db-name &optional (mode 0))
+  (declare (ignore mode) (type string db-name))
+  (let ((dbp (allocate-foreign-object 'sqlite3-db)))
+    (declare (type sqlite3-db-ptr-type dbp))
+    (with-cstring (db-name-native db-name) 
+      (let ((result (%open db-name-native dbp)))
+       (if (/=  result 0)
+           (progn
+             ;; According to docs, the db must be closed even in case
+             ;; of error.
+             (%close (deref-pointer dbp 'sqlite3-db))
+             (free-foreign-object dbp)
+             (signal-sqlite3-error result))
+           (let ((db (deref-pointer dbp 'sqlite3-db)))
+             (declare (type sqlite3-db-type db))
+             (setf (gethash db *db-pointers*) dbp)
+             db))))))
+
+(declaim (ftype (function (sqlite3-db-type) t) sqlite3-close))
+(defun sqlite3-close (db)
+  (declare (type sqlite3-db-type db))
+  (let ((result (%close db)))
+    (if (/= result 0)
+       (signal-sqlite3-error result)
+       (progn
+         (free-foreign-object (gethash db *db-pointers*))
+         (remhash db *db-pointers*)
+         t))))
+
+(declaim (ftype (function (sqlite3-db-type string) sqlite3-stmt-type) sqlite3-prepare))
+(defun sqlite3-prepare (db sql)
+  (declare (type sqlite3-db-type db))
+  (with-cstring (sql-native sql)
+    (let ((stmtp (allocate-foreign-object 'sqlite3-stmt)))
+      (declare (type sqlite3-stmt-ptr-type stmtp))
+      (with-foreign-object (sql-tail '(* :unsigned-char))
+       (let ((result (%prepare db sql-native -1 stmtp sql-tail)))
+         (if (/= result SQLITE-OK)
+             (progn
+               (unless (null-pointer-p stmtp)
+                 ;; There is an error, but a statement has been allocated:
+                 ;; finalize it (better safe than sorry).
+                 (%finalize (deref-pointer stmtp 'sqlite3-stmt)))
+               (free-foreign-object stmtp)
+               (signal-sqlite3-error db))
+             (let ((stmt (deref-pointer stmtp 'sqlite3-stmt)))
+               (declare (type sqlite3-stmt-type stmt))
+               (setf (gethash stmt *stmt-pointers*) stmtp)
+               stmt)))))))
+
+(declaim (ftype (function (sqlite3-stmt-type) t) sqlite3-step))
+(defun sqlite3-step (stmt)
+  (declare (type sqlite3-stmt-type stmt))
+  (let ((result (%step stmt)))
+    (cond ((= result SQLITE-ROW) t)
+         ((= result SQLITE-DONE) nil)
+         (t (signal-sqlite3-error result)))))
+
+(declaim (ftype (function (sqlite3-stmt-type) t) sqlite3-finalize))
+(defun sqlite3-finalize (stmt)
+  (declare (type sqlite3-stmt-type stmt))
+  (let ((result (%finalize  stmt)))
+    (if (/= result SQLITE-OK)
+       (signal-sqlite3-error result)
+       (progn
+         (free-foreign-object (gethash stmt *stmt-pointers*))
+         (remhash stmt *stmt-pointers*)
+         t))))
+
+(declaim (inline sqlite3-column-name))
+(defun sqlite3-column-name (stmt n)
+  (declare (type sqlite3-stmt-type stmt) (type fixnum n))
+  (convert-from-cstring (%column-name stmt n)))
diff --git a/db-sqlite3/sqlite3-loader.lisp b/db-sqlite3/sqlite3-loader.lisp
new file mode 100644 (file)
index 0000000..9efaf1f
--- /dev/null
@@ -0,0 +1,46 @@
+;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
+;;;; *************************************************************************
+;;;; FILE IDENTIFICATION
+;;;;
+;;;; Name:          sqlite3-loader.lisp
+;;;; Purpose:       Sqlite3 library loader using UFFI
+;;;; Programmer:    Aurelio Bignoli
+;;;; Date Started:  Oct 2004
+;;;;
+;;;; $Id$
+;;;;
+;;;; This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli
+;;;;
+;;;; CLSQL users are granted the rights to distribute and use this software
+;;;; as governed by the terms of the Lisp Lesser GNU Public License
+;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
+;;;; *************************************************************************
+
+(in-package #:clsql-sqlite3)
+
+(defvar *sqlite3-supporting-libraries* '("c")
+  "Used only by CMU. List of library flags needed to be passed to ld
+to load the Sqlite3 library succesfully.  If this differs at your site,
+set to the right path before compiling or loading the system.")
+
+(defvar *sqlite3-library-loaded* nil
+  "T if foreign library was able to be loaded successfully")
+
+(defmethod database-type-library-loaded ((database-type (eql :sqlite3)))
+  "T if foreign library was able to be loaded successfully. "
+  *sqlite3-library-loaded*)
+
+(defmethod database-type-load-foreign ((database-type (eql :sqlite3)))
+  (let ((libpath (uffi:find-foreign-library
+                 '("libsqlite3" "sqlite3")
+                 '(#+64bit "/usr/lib64/"
+                   "/usr/lib/" "/usr/local/lib/" "/bin/")
+                 :drive-letters '("C" "D" "E"))))
+    (if (uffi:load-foreign-library libpath
+                                  :module "sqlite3"
+                                  :supporting-libraries 
+                                  *sqlite3-supporting-libraries*)
+       (setq *sqlite3-library-loaded* t)
+       (warn "Can't load Sqlite3 library ~A" libpath))))
+
+(clsql-sys:database-type-load-foreign :sqlite3)
diff --git a/db-sqlite3/sqlite3-package.lisp b/db-sqlite3/sqlite3-package.lisp
new file mode 100644 (file)
index 0000000..1acc6ea
--- /dev/null
@@ -0,0 +1,23 @@
+;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
+;;;; *************************************************************************
+;;;; FILE IDENTIFICATION
+;;;;
+;;;; Name:          sqlite-package.lisp
+;;;; Purpose:       Package definition for low-level SQLite3 interface
+;;;; Programmer:    Aurelio Bignoli
+;;;; Date Started:  Oct 2004
+;;;;
+;;;; $Id$
+;;;;
+;;;; This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli
+;;;;
+;;;; CLSQL users are granted the rights to distribute and use this software
+;;;; as governed by the terms of the Lisp Lesser GNU Public License
+;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
+;;;; *************************************************************************
+
+(in-package #:cl-user)
+
+(defpackage #:clsql-sqlite3
+  (:use #:common-lisp #:clsql-sys)
+  (:export #:sqlite3-database))
diff --git a/db-sqlite3/sqlite3-sql.lisp b/db-sqlite3/sqlite3-sql.lisp
new file mode 100644 (file)
index 0000000..0d6bf70
--- /dev/null
@@ -0,0 +1,321 @@
+;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
+;;;; *************************************************************************
+;;;; FILE IDENTIFICATION
+;;;;
+;;;; Name:     sqlite-sql.lisp
+;;;; Purpose:  High-level SQLite3 interface
+;;;; Authors:  Aurelio Bignoli
+;;;; Created:  Oct 2004
+;;;;
+;;;; $Id$
+;;;;
+;;;; This file, part of CLSQL, is Copyright (c) 2004 by Aurelio Bignoli
+;;;;
+;;;; CLSQL users are granted the rights to distribute and use this software
+;;;; as governed by the terms of the Lisp Lesser GNU Public License
+;;;; (http://opensource.franz.com/preamble.html), also known as the LLGPL.
+;;;; *************************************************************************
+
+(in-package #:clsql-sqlite3)
+
+(defclass sqlite3-database (database)
+  ((sqlite3-db :initarg :sqlite3-db :accessor sqlite3-db)))
+
+(defmethod database-type ((database sqlite3-database))
+  :sqlite3)
+
+(defmethod database-initialize-database-type ((database-type (eql :sqlite3)))
+  t)
+
+(defun check-sqlite3-connection-spec (connection-spec)
+  (check-connection-spec connection-spec :sqlite3 (name &optional init-foreign-func)))
+
+(defmethod database-name-from-spec (connection-spec
+                                   (database-type (eql :sqlite3)))
+  (check-sqlite3-connection-spec connection-spec)
+  (first connection-spec))
+
+(defmethod database-connect (connection-spec (database-type (eql :sqlite3)))
+  (check-sqlite3-connection-spec connection-spec)
+    (handler-case
+       (let ((db (sqlite3:sqlite3-open (first connection-spec)))
+             (init-foreign-func (second connection-spec)))
+         (declare (type sqlite3:sqlite3-db-type db))
+         (when init-foreign-func
+           (handler-case
+               (funcall init-foreign-func db)
+             (condition (c)
+               (progn
+                 (sqlite3:sqlite3-close db)
+                 (error c)))))
+         (make-instance 'sqlite3-database
+                        :name (database-name-from-spec connection-spec :sqlite3)
+                        :database-type :sqlite3
+                        :connection-spec connection-spec
+                        :sqlite3-db db))
+      (sqlite3:sqlite3-error (err)
+       (error 'sql-connection-error
+              :database-type database-type
+              :connection-spec connection-spec
+              :error-id (sqlite3:sqlite3-error-code err)
+              :message (sqlite3:sqlite3-error-message err)))))
+
+(defmethod database-disconnect ((database sqlite3-database))
+  (sqlite3:sqlite3-close (sqlite3-db database))
+  (setf (sqlite3-db database) nil)
+  t)
+
+(defmethod database-execute-command (sql-expression (database sqlite3-database))
+  (handler-case
+      (let ((stmt (sqlite3:sqlite3-prepare (sqlite3-db database) sql-expression)))
+       (declare (type sqlite3:sqlite3-stmt-type stmt))
+       (when stmt
+         (unwind-protect
+              (sqlite3:sqlite3-step stmt)
+           (sqlite3:sqlite3-finalize stmt))))
+    (sqlite3:sqlite3-error (err)
+      (error 'sql-database-data-error
+            :database database
+            :expression sql-expression
+            :error-id (sqlite3:sqlite3-error-code err)
+            :message (sqlite3:sqlite3-error-message err))))
+  t)
+
+(defstruct sqlite3-result-set
+  (stmt sqlite3:null-stmt
+       :type sqlite3:sqlite3-stmt-type)
+  (n-col 0 :type fixnum)
+  (col-names '())
+  (result-types '()))
+
+(declaim (ftype (function (sqlite3:sqlite3-stmt-type fixnum t) list) get-result-types))
+(defun get-result-types (stmt n-col result-types)
+  (declare (type sqlite3:sqlite3-stmt-type stmt) (type fixnum n-col))
+  (if (eq :auto result-types)
+      (loop for n from 0 below n-col
+           collect (let ((column-type (sqlite3:sqlite3-column-type stmt n)))
+                     (cond
+                       ((= column-type sqlite3:SQLITE-INTEGER) :int64)
+                       ((= column-type sqlite3:SQLITE-FLOAT) :double)
+                       ((= column-type sqlite3:SQLITE-TEXT) :string)
+                       ((= column-type sqlite3:SQLITE-BLOB) :blob)
+                       ((= column-type sqlite3:SQLITE-NULL) :string)
+                       (t :string))))
+      (loop for type in result-types
+           collect (case type
+                     ((:int :integer :tinyint :long) :int32)
+                     (:bigint :int64)
+                     ((:float :double) :double)
+                     ((:numeric) :number)
+                     (otherwise :string)))))
+
+(defmethod database-query-result-set ((query-expression string)
+                                     (database sqlite3-database)
+                                     &key result-types full-set)
+  (let ((stmt sqlite3:null-stmt))
+    (declare (type sqlite3:sqlite3-stmt-type stmt))
+    (handler-case
+       (progn
+         (setf stmt (sqlite3:sqlite3-prepare (sqlite3-db database) 
+                                             query-expression))
+         (let* ((n-col (if (sqlite3:sqlite3-step stmt)
+                           ;; Non empty result set.
+                           (sqlite3:sqlite3-column-count stmt)
+                           ;; Empty result set.
+                           0))
+                (result-set (make-sqlite3-result-set 
+                             :stmt stmt
+                             :n-col n-col
+                             :col-names (loop for n from 0 below n-col
+                                              collect (sqlite3:sqlite3-column-name stmt n))
+                             :result-types (when (> n-col 0) 
+                                             (get-result-types stmt n-col result-types)))))
+           (if full-set
+               (values result-set n-col nil)
+               (values result-set n-col))))
+    (sqlite3:sqlite3-error (err)
+       (progn
+         (unless (eq stmt sqlite3:null-stmt)
+           (ignore-errors
+             (sqlite3:sqlite3-finalize stmt)))
+         (error 'sql-database-data-error
+                :database database
+                :expression query-expression
+                :error-id (sqlite3:sqlite3-error-code err)
+                :message (sqlite3:sqlite3-error-message err)))))))
+
+(defmethod database-dump-result-set (result-set (database sqlite3-database))
+  (handler-case 
+      (sqlite3:sqlite3-finalize (sqlite3-result-set-stmt result-set))
+    (sqlite3:sqlite3-error (err)
+      (error 'sql-database-error
+            :message
+            (format nil "Error finalizing SQLite3 statement: ~A"
+                    (sqlite3:sqlite3-error-message err))))))
+
+(defmethod database-store-next-row (result-set (database sqlite3-database) list)
+  (let ((n-col (sqlite3-result-set-n-col result-set)))
+    (if (= n-col 0)
+       ;; empty result set.
+       nil
+       ;; Non-empty set.
+       (let ((stmt (sqlite3-result-set-stmt result-set)))
+         (declare (type sqlite3:sqlite3-stmt-type stmt))
+         ;; Store row in list.
+         (loop for i = 0 then (1+ i)
+               for rest on list
+               for types = (sqlite3-result-set-result-types result-set) then (rest types)
+               do (setf (car rest)
+                        (if (eq (first types) :blob)
+                            (clsql-uffi:convert-raw-field
+                             (sqlite3:sqlite3-column-blob stmt i)
+                             types 0
+                             (sqlite3:sqlite3-column-bytes stmt i))
+                            (clsql-uffi:convert-raw-field
+                             (sqlite3:sqlite3-column-text stmt i)
+                             types 0))))
+         ;; Advance result set cursor.
+         (handler-case
+             (unless (sqlite3:sqlite3-step stmt)
+               (setf (sqlite3-result-set-n-col result-set) 0))
+           (sqlite3:sqlite3-error (err)
+             (error 'sql-database-error
+                    :message "Error in sqlite3-step: ~A"
+                    (sqlite3:sqlite3-error-message err))))
+         t))))
+
+
+(defmethod database-query (query-expression (database sqlite3-database) result-types field-names)
+  (declare (optimize (speed 3) (safety 0) (debug 0) (space 0)))
+  (handler-case
+      (let ((stmt (sqlite3:sqlite3-prepare (sqlite3-db database) 
+                                          query-expression))
+           (rows '())
+           (col-names '()))
+       (declare (type sqlite3:sqlite3-stmt-type stmt))
+       (unwind-protect
+            (when (sqlite3:sqlite3-step stmt)
+              (let ((n-col (sqlite3:sqlite3-column-count stmt)))
+                (flet ((extract-row-data ()
+                         (loop for i from 0 below n-col
+                               for types = (get-result-types stmt n-col result-types) then (rest types)
+                               collect (if (eq (first types) :blob)
+                                           (clsql-uffi:convert-raw-field
+                                            (sqlite3:sqlite3-column-blob stmt i)
+                                            types 0
+                                            (sqlite3:sqlite3-column-bytes stmt i))
+                                           (clsql-uffi:convert-raw-field
+                                            (sqlite3:sqlite3-column-text stmt i)
+                                            types 0)))))
+                  (when field-names 
+                    (setf col-names (loop for n from 0 below n-col
+                                          collect (sqlite3:sqlite3-column-name stmt n))))
+                  (push (extract-row-data) rows)
+                  (do* () (nil)
+                    (if (sqlite3:sqlite3-step stmt)
+                        (push (extract-row-data) rows)
+                        (return))))))
+              (sqlite3:sqlite3-finalize stmt))
+       (values (nreverse rows) col-names))
+    (sqlite3:sqlite3-error (err)
+      (error 'sql-database-data-error
+            :database database
+            :expression query-expression
+            :error-id (sqlite3:sqlite3-error-code err)
+            :message (sqlite3:sqlite3-error-message err)))))
+
+;;; Object listing
+
+(defmethod database-list-tables-and-sequences ((database sqlite3-database) &key owner)
+  (declare (ignore owner))
+  ;; Query is copied from .table command of sqlite3 command line utility.
+  (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 nil nil)))
+
+(defmethod database-list-tables ((database sqlite3-database) &key owner)
+  (remove-if #'(lambda (s)
+                 (and (>= (length s) 11)
+                      (string-equal (subseq s 0 11) "_CLSQL_SEQ_")))
+            (database-list-tables-and-sequences database :owner owner)))
+
+(defmethod database-list-views ((database sqlite3-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 nil)))
+
+(defmethod database-list-indexes ((database sqlite3-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 nil)))
+
+(defmethod database-list-table-indexes (table (database sqlite3-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 sqlite3-table-info))
+(defun sqlite3-table-info (table database)
+  (database-query (format nil "PRAGMA table_info('~A')" table)
+                 database nil nil))
+
+(defmethod database-list-attributes (table (database sqlite3-database)
+                                           &key (owner nil))
+  (declare (ignore owner))
+  (mapcar #'(lambda (table-info) (second table-info))
+         (sqlite3-table-info table database)))
+
+(defmethod database-attribute-type (attribute table 
+                                   (database sqlite3-database)
+                                    &key (owner nil))
+  (declare (ignore owner))
+  (loop for field-info in (sqlite3-table-info table database)
+      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)))))
+
+(defmethod database-create (connection-spec (type (eql :sqlite3)))
+  (declare (ignore connection-spec))
+  ;; databases are created automatically by Sqlite3
+  t)
+
+(defmethod database-destroy (connection-spec (type (eql :sqlite3)))
+  (destructuring-bind (name) connection-spec
+    (if (probe-file name)
+       (delete-file name)
+       nil)))
+
+(defmethod database-probe (connection-spec (type (eql :sqlite3)))
+  (destructuring-bind (name) connection-spec
+    ;; TODO: Add a test that this file is a real sqlite3 database
+    (or (string-equal ":memory:" name)
+       (and (probe-file name) t))))
+
+;;; Database capabilities
+
+(defmethod db-type-has-boolean-where? ((db-type (eql :sqlite3)))
+  nil)
index b1a89341af6499018a7025cc7c4b0474519b1649..84d09d3d0bbceb2d4da08110cff0a66f39c1393c 100644 (file)
                                :fdml/select/21 :fdml/select/32 
                                 :fdml/select/33))
           (push (cons test "not supported by sqlite") skip-tests))
+         ((and (eql *test-database-type* :sqlite3)
+               (clsql-sys:in test :fddl/view/4 :fdml/select/10
+                             :fdml/select/21 :fdml/select/32 
+                             :fdml/select/33))
+          (push (cons test "not supported by sqlite3") skip-tests))
          ((and (not (clsql-sys:db-type-has-bigint? db-type))
                (clsql-sys:in test :basic/bigint/1))
           (push (cons test "bigint not supported") skip-tests))
index eb10ec33fe989ae5cd7751f0bd4a48af1ea63972..948d75a53b034d517a6ed28b0ba734360bbb775e 100644 (file)
@@ -24,7 +24,7 @@
                 :type "config"))
 
 (defvar +all-db-types+
-  '(:postgresql :postgresql-socket :mysql :sqlite :odbc :oracle
+  '(:postgresql :postgresql-socket :mysql :sqlite :sqlite3 :odbc :oracle
     #+allegro :aodbc))
 
 (defclass conn-specs ()
@@ -33,6 +33,7 @@
    (postgresql :accessor postgresql-spec :initform nil)
    (postgresql-socket :accessor postgresql-socket-spec :initform nil)
    (sqlite :accessor sqlite-spec :initform nil)
+   (sqlite3 :accessor sqlite3-spec :initform nil)
    (odbc :accessor odbc-spec :initform nil)
    (oracle :accessor oracle-spec :initform nil))
   (:documentation "Connection specs for CLSQL testing"))