From fd3c9c9f21ff40904bf27eb7a797bbd6c2d80630 Mon Sep 17 00:00:00 2001 From: "Kevin M. Rosenberg" Date: Wed, 10 Mar 2004 22:33:14 +0000 Subject: [PATCH] r8710: new backend --- ChangeLog | 3 + clsql-sqlite.asd | 41 ++++ clsql-tests.asd | 7 +- db-sqlite/sqlite-api-clisp.lisp | 351 ++++++++++++++++++++++++++++++++ db-sqlite/sqlite-api-uffi.lisp | 311 ++++++++++++++++++++++++++++ db-sqlite/sqlite-loader.lisp | 52 +++++ db-sqlite/sqlite-package.lisp | 23 +++ db-sqlite/sqlite-sql.lisp | 179 ++++++++++++++++ db-sqlite/sqlite-usql.lisp | 70 +++++++ debian/changelog | 6 + doc/html.tar.gz | Bin 27314 -> 27205 bytes tests/tests.lisp | 46 ++++- 12 files changed, 1080 insertions(+), 9 deletions(-) create mode 100644 clsql-sqlite.asd create mode 100644 db-sqlite/sqlite-api-clisp.lisp create mode 100644 db-sqlite/sqlite-api-uffi.lisp create mode 100644 db-sqlite/sqlite-loader.lisp create mode 100644 db-sqlite/sqlite-package.lisp create mode 100644 db-sqlite/sqlite-sql.lisp create mode 100644 db-sqlite/sqlite-usql.lisp diff --git a/ChangeLog b/ChangeLog index 45929d0..554b73d 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,6 @@ +10 Mar 2004 Kevin Rosenberg (kevin@rosenberg.net) + * Integrate patch from Aurelio Bignoli for SQLite backend + 11 Nov 2003 Kevin Rosenberg (kevin@rosenberg.net) * Converted documentation to XML format * Made package installable with asdf-install diff --git a/clsql-sqlite.asd b/clsql-sqlite.asd new file mode 100644 index 0000000..c3840fb --- /dev/null +++ b/clsql-sqlite.asd @@ -0,0 +1,41 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: clsql-postgresql.asd +;;;; Purpose: ASDF file for CLSQL SQLite backend +;;;; Programmer: Aurelio Bignoli +;;;; Date Started: Aug 2003 +;;;; +;;;; $Id: clsql-sqlite.asd,v 1.5 2004/03/09 20:55:11 aurelio Exp $ +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2003 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-sqlite-system (:use #:asdf #:cl)) +(in-package #:clsql-sqlite-system) + +(defsystem clsql-sqlite + :name "cl-sql-sqlite" + :author "Aurelio Bignoli " + :maintainer "Aurelio Bignoli" + :licence "Lessor Lisp General Public License" + :description "Common Lisp SQLite Driver" + :long-description "cl-sql-sqlite package provides a database driver to SQLite database library." + + :components + ((:module :db-sqlite + :components + ((:file "sqlite-package") + (:file "sqlite-loader" :depends-on ("sqlite-package")) + (:file #+clisp "sqlite-api-clisp" + #-clisp "sqlite-api-uffi" + :depends-on ("sqlite-loader")) + (:file "sqlite-sql" :depends-on (#+clisp "sqlite-api-clisp" + #-clisp "sqlite-api-uffi")) + (:file "sqlite-usql" :depends-on ("sqlite-sql"))))) + :depends-on (#-clisp :uffi + :clsql-base)) diff --git a/clsql-tests.asd b/clsql-tests.asd index a9c8729..6da0173 100644 --- a/clsql-tests.asd +++ b/clsql-tests.asd @@ -21,9 +21,12 @@ :licence "Lessor Lisp General Public License" :description "Testing suite for CLSQL" - :depends-on (:clsql :clsql-mysql :clsql-postgresql :clsql-postgresql-socket + :depends-on (:clsql #-clisp :clsql-mysql + #-clisp :clsql-postgresql + #-clisp :clsql-postgresql-socket :ptester - #+(and allegro (not allegro-cl-trial)) :clsql-aodbc) + #+(and allegro (not allegro-cl-trial)) :clsql-aodbc + :clsql-sqlite) :components ((:module tests :components diff --git a/db-sqlite/sqlite-api-clisp.lisp b/db-sqlite/sqlite-api-clisp.lisp new file mode 100644 index 0000000..101f5b4 --- /dev/null +++ b/db-sqlite/sqlite-api-clisp.lisp @@ -0,0 +1,351 @@ +;; sqlite.lisp --- CLISP FFI for SQLite (http://www.sqlite.org). + +;; Copyright (C) 2003 Aurelio Bignoli + +;; This program is free software; you can redistribute it and/or +;; modify it under the terms of the GNU General Public License as +;; published by the Free Software Foundation; either version 2 of +;; the License, or (at your option) any later version. + +;; This program is distributed in the hope that it will be +;; useful, but WITHOUT ANY WARRANTY; without even the implied +;; warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR +;; PURPOSE. See the GNU General Public License for more details. + +;; You should have received a copy of the GNU General Public +;; License along with this program; if not, write to the Free +;; Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, +;; MA 02111-1307 USA + +;; $Id: sqlite.lisp,v 1.4 2003/11/28 21:02:43 aurelio Exp $ + +(in-package :cl-user) + +(defpackage :sqlite + (:use :common-lisp :ffi) + (:export + ;;; Conditions + #:sqlite-error + #:sqlite-error-code + #:sqlite-error-message + + ;;; Core API. + #:sqlite-open + #:sqlite-close + + ;;; New API. + #:sqlite-compile + #:sqlite-step + #:sqlite-finalize + + ;;; Extended API. + #:sqlite-get-table + #:sqlite-version ; Defined as constant. + #:sqlite-encoding ; Defined as constant. + #:sqlite-last-insert-rowid + + ;;; Utility functions (used by CLSQL) + #:make-null-row + #:null-row-p + + ;;; Macros. + #:with-open-sqlite-db + #:with-sqlite-vm)) + +(in-package :sqlite) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; 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 sqlite_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 "(Internal Only) Database table 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-ROW 100 "sqlite_step() has another row ready") +(defconstant SQLITE-DONE 101 "sqlite_step() has finished executing") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; C types. +;;;; +(def-c-type sqlite-db c-pointer) +(def-c-type sqlite-vm c-pointer) +(def-c-type error-message (c-ptr c-pointer)) + ; It is not NULL only in case of error. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Conditions. +;;;; +(define-condition sqlite-error () + ((message :initarg :message :reader sqlite-error-message :initform "") + (code :initarg :code :reader sqlite-error-code)) + (:report (lambda (condition stream) + (let ((code (sqlite-error-code condition))) + (format stream "SQLite error [~A] - ~A : ~A" + code (error-string code) + (sqlite-error-message condition)))))) + +(defun signal-sqlite-error (code message) + (let ((condition + (make-condition 'sqlite-error + :code code + :message + (typecase message + (string message) + (t (error-message-as-string message)))))) + (unless (signal condition) + (invoke-debugger condition)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Library functions. +;;;; +(defmacro def-sqlite-call-out (name &rest args) + `(def-call-out ,name + (:language :stdc) + (:library "libsqlite.so") + ,@args)) + +(def-sqlite-call-out error-string + (:name "sqlite_error_string") + (:arguments + (error-code int :in)) + (:return-type c-string)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Core API. +;;;; +(def-sqlite-call-out %open + (:name "sqlite_open") + (:arguments + (dbname c-string :in) + (mode int :in) + (errmsg error-message :out)) + (:return-type sqlite-db)) + +(def-sqlite-call-out sqlite-close + (:name "sqlite_close") + (:arguments (db sqlite-db :in)) + (:return-type nil)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; New API. +;;;; +(def-sqlite-call-out %compile + (:name "sqlite_compile") + (:arguments + (db sqlite-db :in) + (sql c-string :in) + (sql-tail (c-ptr c-string) :out) + (vm (c-ptr sqlite-vm) :out) + (errmsg error-message :out)) + (:return-type int)) + +(def-sqlite-call-out %step + (:name "sqlite_step") + (:arguments + (vm sqlite-vm :in) + (cols-n (c-ptr int) :out) + (cols (c-ptr c-pointer) :out) + (col-names (c-ptr c-pointer) :out)) + (:return-type int)) + +(def-sqlite-call-out %finalize + (:name "sqlite_finalize") + (:arguments + (vm sqlite-vm :in) + (errmsg error-message :out)) + (:return-type int)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Extended API. +;;;; +(def-sqlite-call-out sqlite-last-insert-rowid + (:name "sqlite_last_insert_rowid") + (:arguments + (db sqlite-db :in)) + (:return-type int)) + +(def-sqlite-call-out %get-table + (:name "sqlite_get_table") + (:arguments + (db sqlite-db :in) + (sql c-string :in) + (result (c-ptr c-pointer) :out) + (n-row (c-ptr int) :out) + (n-column (c-ptr int) :out) + (errmsg error-message :out)) + (:return-type int)) + +(def-sqlite-call-out %free-table + (:name "sqlite_free_table") + (:arguments + (rows c-pointer :in)) + (:return-type nil)) + +(def-c-var %version + (:name "sqlite_version") + (:library "libsqlite.so") + (:type (c-array-max char 32)) + (:read-only t)) + +(def-c-var %encoding + (:name "sqlite_encoding") + (:library "libsqlite.so") + (:type (c-array-max char 32)) + (:read-only t)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Wrapper functions. +;;;; +(defconstant sqlite-version + (ext:convert-string-from-bytes %version custom:*terminal-encoding*)) + +(defconstant sqlite-encoding + (ext:convert-string-from-bytes %encoding custom:*terminal-encoding*)) + +(defun error-message-as-string (p) + (with-c-var (p1 'c-pointer p) + (prog1 + (cast p1 'c-string) + (foreign-free p1)))) + +(defun sqlite-open (db-name &optional (mode 0)) + (multiple-value-bind (db error-message) + (%open db-name mode) + (if db + db + (signal-sqlite-error SQLITE-ERROR error-message)))) + +(defun c-pointer-to-string-array (p element-n) + (if (null p) + p + (with-c-var (p1 'c-pointer p) + (cast p1 `(c-ptr (c-array c-string ,element-n)))))) + +(defun sqlite-compile (db sql) + (multiple-value-bind (result sql-tail vm error-message) + (%compile db sql) + (declare (ignore sql-tail)) + (if (= result SQLITE-OK) + vm + (signal-sqlite-error result error-message)))) + +(defun sqlite-step (vm) + (multiple-value-bind (result n-col cols col-names) + (%step vm) + (cond + ((= result SQLITE-ROW) + (values n-col (c-pointer-to-string-array cols n-col) + (c-pointer-to-string-array col-names (* 2 n-col)))) + ((= result SQLITE-DONE) (values 0 nil nil)) + (t (signal-sqlite-error result "sqlite-step"))))) + +(defun sqlite-finalize (vm) + (multiple-value-bind (result error-message) + (%finalize vm) + (if (= result SQLITE-OK) + t + (signal-sqlite-error result error-message)))) + +(defun sqlite-get-table (db sql) + (multiple-value-bind (result rows n-row n-col error-message) + (%get-table db sql) + (if (= result SQLITE-OK) + (let ((x (c-pointer-to-string-array rows (* (1+ n-row) n-col)))) + (%free-table rows) + (values x n-row n-col)) + (signal-sqlite-error result error-message)))) + +(defmacro with-open-sqlite-db ((db dbname &key (mode 0)) &body body) + (let ((error-message (gensym))) + `(multiple-value-bind (,db ,error-message) + (sqlite-open ,dbname ,mode) + (if (null ,db) + (signal-sqlite-error SQLITE-ERROR ,error-message) + (unwind-protect + (progn ,@body) + (sqlite-close ,db)))))) + +(defmacro with-sqlite-vm ((vm db sql) &body body) + `(let ((,vm (sqlite-compile ,db ,sql))) + (unwind-protect + (progn ,@body) + (sqlite-finalize ,vm)))) + +(declaim (inline null-row-p)) +(defun null-row-p (row) + (null row)) + +(declaim (inline make-null-row)) +(defun make-null-row () + nil) + +#+nil +(defun test-function (db-name) + (with-open-sqlite-db (db db-name) + (let ((x (sqlite-get-table db "select * from sqlite_master;"))) + (with-sqlite-vm (vm db "select * from sqlite_master;") + (let ((error-n 0)) + (loop for i = 1 then (1+ i) + do (multiple-value-bind (n-col cols col-names) + (sqlite-step vm) + (declare (ignore col-names)) + (if (= n-col 0) + (return-from nil) + (loop for j from 0 to (1- n-col) + for j1 = (* n-col i) then (1+ j1) + do + (when (string/= (aref x j1) (aref cols j)) + (format t "~&row=~A, col=~A: ~A - ~A~%" + i j + (aref x j1) (aref cols j)) + (incf error-n)))))) + (if (= error-n 0) + (format t "~&Test passed!~%") + (format t "~&Test not passed. ~A errors" error-n))))))) + +(defun get-column-types (db-name table-name) + (with-open-sqlite-db (db db-name) + (with-sqlite-vm (vm db (format nil "pragma table_info('~A')" table-name)) + (loop + (multiple-value-bind (n-col cols col-names) + (sqlite-step vm) + (declare (ignore col-names)) + (if (= n-col 0) + (return-from nil) + (format t "~&column name = ~A, type = ~A~%" + (aref cols 1) (aref cols 2)))))))) + +;;;; Local Variables: +;;;; Mode: lisp +;;;; Syntax: ANSI-Common-Lisp +;;;; Package: sqlite +;;;; End: \ No newline at end of file diff --git a/db-sqlite/sqlite-api-uffi.lisp b/db-sqlite/sqlite-api-uffi.lisp new file mode 100644 index 0000000..90f8cef --- /dev/null +++ b/db-sqlite/sqlite-api-uffi.lisp @@ -0,0 +1,311 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: sqlite-api-uffi.lisp +;;;; Purpose: Low-level SQLite interface using UFFI +;;;; Programmers: Aurelio Bignoli +;;;; Date Started: Nov 2003 +;;;; +;;;; $Id: sqlite-api-uffi.lisp,v 1.5 2004/03/09 20:57:19 aurelio Exp $ +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2003 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. +;;;; ************************************************************************* +(declaim (optimize (debug 0) (speed 3) (safety 0) (compilation-speed 0))) + +(in-package :cl-user) + +(defpackage :sqlite + (:use :common-lisp :uffi) + (:export + ;;; Conditions + #:sqlite-error + #:sqlite-error-code + #:sqlite-error-message + + ;;; Core API. + #:sqlite-open + #:sqlite-close + + ;;; New API. + #:sqlite-compile + #:sqlite-step + #:sqlite-finalize + + ;;; Extended API. + #:sqlite-get-table + #:sqlite-free-table + #:sqlite-version ; Defined as constant. + #:sqlite-encoding ; Defined as constant. + #:sqlite-last-insert-rowid + + ;;; Utility functions. + #:make-null-row + #:make-null-vm + #:null-row-p + #:sqlite-aref + #:sqlite-free-row + + ;;; Types. + #:sqlite-row + #:sqlite-row-pointer + #:sqlite-vm-pointer)) + +(in-package :sqlite) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; 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-ROW 100 "sqlite_step() has another row ready") +(defconstant SQLITE-DONE 101 "sqlite_step() has finished executing") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Conditions. +;;;; +(define-condition sqlite-error () + ((message :initarg :message :reader sqlite-error-message :initform "") + (code :initarg :code :reader sqlite-error-code)) + (:report (lambda (condition stream) + (let ((code (sqlite-error-code condition))) + (format stream "SQLite error [~A]: ~A" + code (sqlite-error-message condition)))))) + +(defun signal-sqlite-error (code &optional message) + (let ((condition + (make-condition 'sqlite-error + :code code + :message (if message + message + (sqlite-error-string code))))) + (unless (signal condition) + (invoke-debugger condition)))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Foreign types definitions. +;;;; +(def-foreign-type sqlite-db :pointer-void) +(def-foreign-type sqlite-vm :pointer-void) +(def-foreign-type errmsg :cstring) + +(def-array-pointer string-array-pointer :cstring) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;; +;;; Lisp types used in declarations. +;;;; +(def-type sqlite-db-pointer '(* sqlite-db)) +(def-type sqlite-int-pointer '(* :int)) +(def-type sqlite-row 'string-array-pointer) +(def-type sqlite-row-pointer '(* string-array-pointer)) +(def-type sqlite-vm-pointer '(* sqlite-vm)) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Library functions. +;;;; +(defmacro def-sqlite-function (name args &key (returning :void)) + `(def-function ,name ,args + :module "sqlite" + :returning ,returning)) + +(def-sqlite-function + "sqlite_error_string" + ((error-code :int)) + :returning :cstring) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Core API. +;;;; +(declaim (inline %open)) +(def-sqlite-function + ("sqlite_open" %open) + ((dbname :cstring) + (mode :int) + (error-message '(* errmsg))) + :returning sqlite-db) + +(declaim (inline sqlite-close)) +(def-sqlite-function + "sqlite_close" + ((db sqlite-db))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; New API. +;;;; +(declaim (inline %compile)) +(def-sqlite-function + ("sqlite_compile" %compile) + ((db sqlite-db) + (sql :cstring) + (sql-tail '(* :cstring)) + (vm '(* sqlite-vm)) + (error-message '(* errmsg))) + :returning :int) + +(declaim (inline %step)) +(def-sqlite-function + ("sqlite_step" %step) + ((vm sqlite-vm) + (cols-n '(* :int)) + (cols '(* (* :cstring))) + (col-names '(* (* :cstring)))) + :returning :int) + +(declaim (inline %finalize)) +(def-sqlite-function + ("sqlite_finalize" %finalize) + ((vm sqlite-vm) + (error-message '(* errmsg))) + :returning :int) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Extended API. +;;;; +(declaim (inline sqlite-last-insert-rowid)) +(def-sqlite-function + "sqlite_last_insert_rowid" + ((db 'sqlite-db)) + :returning :int) + +(declaim (inline %get-table)) +(def-sqlite-function + ("sqlite_get_table" %get-table) + ((db sqlite-db) + (sql :cstring) + (result '(* (* :cstring))) + (rows-n '(* :int)) + (cols-n '(* :int)) + (error-message '(* errmsg))) + :returning :int) + +(declaim (inline %free-table)) +(def-sqlite-function + ("sqlite_free_table" %free-table) + ((rows :pointer-void))) + +(declaim (inline sqlite-libversion)) +(def-sqlite-function + "sqlite_libversion" + () + :returning :cstring) + +(declaim (inline sqlite-libencoding)) +(def-sqlite-function + "sqlite_libencoding" + () + :returning :cstring) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Wrapper functions. +;;;; +(defparameter sqlite-version (sqlite-libversion)) +(defparameter sqlite-encoding (sqlite-libencoding)) + +(defun sqlite-open (db-name &optional (mode 0)) + (let ((db (%open db-name mode nil))) + (declare (type sqlite-db-pointer db)) + (if (null-pointer-p db) + (signal-sqlite-error SQLITE-ERROR + (format nil "unable to open ~A" db-name)) + db))) + +(defun sqlite-compile (db sql) + (declare (type sqlite-db-pointer db)) + (let ((vm (allocate-foreign-object 'sqlite-vm))) + (with-foreign-object (sql-tail :cstring) + (let ((result (%compile db sql sql-tail vm nil))) + (if (= result SQLITE-OK) + vm + (progn + (free-foreign-object vm) + (signal-sqlite-error result))))))) + +(defun sqlite-step (vm) + (declare (type sqlite-vm-pointer vm)) + (with-foreign-object (cols-n :int) + (let ((cols (allocate-foreign-object '(* :cstring))) + (col-names (allocate-foreign-object '(* :cstring)))) + (declare (type sqlite-row-pointer cols col-names)) + (let ((result (%step (deref-pointer vm 'sqlite-vm) + cols-n cols col-names))) + (cond + ((= result SQLITE-ROW) + (let ((n (deref-pointer cols-n :int))) + (values n cols col-names))) + ((= result SQLITE-DONE) + (free-foreign-object cols) + (free-foreign-object col-names) + (values 0 (make-null-pointer 'string-array-pointer) + (make-null-pointer 'string-array-pointer))) + (t + (free-foreign-object cols) + (free-foreign-object col-names) + (signal-sqlite-error result))))))) + +(defun sqlite-finalize (vm) + (declare (type sqlite-vm-pointer vm)) + (let ((result (%finalize (deref-pointer vm 'sqlite-vm) nil))) + (if (= result SQLITE-OK) + (progn + (free-foreign-object vm) + t) + (signal-sqlite-error result)))) + +(defun sqlite-get-table (db sql) + (declare (type sqlite-db-pointer db)) + (let ((rows (allocate-foreign-object '(* :cstring)))) + (with-foreign-object (rows-n :int) + (with-foreign-object (cols-n :int) + (declare (type sqlite-row-pointer rows)) + (let ((result (%get-table db sql rows rows-n cols-n nil))) + (if (= result SQLITE-OK) + (let ((cn (deref-pointer cols-n :int)) + (rn (deref-pointer rows-n :int))) + (values rows rn cn)) + (progn + (free-foreign-object rows) + (signal-sqlite-error result)))))))) + +(declaim (inline sqlite-free-table)) +(defun sqlite-free-table (table) + (declare (type sqlite-row-pointer table)) + (%free-table (deref-pointer table 'sqlite-row-pointer))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;;;; +;;;; Utility functions. +;;;; +(declaim (inline make-null-row)) +(defun make-null-row () + (uffi:make-null-pointer 'string-array-pointer)) + +(declaim (inline make-null-vm)) +(defun make-null-vm () + (uffi:make-null-pointer 'sqlite-vm)) + +(declaim (inline null-row-p)) +(defun null-row-p (row) + (null-pointer-p row)) + +(declaim (inline sqlite-aref)) +(defun sqlite-aref (a n) + (declare (type sqlite-row-pointer a)) + (deref-array (deref-pointer a 'sqlite-row-pointer) '(:array :cstring) n)) + +(declaim (inline sqlite-free-row)) +(defun sqlite-free-row (row) + (declare (type sqlite-row-pointer row)) + (free-foreign-object row)) diff --git a/db-sqlite/sqlite-loader.lisp b/db-sqlite/sqlite-loader.lisp new file mode 100644 index 0000000..5b1eef9 --- /dev/null +++ b/db-sqlite/sqlite-loader.lisp @@ -0,0 +1,52 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: sqlite-loader.lisp +;;;; Purpose: SQLite library loader using UFFI +;;;; Programmer: Aurelio Bignoli +;;;; Date Started: Nov 2003 +;;;; +;;;; $Id: sqlite-loader.lisp,v 1.2 2003/12/03 14:07:31 aurelio Exp $ +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2003 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-sqlite) + +(defvar *sqlite-supporting-libraries* '("c") + "Used only by CMU. List of library flags needed to be passed to ld +to load the SQLite library succesfully. If this differs at your site, +set to the right path before compiling or loading the system.") + +(defvar *sqlite-library-loaded* #+clisp t + #-clisp nil + "T if foreign library was able to be loaded successfully") + +(defmethod database-type-library-loaded ((database-type (eql :sqlite))) + "T if foreign library was able to be loaded successfully. " + *sqlite-library-loaded*) + +(defmethod database-type-load-foreign ((database-type (eql :sqlite))) + #+clisp + t + #-clisp + (let ((libpath (uffi:find-foreign-library + "libsqlite" + '("/usr/lib/" "/usr/local/lib/") + :drive-letters '("C" "D" "E")))) + (if (uffi:load-foreign-library libpath + :module "sqlite" + :supporting-libraries + *sqlite-supporting-libraries*) + (setq *sqlite-library-loaded* t) + (warn "Can't load SQLite library ~A" libpath)))) + +(clsql-base-sys:database-type-load-foreign :sqlite) + + + diff --git a/db-sqlite/sqlite-package.lisp b/db-sqlite/sqlite-package.lisp new file mode 100644 index 0000000..00c4d97 --- /dev/null +++ b/db-sqlite/sqlite-package.lisp @@ -0,0 +1,23 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: sqlite-package.lisp +;;;; Purpose: Package definition for low-level SQLite interface +;;;; Programmer: Aurelio Bignoli +;;;; Date Started: Aug 2003 +;;;; +;;;; $Id: sqlite-package.lisp,v 1.2 2003/11/27 20:23:26 aurelio Exp $ +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2003 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-sqlite + (:use :common-lisp :clsql-base-sys) + (:export #:sqlite-database)) diff --git a/db-sqlite/sqlite-sql.lisp b/db-sqlite/sqlite-sql.lisp new file mode 100644 index 0000000..78068fb --- /dev/null +++ b/db-sqlite/sqlite-sql.lisp @@ -0,0 +1,179 @@ +;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: sqlite-sql.lisp +;;;; Purpose: High-level SQLite interface +;;;; Programmers: Aurelio Bignoli +;;;; Date Started: Aug 2003 +;;;; +;;;; $Id: sqlite-sql.lisp,v 1.5 2004/03/09 20:57:44 aurelio Exp $ +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2003 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. +;;;; ************************************************************************* + +(declaim (optimize (speed 3) (debug 0) (safety 0))) + +(in-package :clsql-sqlite) + +(defclass sqlite-database (database) + ((sqlite-db :initarg :sqlite-db :accessor sqlite-db))) + +(defmethod database-initialize-database-type ((database-type (eql :sqlite))) + t) + +(defun check-sqlite-connection-spec (connection-spec) + (check-connection-spec connection-spec :sqlite (name))) + +(defmethod database-name-from-spec (connection-spec + (database-type (eql :sqlite))) + (check-sqlite-connection-spec connection-spec) + (first connection-spec)) + +(defmethod database-connect (connection-spec (database-type (eql :sqlite))) + (check-sqlite-connection-spec connection-spec) + (handler-case + (make-instance 'sqlite-database + :name (database-name-from-spec connection-spec :sqlite) + :sqlite-db (sqlite:sqlite-open (first connection-spec))) + (sqlite:sqlite-error (err) + (error 'clsql-connect-error + :database-type database-type + :connection-spec connection-spec + :errno (sqlite:sqlite-error-code err) + :error (sqlite:sqlite-error-message err))))) + +(defmethod database-disconnect ((database sqlite-database)) + (sqlite:sqlite-close (sqlite-db database)) + (setf (sqlite-db database) nil) + t) + +(defmethod database-execute-command (sql-expression (database sqlite-database)) + (handler-case + (multiple-value-bind (data row-n col-n) + (sqlite:sqlite-get-table (sqlite-db database) sql-expression) + #+clisp (declare (ignore data)) + #-clisp (sqlite:sqlite-free-table data) + (unless (= row-n 0) + (error 'clsql-simple-warning + :format-control + "Result set not empty: ~@(~A~) row~:P, ~@(~A~) column~:P " + :format-arguments (list row-n col-n)))) + (sqlite:sqlite-error (err) + (error 'clsql-sql-error + :database database + :expression sql-expression + :errno (sqlite:sqlite-error-code err) + :error (sqlite:sqlite-error-message err)))) + t) + +(defmethod database-query (query-expression (database sqlite-database) types) + (declare (ignore 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 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)) + )) + (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))))) + +#-clisp +(defstruct sqlite-result-set + (vm (sqlite:make-null-vm) + :type sqlite:sqlite-vm-pointer) + (first-row (sqlite:make-null-row) + :type sqlite:sqlite-row-pointer) + (n-col 0 :type fixnum)) +#+clisp +(defstruct sqlite-result-set + (vm nil) + (first-row nil) + (n-col 0 :type fixnum)) + +(defmethod database-query-result-set + (query-expression (database sqlite-database) &key full-set types) + (declare (ignore full-set types)) + (handler-case + (let* ((vm (sqlite:sqlite-compile (sqlite-db database) + query-expression)) + (result-set (make-sqlite-result-set :vm vm))) + #-clisp (declare (type sqlite:sqlite-vm-pointer vm)) + + ;;; To obtain column number we have to read the first row. + (multiple-value-bind (n-col cols col-names) + (sqlite:sqlite-step vm) + (declare (ignore col-names) + #-clisp (type sqlite:sqlite-row-pointer cols) + ) + (setf (sqlite-result-set-first-row result-set) cols + (sqlite-result-set-n-col result-set) n-col) + (values result-set n-col nil))) + (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))))) + +(defmethod database-dump-result-set (result-set (database sqlite-database)) + (declare (ignore database)) + (handler-case + (sqlite:sqlite-finalize (sqlite-result-set-vm result-set)) + (sqlite:sqlite-error (err) + (error 'clsql-simple-error + :format-control "Error finalizing SQLite VM: ~A" + :format-arguments (list (sqlite:sqlite-error-message err)))))) + +(defmethod database-store-next-row (result-set (database sqlite-database) list) + (let ((n-col (sqlite-result-set-n-col result-set))) + (if (= n-col 0) + ;; empty result set + nil + (let ((row (sqlite-result-set-first-row result-set))) + (if (sqlite:null-row-p row) + ;; First row already used. fetch another row from DB. + (handler-case + (multiple-value-bind (n new-row col-names) + (sqlite:sqlite-step (sqlite-result-set-vm result-set)) + (declare (ignore n col-names) + #-clisp (type sqlite:sqlite-row-pointer new-row) + ) + (if (sqlite:null-row-p new-row) + (return-from database-store-next-row nil) + (setf row new-row))) + (sqlite:sqlite-error (err) + (error 'clsql-simple-error + :format-control "Error in sqlite-step: ~A" + :format-arguments + (list (sqlite:sqlite-error-message err))))) + + ;; Use the row previously read by database-query-result-set. + (setf (sqlite-result-set-first-row result-set) + (sqlite:make-null-row))) + (loop for i = 0 then (1+ i) + for rest on list + do (setf (car rest) + (#+clisp aref + #-clisp sqlite:sqlite-aref + row i))) + #-clisp (sqlite:sqlite-free-row row) + t)))) diff --git a/db-sqlite/sqlite-usql.lisp b/db-sqlite/sqlite-usql.lisp new file mode 100644 index 0000000..4d66be7 --- /dev/null +++ b/db-sqlite/sqlite-usql.lisp @@ -0,0 +1,70 @@ +;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*- +;;;; ************************************************************************* +;;;; FILE IDENTIFICATION +;;;; +;;;; Name: sqlite-usql.lisp +;;;; Purpose: SQLite interface for USQL routines +;;;; Programmers: Aurelio Bignoli +;;;; Date Started: Aug 2003 +;;;; +;;;; $Id: sqlite-usql.lisp,v 1.3 2004/03/09 20:58:38 aurelio Exp $ +;;;; +;;;; This file, part of CLSQL, is Copyright (c) 2003 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-sqlite) + +(defun %sequence-name-to-table-name (sequence-name) + (concatenate 'string "_usql_seq_" (sql-escape sequence-name))) + +(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)") + database) + (database-execute-command + (format nil "INSERT INTO ~A VALUES (-1)" table-name) + database))) + +(defmethod database-drop-sequence (sequence-name + (database sqlite-database)) + (database-execute-command + (concatenate 'string "DROP TABLE " + (%sequence-name-to-table-name sequence-name)) + database)) + +(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))) + +(defmethod database-list-tables ((database sqlite-database) &key system-tables) + (declare (ignore system-tables)) + ;; Query is copied from .table command of sqlite comamnd 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 '()))) + +(declaim (inline sqlite-table-info)) +(defun sqlite-table-info (table database) + (database-query (format nil "PRAGMA table_info('~A')" table) + database '())) + +(defmethod database-list-attributes (table (database sqlite-database)) + (mapcar #'(lambda (table-info) (third table-info)) + (sqlite-table-info table database))) + +(defmethod database-attribute-type (attribute table + (database sqlite-database)) + (loop for field-info in (sqlite-table-info table database) + when (string= attribute (second field-info)) + return (third field-info))) diff --git a/debian/changelog b/debian/changelog index 6627c26..22e69c3 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +cl-sql (1.9.0-1) unstable; urgency=low + + * Add SQLlite backend as contributed by Aurelio Bignoli + + -- Kevin M. Rosenberg Wed, 10 Mar 2004 15:19:46 -0700 + cl-sql (1.8.7-1) unstable; urgency=low * New upstream diff --git a/doc/html.tar.gz b/doc/html.tar.gz index 8a1521e8bd0ee9bad38ba3139d4b061810e22c6b..0e952d12f63907ff6ced96eaa3f99a5047e5a5ca 100644 GIT binary patch delta 27081 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 b|J7<*?6C0fe8=4}1a?}w_!;U17V5tMnF6l? diff --git a/tests/tests.lisp b/tests/tests.lisp index dd609f0..76d0dd9 100644 --- a/tests/tests.lisp +++ b/tests/tests.lisp @@ -26,7 +26,8 @@ ;;; ((:mysql ("localhost" "a-mysql-db" "user1" "secret")) ;;; (:aodbc ("my-dsn" "a-user" "pass")) ;;; (:postgresql ("localhost" "another-db" "user2" "dont-tell")) -;;; (:postgresql-socket ("pg-server" "a-db-name" "user" "secret-password"))) +;;; (:postgresql-socket ("pg-server" "a-db-name" "user" "secret-password")) +;;; (:sqlite ("path-to-sqlite-db"))) (in-package :clsql-tests) @@ -40,7 +41,8 @@ ((aodbc-spec :accessor aodbc-spec) (mysql-spec :accessor mysql-spec) (pgsql-spec :accessor pgsql-spec) - (pgsql-socket-spec :accessor pgsql-socket-spec)) + (pgsql-socket-spec :accessor pgsql-socket-spec) + (sqlite-spec :accessor sqlite-spec)) (:documentation "Test fixture for CLSQL testing")) @@ -54,6 +56,7 @@ (setf (pgsql-spec specs) (cadr (assoc :postgresql config))) (setf (pgsql-socket-spec specs) (cadr (assoc :postgresql-socket config))) + (setf (sqlite-spec specs) (cadr (assoc :sqlite config))) specs)) (progn (warn "CLSQL tester config file ~S not found" path) @@ -71,6 +74,9 @@ (defmethod pgsql-socket-table-test ((test conn-specs)) (test-table (pgsql-socket-spec test) :postgresql-socket)) +(defmethod sqlite-table-test ((test conn-specs)) + (test-table (sqlite-spec test) :sqlite)) + (defmethod test-table (spec type) (when spec (let ((db (clsql:connect spec :database-type type :if-exists :new))) @@ -105,8 +111,32 @@ ) (disconnect :database db))))) +;;; +;;; SQLite is typeless: execute untyped tests only. +;;; +(defmethod test-table (spec (type (eql :sqlite))) + (when spec + (let ((db (clsql:connect spec :database-type type :if-exists :new))) + (unwind-protect + (progn + (create-test-table db) + (dolist (row (query "select * from test_clsql" :database db :types nil)) + (test-table-row row nil type)) + (loop for row across (map-query 'vector #'list "select * from test_clsql" + :database db :types nil) + do (test-table-row row nil type)) + (loop for row in (map-query 'list #'list "select * from test_clsql" + :database db :types nil) + do (test-table-row row nil type)) + + (do-query ((int float bigint str) "select * from test_clsql") + (test-table-row (list int float bigint str) nil type)) + (drop-test-table db) + ) + (disconnect :database db))))) (defmethod mysql-low-level ((test conn-specs)) + #-clisp (let ((spec (mysql-spec test))) (when spec (let ((db (clsql-mysql::database-connect spec :mysql))) @@ -197,11 +227,12 @@ (test t nil :fail-info (format nil "Invalid types field (~S) passed to test-table-row" types)))) - (test (transform-float-1 int) - float - :test #'eql - :fail-info - (format nil "Wrong float value ~A for int ~A (row ~S)" float int row)) + (unless (eq db-type :sqlite) ; SQLite is typeless. + (test (transform-float-1 int) + float + :test #'eql + :fail-info + (format nil "Wrong float value ~A for int ~A (row ~S)" float int row))) (test float (parse-double str) :test #'double-float-equal @@ -233,6 +264,7 @@ (pgsql-table-test specs) (pgsql-socket-table-test specs) (aodbc-table-test specs) + (sqlite-table-test specs) )) t) -- 2.34.1