+10 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net)
+ * Version 2.5.3 released:
+ * base/database.lisp: Added CREATE-DATABASE,
+ DESTROY-DATABASE, PROBE-DATABASE commands
+ * base/conditions.lisp: Added CLSQL-ACCESS-ERROR
+ * db-*/*-sql.lisp: Added DATABASE-CREATE,
+ DATABASE-DESTORY, PROBE-DATABASE methods
+ * tests/test-init.lisp, clasic-tests/tests.lisp:
+ Use destroy-database and create-database to ensure
+ testing with empty database
+ * tests/test-connection.lisp: Add tests for
+ parsing of string connection-specs
+
10 Apr 2004 Kevin Rosenberg (kevin@rosenberg.net)
* Version 2.5.1 released:
* tests/*.lisp: Rework so tests are run
:database-type ,database-type
:template (quote ,template)))))
-(define-condition clsql-connect-error (clsql-error)
+(define-condition clsql-access-error (clsql-error)
((database-type :initarg :database-type
- :reader clsql-connect-error-database-type)
+ :reader clsql-access-error-database-type)
(connection-spec :initarg :connection-spec
- :reader clsql-connect-error-connection-spec)
- (errno :initarg :errno :reader clsql-connect-error-errno)
- (error :initarg :error :reader clsql-connect-error-error))
+ :reader clsql-access-error-connection-spec)
+ (error :initarg :error :reader clsql-access-error-error))
+ (:report (lambda (c stream)
+ (format stream "While trying to access database ~A~% using database-type ~A:~% Error ~A~% has occurred."
+ (database-name-from-spec
+ (clsql-access-error-connection-spec c)
+ (clsql-access-error-database-type c))
+ (clsql-access-error-database-type c)
+ (clsql-access-error-error c)))))
+
+(define-condition clsql-connect-error (clsql-access-error)
+ ((errno :initarg :errno :reader clsql-connect-error-errno))
(:report (lambda (c stream)
(format stream "While trying to connect to database ~A~% using database-type ~A:~% Error ~D / ~A~% has occurred."
(database-name-from-spec
- (clsql-connect-error-connection-spec c)
- (clsql-connect-error-database-type c))
- (clsql-connect-error-database-type c)
+ (clsql-access-error-connection-spec c)
+ (clsql-access-error-database-type c))
+ (clsql-access-error-database-type c)
(clsql-connect-error-errno c)
- (clsql-connect-error-error c)))))
+ (clsql-access-error-error c)))))
(define-condition clsql-sql-error (clsql-error)
((database :initarg :database :reader clsql-sql-error-database)
(print-separator total-size))))
(values)))
+(defun create-database (connection-spec &key database-type)
+ (when (stringp connection-spec)
+ (setq connection-spec (string-to-list-connection-spec connection-spec)))
+ (database-create connection-spec database-type))
+
+(defun probe-database (connection-spec &key database-type)
+ (when (stringp connection-spec)
+ (setq connection-spec (string-to-list-connection-spec connection-spec)))
+ (database-probe connection-spec database-type))
+
+(defun destroy-database (connection-spec &key database-type)
+ (when (stringp connection-spec)
+ (setq connection-spec (string-to-list-connection-spec connection-spec)))
+ (database-destory connection-spec database-type))
+
(defmacro with-database ((db-var connection-spec &rest connect-args) &body body)
"Evaluate the body in an environment, where `db-var' is bound to the
`(progv '(*default-database*)
(list ,database)
,@body))
+
"Returns t and stores the next row in the result set in list or
returns nil when result-set is finished."))
+(defgeneric database-create (connection-spec type)
+ (:documentation
+ "Creates a database, returns T if successfull or signals an error."))
+
+(defgeneric database-probe (connection-spec type)
+ (:documentation
+ "Probes for the existence of a database, returns T if database found or NIL
+if not found. May signal an error if unable to communicate with database server."))
-;; Interfaces to support UncommonSQL
+(defgeneric database-destory (connection-spec type)
+ (:documentation
+ "Destroys a database, returns T if successfull or signals an error
+if unable to destory."))
(defgeneric database-create-sequence (name database)
(:documentation "Create a sequence in DATABASE."))
#:*epilogue*)))
#+allegro
-(eval-when (:compile-toplevel :load-toplevel :execute)
- (unless (find-package '#:ansi-loop)
- (let ((excl::*enable-package-locked-errors* nil))
- (rename-package '#:excl '#:excl
- (cons '#:ansi-loop
- (package-nicknames (find-package '#:excl)))))))
-
-#+lispworks
-(eval-when (:compile-toplevel :load-toplevel :execute)
- (unless (find-package '#:ansi-loop)
- (rename-package '#:loop '#:loop
- (cons '#:ansi-loop
- (package-nicknames (find-package '#:loop))))))
+(defpackage #:ansi-loop
+ (:import-from #:excl
+ #:loop-error
+ #:*loop-epilogue*
+ #:*loop-ansi-universe*
+ #:add-loop-path))
-#+(or sbcl lispworks)
+#+sbcl
(defun ansi-loop::loop-gentemp (&optional (pref 'loopva-))
(gensym (string pref)))
#+lispworks (in-package loop)
+#+lispworks
+(defun loop::loop-gentemp (&optional (pref 'loopva-))
+ (gensym (string pref)))
+
#+lispworks
(cl-user::define-loop-method (record records tuple tuples) ansi-loop::clsql-loop-method (in of from))
#:database-query-result-set
#:database-dump-result-set
#:database-store-next-row
-
- ;; For UncommonSQL support
+ #:database-create
+ #:database-destroy
+ #:database-probe
+
#:database-list-tables
#:database-list-attributes
#:database-attribute-type
#:clsql-invalid-spec-error-connection-spec
#:clsql-invalid-spec-error-database-type
#:clsql-invalid-spec-error-template
+ #:clsql-access-error
+ #:clsql-access-error-database-type
+ #:clsql-access-error-connection-spec
+ #:clsql-access-error-error
#:clsql-connect-error
- #:clsql-connect-error-database-type
- #:clsql-connect-error-connection-spec
#:clsql-connect-error-errno
- #:clsql-connect-error-error
#:clsql-sql-error
#:clsql-sql-error-database
#:clsql-sql-error-expression
#:with-database
#:with-default-database
#:disconnect-pooled
+ #:create-database
+ #:destroy-database
+ #:probe-database
;; basic-sql.lisp
#:query
(cond
((and at-pos (> (length str) at-pos))
;; Connection spec is SQL*NET format
- (append (delimited-string-to-list (subseq str 0 at-pos) #\/)
- (list (subseq str (1+ at-pos)))))
+ (cons (subseq str (1+ at-pos))
+ (delimited-string-to-list (subseq str 0 at-pos) #\/)))
(t
(delimited-string-to-list str #\/)))))
(warn "CLSQL test config file ~S not found" path)
nil)))
+(defgeneric test-table (spec type))
+
(defmethod test-table (spec type)
(when spec
(let ((db (clsql:connect spec :database-type type :if-exists :new)))
)
(disconnect :database db)))))
-(defmethod mysql-low-level ((test conn-specs))
+(defun mysql-low-level (specs)
#-clisp
- (let ((spec (mysql-spec test)))
+ (let ((spec (mysql-spec specs)))
(when spec
(let ((db (clsql-mysql::database-connect spec :mysql)))
(clsql-mysql::database-execute-command "DROP TABLE IF EXISTS test_clsql" db)
(let ((spec (db-type-spec db-type specs)))
(when spec
(db-type-ensure-system db-type)
+ (ignore-errors (destroy-database spec db-type))
+ (ignore-errors (create-database spec db-type))
(test-table spec db-type))))))
t)
#:clsql-invalid-spec-error-connection-spec
#:clsql-invalid-spec-error-database-type
#:clsql-invalid-spec-error-template
+ #:clsql-access-error
+ #:clsql-access-error-database-type
+ #:clsql-access-error-connection-spec
+ #:clsql-access-error-error
#:clsql-connect-error
- #:clsql-connect-error-database-type
- #:clsql-connect-error-connection-spec
#:clsql-connect-error-errno
- #:clsql-connect-error-error
#:clsql-sql-error
#:clsql-sql-error-database
#:clsql-sql-error-expression
#:status ; database xx
#:with-database
#:with-default-database
+ #:create-database
+ #:destroy-database
+ #:probe-database
;; basic-sql.lisp
#:query
(defmethod perform ((o test-op) (c (eql (find-system 'clsql))))
(operate 'load-op 'clsql-tests)
- (operate 'test-op 'clsql-tests))
+ (operate 'test-op 'clsql-tests :force t))
(check-connection-spec connection-spec database-type (host db user password))
(destructuring-bind (host db user password) connection-spec
(declare (ignore password))
- (concatenate 'string host "/" db "/" user)))
+ (concatenate 'string
+ (if host host "localhost")
+ "/" db "/" user)))
(defmethod database-connect (connection-spec (database-type (eql :mysql)))
(check-connection-spec connection-spec database-type (host db user password))
(defmethod database-sequence-last (sequence-name (database mysql-database))
(declare (ignore sequence-name)))
-;; Functions depending upon high-level CommonSQL classes/functions
-#|
-(defmethod database-output-sql ((expr clsql-sys::sql-typecast-exp)
- (database mysql-database))
- (with-slots (clsql-sys::modifier clsql-sys::components)
- expr
- (if clsql-sys::modifier
- (progn
- (clsql-sys::output-sql clsql-sys::components database)
- (write-char #\: sql-sys::*sql-stream*)
- (write-char #\: sql-sys::*sql-stream*)
- (write-string (symbol-name clsql-sys::modifier)
- clsql-sys::*sql-stream*)))))
-
-(defmethod database-output-sql-as-type ((type (eql 'integer)) val
- (database mysql-database))
- ;; typecast it so it uses the indexes
- (when val
- (make-instance 'clsql-sys::sql-typecast-exp
- :modifier 'int8
- :components val)))
-|#
+
+
+(defmethod database-create (connection-spec (type (eql :mysql)))
+ (destructuring-bind (host name user password) connection-spec
+ (let ((asdf::*verbose-out* (make-string-output-stream)))
+ (unwind-protect
+ (let* ((status (asdf:run-shell-command
+ "mysqladmin create -u~A -p~A -h~A ~A"
+ user password
+ (if host host "localhost")
+ name))
+ (result (get-output-stream-string asdf::*verbose-out*)))
+
+ (if (search "CREATE DATABASE failed;" result)
+ (error 'clsql-access-error
+ :connection-spec connection-spec
+ :database-type type
+ :error
+ (format nil "database-create failed: ~s" result))
+ t))
+ (close asdf::*verbose-out*)))))
+
+(defmethod database-destory (connection-spec (type (eql :mysql)))
+ (destructuring-bind (host name user password) connection-spec
+ (let ((asdf::*verbose-out* (make-string-output-stream)))
+ (unwind-protect
+ (let* ((status (asdf:run-shell-command
+ "mysqladmin drop -f -u~A -p~A -h~A ~A"
+ user password
+ (if host host "localhost")
+ name))
+ (result (get-output-stream-string asdf::*verbose-out*)))
+
+ (if (search "DROP DATABASE failed;" result)
+ (error 'clsql-access-error
+ :connection-spec connection-spec
+ :database-type type
+ :error
+ (format nil "database-destory failed: ~s" result))
+ t))
+ (close asdf::*verbose-out*)))))
+
+(defmethod database-probe (connection-spec (type (eql :mysql)))
+ (destructuring-bind (name user password) connection-spec
+ (error "not-yet-implemented")))
(when (clsql-base-sys:database-type-library-loaded :mysql)
(clsql-base-sys:initialize-database-type :database-type :mysql))
(declare (ignore password options tty))
(concatenate 'string
(etypecase host
+ (null
+ "localhost")
(pathname (namestring host))
(string host))
(when port
database nil)))))
-;; Functions depending upon high-level CommonSQL classes/functions
-#|
-(defmethod database-output-sql ((expr clsql-sys::sql-typecast-exp)
- (database postgresql-socket-database))
- (with-slots (clsql-sys::modifier clsql-sys::components)
- expr
- (if clsql-sys::modifier
- (progn
- (clsql-sys::output-sql clsql-sys::components database)
- (write-char #\: clsql-sys::*sql-stream*)
- (write-char #\: clsql-sys::*sql-stream*)
- (write-string (symbol-name clsql-sys::modifier)
- clsql-sys::*sql-stream*)))))
-
-(defmethod database-output-sql-as-type ((type (eql 'integer)) val
- (database postgresql-socket-database))
- (when val ;; typecast it so it uses the indexes
- (make-instance 'clsql-sys::sql-typecast-exp
- :modifier 'int8
- :components val)))
-|#
+(defmethod database-create (connection-spec (type (eql :postgresql-socket)))
+ (error 'clsql-access-error
+ :connection-spec connection-spec
+ :database-type type
+ :error "Unable to create databases on a socket connection."))
+
+(defmethod database-destroy (connection-spec (type (eql :postgresql-socket)))
+ (error 'clsql-access-error
+ :connection-spec connection-spec
+ :database-type type
+ :error "Unable to create databases on a socket connection."))
+
+(defmethod database-probe (connection-spec (type (eql :postgresql-socket)))
+ (destructuring-bind (host name user password) connection-spec
+ (let ((database (database-connect (list host "template1" user password)
+ type)))
+ (unwind-protect
+ (find name (database-query "select datname from pg_database"
+ database :auto)
+ :key #'car :test #'string-equal)
+ (database-disconnect database)))))
(when (clsql-base-sys:database-type-library-loaded :postgresql-socket)
(clsql-base-sys:initialize-database-type :database-type :postgresql-socket))
(declare (ignore password options tty))
(concatenate 'string
(etypecase host
+ (null "localhost")
(pathname (namestring host))
(string host))
(when port
(concatenate 'string "SELECT LAST_VALUE ('" sequence-name "')")
database nil)))))
+(defmethod database-create (connection-spec (type (eql :postgresql)))
+ (destructuring-bind (host name user password) connection-spec
+ (declare (ignore password))
+ (let ((asdf::*verbose-out* (make-string-output-stream)))
+ (unwind-protect
+ (let* ((status (asdf:run-shell-command
+ "su -c ~A createdb -h~A ~A"
+ user
+ (if host host "localhost")
+ name))
+ (result (get-output-stream-string asdf::*verbose-out*)))
+
+ (if (search "database creation failed: ERROR:" result)
+ (error 'clsql-access-error
+ :connection-spec connection-spec
+ :database-type type
+ :error
+ (format nil "database-create failed: ~s" result))
+ t))
+ (close asdf::*verbose-out*)))))
+
+(defmethod database-destroy (connection-spec (type (eql :postgresql)))
+ (destructuring-bind (host name user password) connection-spec
+ (declare (ignore password))
+ (let ((asdf::*verbose-out* (make-string-output-stream)))
+ (unwind-protect
+ (let* ((status (asdf:run-shell-command
+ "su -c ~A dropdb -h~A ~A"
+ user
+ (if host host "localhost")
+ name))
+ (result (get-output-stream-string asdf::*verbose-out*)))
+
+ (if (search "database removal failed: ERROR:" result)
+ (error 'clsql-access-error
+ :connection-spec connection-spec
+ :database-type type
+ :error
+ (format nil "database-destroy failed: ~s" result))
+ t))
+ (close asdf::*verbose-out*)))))
+
+
+(defmethod database-probe (connection-spec (type (eql :postgresql)))
+ (destructuring-bind (host name user password) connection-spec
+ (let ((database (database-connect (list host "template1" user password)
+ type)))
+ (unwind-protect
+ (find name (database-query "select datname from pg_database"
+ database :auto)
+ :key #'car :test #'string-equal)
+ (database-disconnect database)))))
-;; Functions depending upon high-level CommonSQL classes/functions
-#|
-(defmethod database-output-sql ((expr clsql-sys::sql-typecast-exp)
- (database postgresql-database))
- (with-slots (clsql-sys::modifier clsql-sys::components)
- expr
- (if clsql-sys::modifier
- (progn
- (clsql-sys::output-sql clsql-sys::components database)
- (write-char #\: clsql-sys::*sql-stream*)
- (write-char #\: clsql-sys::*sql-stream*)
- (write-string (symbol-name clsql-sys::modifier)
- clsql-sys::*sql-stream*)))))
-
-(defmethod database-output-sql-as-type ((type (eql 'integer)) val
- (database postgresql-database))
- (when val ;; typecast it so it uses the indexes
- (make-instance 'clsql-sys::sql-typecast-exp
- :modifier 'int8
- :components val)))
-|#
(when (clsql-base-sys:database-type-library-loaded :postgresql)
(clsql-base-sys:initialize-database-type :database-type :postgresql))
(defmethod database-sequence-last (sequence-name (database sqlite-database))
(declare (ignore sequence-name)))
+
+(defmethod database-create (connection-spec (type (eql :sqlite)))
+ (declare (ignore connection-spec))
+ ;; databases are created automatically by SQLite
+ t)
+
+(defmethod database-destroy (connection-spec (type (eql :sqlite)))
+ (destructuring-bind (name) connection-spec
+ (if (probe-file name)
+ (delete-file name)
+ nil)))
+
+(defmethod database-probe (connection-spec (type (eql :sqlite)))
+ (destructuring-bind (name) connection-spec
+ ;; TODO: Add a test that this file is a real sqlite database
+ (and (probe-file name) t)))
+
+
#:clsql-invalid-spec-error-connection-spec
#:clsql-invalid-spec-error-database-type
#:clsql-invalid-spec-error-template
+ #:clsql-access-error
+ #:clsql-access-error-database-type
+ #:clsql-access-error-connection-spec
+ #:clsql-access-error-error
#:clsql-connect-error
- #:clsql-connect-error-database-type
- #:clsql-connect-error-connection-spec
#:clsql-connect-error-errno
- #:clsql-connect-error-error
#:clsql-sql-error
#:clsql-sql-error-database
#:clsql-sql-error-expression
#:status ; database xx
#:with-database
#:with-default-database
+ #:create-database
+ #:destroy-database
+ #:probe-database
;; pool.lisp
#:disconnect-pooled
;;;; -*- Mode: LISP; Syntax: ANSI-Common-Lisp; Base: 10 -*-
;;;; ======================================================================
;;;; File: test-connection.lisp
-;;;; Author: Marcus Pearce <m.t.pearce@city.ac.uk>
+;;;; Authors: Marcus Pearce <m.t.pearce@city.ac.uk>, Kevin Rosenberg
;;;; Created: 30/03/2004
;;;; Updated: $Id$
;;;; ======================================================================
(eql (clsql:database-type database) *test-database-type*))
t)
+(deftest :connection/2
+ (clsql-base-sys::string-to-list-connection-spec
+ "localhost/dbname/user/passwd")
+ ("localhost" "dbname" "user" "passwd"))
+
+(deftest :connection/3
+ (clsql-base-sys::string-to-list-connection-spec
+ "dbname/user@hostname")
+ ("hostname" "dbname" "user"))
+
))
(dolist (db-type +all-db-types+)
(let ((spec (db-type-spec db-type specs)))
(when spec
+ (format t
+"~&
+*******************************************************************
+*** Running CLSQL tests with ~A backend.
+*******************************************************************
+" db-type)
(db-type-ensure-system db-type)
(rt:rem-all-tests)
+ (ignore-errors (destroy-database spec :database-type db-type))
+ (ignore-errors (create-database spec :database-type db-type))
(dolist (test (append *rt-connection* *rt-fddl* *rt-fdml*
*rt-ooddl* *rt-oodml* *rt-syntax*))
(eval test))
- (format t "~&Running CLSQL tests with ~A backend.~%" db-type)
(test-connect-to-database db-type spec)
(test-initialise-database)
(rtest:do-tests))))))