1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367
|
;;;; -*- 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
;;;;
;;;; 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)))))
(defgeneric signal-sqlite3-error (db))
(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 &optional (mode 0)
&aux (db-name (etypecase db
(pathname (namestring db))
(string db))))
(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)))
|