;; @module DF.DB, DF.SQL, DF.BLOB ;; @description Generic database access interface for Dragonfly using Objective newLISP ;; @version 1.1 ;; @author Greg Slepak ;; @location http://www.taoeffect.com/newlisp/database.lsp.txt ;;
The purpose of this module is to standardize the interface to ;; access databases. This will allow you, for the most part, to write your code once ;; and easily switch the type of database that you're using.
;;To accomplish this, the interface introduces three Objective newLISP classes: ;; 'DF.DB', 'DF.SQL', and 'DF.BLOB'.
;;It is used to retrieve rows from the result set of a query one-by-one.
;;
;; (push-autorelease-pool) ; we're going to be using DF.BLOB's.
;; (setf db (instantiate Sqlite3 ":memory:"))
;; (if-not db (throw-error "couldn't open db"))
;; (db:execute-update "CREATE TABLE fish (id INTEGER PRIMARY KEY, name TEXT, weight REAL, blah BLOB)")
;; (db:execute-update "INSERT INTO fish (name,weight) VALUES (?,?)" '("flipper" 234.123))
;; (db:execute-update "INSERT INTO fish (name,weight) VALUES (?1,?2)" '(("?1" "catfish") ("?2" 100.3)))
;; (db:execute-update "INSERT INTO fish (blah) VALUES (?)" (list (DF.BLOB (dup "\000" 10))))
;; (db:execute-update "INSERT INTO fish (blah) VALUES (:cat)" (list (list ":cat" (DF.BLOB (dup "\000" 10)))))
;; (setf sql (db:execute-query "SELECT * FROM fish"))
;; (do-while (list? row)
;; (push-autorelease-pool) ; "in case" we end up fetching a lot of large blobs
;; (setf row (sql:next-row))
;; (println "row: " row)
;; (pop-autorelease-pool)
;; )
;; (deallocate sql)
;; (deallocate db)
;; (pop-autorelease-pool) ; deallocate the blobs we created
;;
;; Represents a database connection. You create a DF.DB object ;; like so:
;;(instantiate;;[ [ ]])
What type should be represented by
The possibly optional
Instances must have code in their 'dealloc' method so that they can properly cleanup their resources ;; (e.g. shutdown connection) if deallocated with 'deallocate'.
(context DF.DB) (define (DF.DB:DF.DB connection) (throw-not-implemented) ) ;; @syntax (DF.DB:openReturns 'true' if the connection was opened successfully, 'nil' ;; if there was an error opening the connection, or throws an exception ;; if already connected.
(define (DF.DB:open connection) (throw-not-implemented) ) ;; @syntax (DF.DB:close) ;;Returns 'true' if the connection was closed successfully or was ;; already closed, or 'nil' if there was an error closing the connection.
(define (DF.DB:close) (throw-not-implemented) ) ;; @syntax (DF.DB:prepare-sqlReturns a 'DF.SQL' object upon success, 'nil' on failure, or throws ;; an exception if not connected.
;;important: If your SQL statement contains placeholders (to be ;; bound later using 'DF.SQL:bind-params') you may not mix and match placeholder styles! ;; Pick one placeholder style and stick with it for the entire statement.
(define (DF.DB:prepare-sql sql) (throw-not-implemented) ) ;; @syntax (DF.DB:execute-updateSame as 'DF.DB:execute-query' but returns 'true' instead of a 'DF.SQL' object upon success. ;; Useful for SQL such as "UPDATE" and "INSERT".
(define (DF.DB:execute-update sql params) (throw-not-implemented) ) ;; @syntax (DF.DB:execute-queryA short-hand for 'DF.DB:prepare-sql' and 'DF.SQL:bind-params'. Returns ;; a 'DF.SQL' object upon success, 'nil' on failure, or throws an exception if ;; not connected.
;;see: documentation for 'DF.SQL:bind-params' for more info on Same as 'DF.DB:execute-query' but retrieves all of the rows and returns them as a list of results. important: If any columns contain BLOB types, you must have
;; an autorelease pool allocated prior to calling this function! Returns the row id for the last row that was inserted or throws an
;; exception if not connected. Returns how many rows were affected by the last INSERT/UPDATE, or throws
;; an exception of not connected Returns the version number of the database library being used as an integer. Returns nil or non-nil depending on whether the table named 'table-name' exists,
;; or throws an exception if not connected. Represents a prepared statement. It is used to bind values to a statement's parameters
;; and retrieve the results of a query. You do not create a DF.SQL instance yourself but obtain one through 'DF.DB:prepare-sql'.
;; However, if you've obtained an instance then you are responsible for freeing its memory
;; and closing its connection when you're finished with it. Subclasses should make sure that their 'dealloc' method calls 'DF.SQL:close' so that
;; statements can be freed and closed in one function call using 'deallocate'. Binds the placeholders of this SQL statement to the values in Values may be any of newLISP's primitives. To specify a BLOB wrap your string
;; in a 'DF.BLOB': Returns a row as a list from the result set of a statement, 'true' if
;; there are no more rows to return, or 'nil' if there was an error. important: If any columns contain BLOB types, you must have
;; an autorelease pool allocated prior to calling this function! Resets the statement so that it can be re-used without preparing another
;; statement (which is more efficient). After reseting a statement you typically
;; use 'DF.SQL:bind-params' to bind new values and then 'DF.SQL:next-row'. Releases the resources used by the SQL statement represented by this object.
;; You cannot use it anymore after calling this method, the only thing left to do
;; is to 'deallocate' it, and since sublasses of 'DF.SQL' must call 'close' in
;; their 'dealloc' methods, it's often simpler to call 'deallocate' on the object instead. Returns 'true' upon success, 'nil' on failure or if already closed. An object wrapper around a blob of data represented by Unlike most other 'ObjNL' classes, you typically create an instance
;; by simply calling its constructor instead of calling 'instantiate' yourself.
;; It will then 'instantiate' and 'autorelease' a 'DF.BLOB' instance containing the data
;; in 'DF.BLOB:blob'. important: An autorelease pool must be in place when using the
;; constructor to instantiate a blob! see: the introduction to this document for example usage.
;; ; "weight" is a REAL and "blah" is a BLOB
;; (push-autorelease-pool) ; create our autorelease pool since we're using DF.BLOB
;; (setf sql (db:prepare-sql "INSERT INTO fish (weight,blah) VALUES (?,?)"))
;; (sql:bind-params (list 1043.3 (DF.BLOB (dup "\000" 5))))
;; (println (sql:next-row))
;; (deallocate sql)
;; (pop-autorelease-pool)
;;
(define (DF.SQL:bind-params params)
(throw-not-implemented)
)
;; @syntax (DF.SQL:next-row)
;; (push-autorelease-pool) ; only necessary if there's a BLOB column
;; (while (list? (setf row (sql:next-row)))
;; (println "row: " row)
;; )
;; (pop-autorelease-pool) ; free any blobs
;;