sqli.scm
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;;; Package   : sqli.scm
;;; Author    : Hans Oesterholt-Dijkema.
;;; Copyright : HOD 2004/2005.
;;; License   : LGPL.
;;; CVS       : $Id: sqli.scm,v 1.13 2007/07/16 19:55:09 hoesterholt Exp $
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;=pod
;
;=head1 Name
;
;SQLI - SQL Interface module
;
;=head1 Description
;
;This SQL Interface module for mzscheme provides a simple interface
;to connect to databases. It works by connecting to a database
;through a given SQL Driver closure; and provides functions
;for handling SQL.
;
;=head1 Author
;
;Hans Oesterholt-Dijkema <hans-at-elemental-programming%dt#org>.
;
;=head1 License
;
;This module is distributed under the LGPL.
;(c) 2004/2005 Hans Oesterholt-Dijkema.
;
;=head1 Version
;
;$Id: sqli.scm,v 1.13 2007/07/16 19:55:09 hoesterholt Exp $
;
;=head1 Synopsis
;
;=syn scm,8
;
; (module test
; 	(import sqli)
; 	(import sqld-sqlite)
; 	(main main))
;
; (define (main argv)
;   (let* ((sqld    (sqld-sqlite-new "test.db"))
; 	   (sqlh    (sqli-connect sqld))
;          (sqli    (sqli-closure sqlh))
;          (results (list)))
;
;     (print (sqli 'driver-name) " - " (sqli 'driver-version))
;
;     (sqli 'query "SELECT * FROM test_table;")
;   
;     (do
; 	((a (sqli 'fetchrow) (sqli 'fetchrow)))
; 	((eq? a #f) #t)
;       (print a))
;
;     (sqli 'begin)
;     (do
; 	((i 1 (+ i 1)))
; 	((> i 10) #t)
;       (sqli 'query
;             "INSERT INTO test_table VALUES ($1, $2)"
;             (string-append "row'" (number->string i)) i))
;     (sqli 'commit)
;
;     (sqli 'register
; 	  'a-selection
; 	  "SELECT $1 FROM test_table WHERE age $2 $3"
; 	  (lambda (a) (list (car a) "cvt" (string->number (cadr a)))))
;     (print (sqli 'exec 'select 'name '> 2))
;     (print (sqli 'fetchall))
;
;     (sqli 'register 'count
;           "SELECT COUNT(*) FROM test_table"
;           (lambda (row) (car row)))
;     (sqli 'exec 'count)
;     (print (sqli 'fetchrow))
;   
;     (sqli 'register 'some
;           "SELECT name FROM test_table WHERE name LIKE '$1%'")
;     (print (sqli 'exec 'some (list "John")))
;     (print (sqli 'fetchall))
;
;     (sqli 'disconnect)
;   0))
;
;=head1 API
;
;=head2 Connection handling
;
;=over 1
;
;=head3 C<(sqli-connect sql-driver) : sqli-handle>
;
;This function connects to the database represented by C<sql-driver>.
;It returns a handle to the driver, or C<#f>, if something went
;wrong.
;
;=head3 C<(sqli-disconnect sqli-handle) : unspecified>
;
;This function disconnects a given sqli-handle from the database.
;This function must always be called before a variable goes out
;of scope.
;
;=back
;
;=head2 Queries
;
;=over 1
;
;=head3 C<(sqli-query sqli-handle query . args) : boolean>
;
;F<sqli-query> executes an SQL query to the sqli-handle. An SQL
;statement can contain arguments of form '$n', where C<1E<lt>=nE<lt>=(length args)>
;Arguments are interpreted:
;
;=over 1
;
;=item *
;
;C<Symbols> are converted to strings, without quotes (usefull for
;column names, operations, etc.).
;
;=item *
;
;C<strings> are converted to quoted strings ((var)char, etc. in
;SQL).
;
;=item *
;
;C<a list of a string> is converted to a string, without quotes.
;This construct can be used when one wants to use strings in
;expressions, etc.
;
;=item *
;C<a vector of a string> is taken literally, i.e. not converted.
;
;=item *
;
;C<integers> and C<numbers> are converted accordingly.
;
;=item *
;
;C<date> types are converted to Database dependent timestamps.
;
;=item *
;
;C<boolean> types are converted to Database dependent booleans.
;
;=back
;
;This function returns C<#t>, if an error occured, C<#f>, otherwise.
;
;=head3 C<(sqli-register sqli-handle name query . conversion-function) : unspecified>
;
;With this function an SQL statement, with the same possibilties as
;with the sqli-query function, can be registered for later processing.
;The advantage of this approach, is that a conversion function can
;be registered along with the registered query. This conversion function
;is called for each fetched row of a query.
;
;Note! If the conversion function converts a list of elements to a
;single atom, and this atom equals #f, the fetchall function will
;fail to retreive all rows.
;
;=head3 C<(sqli-register sqli-handle name query . types) : unspecified>
;
;With this function an SQL statement, with the same possibilties as
;with the sqli-query function, can be registered for later processing.
;With this function, the expected types can be given as symbols. Fetched
;rows are converted to the expected types. The following types can be used:
;
;=over 1
;
;=item 'string
;
;Type string is expected.
;
;=item 'int
;
;Type integer (exact number) is expected.
;
;=item 'number
;
;Type number (exact or inexact) is expected.
;
;=item 'date
;
;Type date is expected.
;
;=item 'bool
;
;Type boolean is expected.
;
;=back
;
;=head3 C<(sqli-exec sqli-handle name . args) : boolean>
;
;With this function a previously registered SQL statement can be
;executed (using arguments 'args'). Arguments are interpreted the
;same way as with F<sqli-query>.
;
;=back
;
;=head2 Transaction processing
;
;Please note: The transaction functions can be called recursively,
;but one must not intermix commits and rollbacks!
;
;=over 1
;
;=head3 C<(sqli-begin sqli-handle) : boolean>
;
;This function starts a transaction on the given sqli-handle. It
;returns C<#t>, if something went wrong, C<#f>, othwerwise.
;This function can be called recursively. Only the first begin
;will be given to the sqld driver.
;
;=head3 C<(sqli-commit sqli-handle) : boolean>
;
;This function commits a transaction on the given sqli-handle. It
;returns C<#t>, if something went wrong, C<#f>, othwerwise.
;This function can be called recursively. Only the last commit
;will be given to the sqld driver.
;
;=head3 C<(sqli-rollback sqli-handle) : boolean>
;
;This function rolls back a transaction on the given sqli-handle. It
;returns C<#t>, if something went wrong, C<#f>, othwerwise.
;This function can be called recursively. Only the last rollback
;will be given to the sqld driver.
;
;=head3 c<sqli-transaction-count sqli-handle) : integer>
;
;Returns the sqli-begin depth for this handle.
;
;=back
;
;=head2 Fetching
;
;=over 1
;
;=head3 C<(sqli-fetchrow sqli-handle) : list>
;
;F<sqli-fetchrow> fetches a row from the last query executed.
;It returns C<#f>, if no (more) rows can be fetched, returns
;a list of results (depending on the conversion function,
;this can be different) otherwise (see L<synopsis|/Synopsis>).
;
;=head3 C<(sqli-fetch sqli-handle count) : list>
;
;F<sqli-fetch> fetches at most C<count> rows from the last
;query for C<sqli-handle>. Returns a list of rows (or the
;empty list, if no row could be fetched).
;
;=head3 C<(sqli-fetchall sqli-handle) : list>
;
;F<sqli-fetchall> fetches all rows from the last query
;for C<sqli-handle>. Returns a list of rows like F<sqli-fetch>.
;If the first F<sqli-fetch> returns #f, the list will be empty
;(null? property).
;
;=back
;
;=head2 Closure interface
;
;=over 1
;
;=head3 C<(sqli-closure sqli-handle) : procedure>
;
;Makes a closure from C<sqli-handle>, that can be treated like
;an object interface for the C<sqli-handle>. All functions
;are called through this closure, using a symbol that denotes
;the function. E.g.:
;
;C<(sqli-closure 'fetchrow)>, fetches a row.
;
;=back
;
;=head2 Predicates, error handling and conversions
;
;=over 1
;
;=head3 C<(sqli? obj) : boolean>
;
;Returns C<#t>, if C<obj> is of type C<sqli>. Note:
;C<(list? sqli-handle)> will return C<#t> also.
;
;=head3 C<(sqli-error? handle)>
;
;Returns C<#t>, if the last query reported an error.
;Returns C<#f>, otherwise.
;SQL error: database is locked

;=head3 C<(sqli-error-message handle)>
;
;Returns the error message that complements the C<sqli-error?>
;indication.
;
;=head3 C<(sqli-convert handle string T) : object of type T>
;
;Converts string as returned from the database query
;to type; where type is a symbol indicating the scheme type
;to convert to. Currently, 'date, 'boolean, 'string, 'number
;and 'integer are supported.
;
;=head3 C<(sqli-driver-name sql-driver) : string>
;
;Returns the driver name in lower case (e.g. sqlite). Refer to the
;driver documentation to get more information on this.
;
;Note! This function works on both the B<sql-driver> and the
;sqli connection handle.
;
;=head3 C<(sqli-driver-version sql-driver) : integer>
;
;Returns the driver version as an integer (e.g. 307 or 285 (for sqlite)).
;Refer to the driver documentation for more information on this.
;
;Note! This function works on both the B<sql-driver> and the
;sqli connection handle.
;
;=head3 C<(sqli-last-query) : string>
;
;Returns the last query that has been executed by the sqlid driver.
;
;=head3 C<(sqli-version) : integer>
;
;Returns the version of SQLI as an integer. Major part*100+minor part.
;
;=back
;
;=wikiwikiwiki
;
;==ROOS Interface
;
; >(require (planet "sqli-oo.scm" ("oesterholt" "sqlid.plt" 1 0)))
; >(require (planet "sqld-sqlite.scm" ("oesterholt" "sqlid.plt" 1 0)))
; >(define d (sqld-sqlite "test.db"))
; >(define o (sqli-oo d))
; >(-> o connect)
; >(-> o error?)
; #f
; >(-> o query "select * from test")
; #f
; >(-> o fetchall)
; (("3"))
; >
;
;#sqli-oo# forms a ROOS layer on top of #sqli#.
;
;===#(-> o connect)#
;
;Equivalent to #sqli-connect#.
;
;===#(-> o disconnect)#
;
;Equivalent to #sqli-disconnect#.
;
;===#(-> o query . args)#
;
;Equivalent to #sqli-query#.
;
;===#(-> o register name query . conv-func|types)#
;
;Equivalent to #sqli-register#.
;
;===#(-> o exec name . args)#
;
;Equivalent to #sqli-exec#.
;
;===#(-> o begin-work)#
;
;Equivalent to #sqli-begin#.
;
;===#(-> o commit)#
;
;Equivalent to #sqli-commit#.
;
;===#(-> o rollback)#
;
;Equivalent to #sqli-rollback#.
;
;===#(-> o fetchrow)#
;
;Equivalent to #sqli-fetchrow#.
;
;===#(-> o fetch n)#
;
;Equivalent to #sqli-fetch#.
;
;===#(-> o fetchall)#
;
;Equivalent to #sqli-fetchall#.
;
;===#(-> o error?)#
;
;Equivalent to #sqli-error?#.
;
;===#(-> o errmsg) | (-> o error-message)#
;
;Equivalent to #sqli-error-message#.
;
;===#(-> o driver-name)#
;
;Equivalent to #sqli-driver-name#.
;
;===#(-> o driver-version)#
;
;Equivalent to #sqli-driver-version#.
;
;===#(-> o version)#
;
;Equivalent to #sqli-version#.
;
;===#(-> o last-query)#
;
;Equivalent to #sqli-last-query#.
;
;===#(-> o -><type> string-from-db)#
;
;Equivalent to #sqli-convert# with the given type:
;
; (-> o ->date dbstr)          <=> (sqli-convert handle dbstr 'date)
; (-> o ->bool dbstr)          <=> (sqli-convert handle dbstr 'boolean)
; (-> o ->boolean dbstr)       <=> (sqli-convert handle dbstr 'boolean)
; (-> o ->integer dbstr)       <=> (sqli-convert handle dbstr 'integer)
; (-> o ->number dbstr)        <=> (sqli-convert handle dbstr 'number)
; (-> o ->symbol dbstr)        <=> (sqli-convert handle dbstr 'symbol)
; (-> o ->string dbstr)        <=> (sqli-convert handle dbstr 'string)
; (-> o ->scheme-data dbstr)   <=> (sqli-convert handle dbstr 'scheme-object)
; (-> o ->scheme-object dbstr) <=> (sqli-convert handle dbstr 'scheme-object)
;
;===#(-> o ->var string-from-db var)#
;
;Determines from the type of var, which conversion to use.
;Returns the converted string. Doesn't set #var#
;
;  (srfi:date? var)                   => ->date
;  (boolean? var)                     => ->bool
;  (and (number? var) (exact? var))   => ->integer
;  (and (number? var) (inexact? var)) => ->number
;  (symbol? var)                      => ->symbol
;  (string? var)                      => ->string
;  else                               => ->scheme-object
;
;=Drivers

;==SQLite driver
;
;Initialize the SQLite driver with #DSN=<filename of database>#.
;E.g.:
;
; (sqld-sqlite-new "test.db")
;
;==PostgreSQL driver
;
;Initialize the PostgreSQL driver with a PostgreSQL connection string.
;E.g.:
;
; (sqld-psql-new "dbname=test user=test password=test host=localhost")
;
;==MySQL driver
;
;Initialize the MySQL driver with #DSN=![<user>] ![<password>] <database> ![<hostname>] ![<port>]#, E.g.:
;
; (sqld-mysql-new "db=test user=test passwd=test host=localhost port=3306")
; (sqld-mysql-new "db=test user=me passwd=mypassword")
;
;==Oracle driver
;
;Initialize the Oracle driver with an Oracle dsn, e.g.:
;
; (sqld-oracle-new "scott/tiger")
;
;==DB2 driver
;
;Initialize the DB2 driver with a DB2 dsn, e.g.:
;
; (sqld-db2-new "alias=test user=me passwd=mypassword")
;
;=wikiwikiwiki
;
;=head1 Literate part
;
;=head2 Module descriptor
;
;This SQLI module has been designed for use with
;mzscheme. The sqli module begins with a module description.
;
;=verbatim scm,8
(module sqli mzscheme
	(require (lib "time.ss" "srfi" "19"))
;=verbatim
;
;In the module description, all exported functions are defined.
;
;=head2 The definition of a handle
;
;The SQLI module uses a handle to a database connection
;for transportation of connection- and status information.
;The handle is build as follows:
;
; (list 'sqli
;        E<lt>driver closureE<gt>
;        E<lt>list of registered queriesE<gt>
;        E<lt>conversion function for the last queryE<gt>)
;
;The driver is the C<sql-driver> that has been given to
;the F<sqli-connect> function. This is a closure that handles
;commands. See the
;L<interface description|SQLD - Interface description for SQLI drivers>
;for a description of the functionality that a driver must implement.
;
;The list of registered queries contains all query-templates and
;conversion functions currently registered.
;
;The conversion function is conversion function for the last
;query executed by the sql driver. A value of B<'nil-converter>
;indicates that there is no conversion function.
;
;=head2 Exported functions
;
;The F<sqli?> function returns true, if obj is a list, not null
;and the car of this list is equal to 'sqli.
;
;=verbatim scm,8
(define (sqli? obj)
  (if (list? obj)
      (if (null? obj)
          #f
          (eq? (car obj) 'sqli))
      #f))

(define (check-handle F handle)
  (if (sqli? handle)
      #t
      (error (format "~a: given handle: '~s' is not an sqli handle" F handle))))
;=verbatim
;
;Connection to the sql driver is simply done by constructing
;a sqli handle. Disconnecting is done by calling the sql driver
;with symbol 'disconnect. The driver must then invalidate itself.
;
;=verbatim scm,8

(define (sqli-connected? handle)
  (check-handle 'sqli-connected? handle)
  (cadddr (cddddr (cdddr handle))))

(define (sqli-connected! handle jn)
  (check-handle 'sqli-connected! handle)
  (set-car! (cdddr (cddddr (cdddr handle))) jn)
  handle)
  
(define FINALIZER (make-will-executor))
(define FINALIZE  (thread (lambda () 
                            (letrec ((f (lambda ()
                                          (will-execute FINALIZER)
                                          (f))))
                              (f)))))
(define (FIN-HANDLE handle)
  (if (> (sqli-transaction-count handle) 0)
      (error (format "Finalizing sqli connection. SQLI handle is in the middle of a transaction (count=~a)"
                     (sqli-transaction-count handle))))
  (if (sqli-connected? handle)
      ((cadr handle) 'disconnect)))
  
(define (sqli-connect sql-driver)
  (let ((R (list 'sqli (sql-driver 'connect) (list) 'nil-converter "" 0 
                 (sql-driver 'name) (sql-driver 'version) "" #f #t)))
    (if (sqli-error? R)
        (sqli-connected! R #f)
        (will-register FINALIZER R FIN-HANDLE))
    R))

(define (sqli-disconnect handle)
  (check-handle 'sqli-disconnect handle)
  (if (> (sqli-transaction-count handle) 0)
      (error (format "Disconnecting an sqli handle in the middle of a transaction (count=~a)" 
                     (sqli-transaction-count handle))))
  (let ((R ((cadr handle) 'disconnect)))
    (sqli-connected! handle #f)
    R))

(define (_sqli-error-message handle . args)
  (let ((L (cddddr (cddddr handle))))
    (if (not (null? args)) (set-car! L (car args)))
    (car L)))

;=verbatim
;
;Error handling is very simple. The F<sqli-error-message> function
;queries the last error of the sql driver. This last error is a
;string reporting an error. If this string equals "" (the empty
;string), there is no error.
;
;=verbatim scm,8
(define (my-string=? a b)
  (and (string? a) (string? b) (string=? a b)))

(define (sqli-error-message handle)
  (check-handle 'sqli-error-message handle)
  (if (not (my-string=? (_sqli-error-message handle) ""))
      (_sqli-error-message handle)
      ((cadr handle) 'lasterr)))

(define (sqli-error? handle)
  (check-handle 'sqli-error? handle)
  (or (not (my-string=? (_sqli-error-message handle) ""))
      (not (my-string=? (sqli-error-message handle) ""))))

;=verbatim
;
;Driver name and version are simply calls wirh the equivalent commands
;to the SQL Driver. Works for both the sql driver handle and the sqli handle
;after connect.
;
;=verbatim scm,8
(define (sqli-driver-name sqlh)
  (if (sqli? sqlh)
      (caddr (cddddr sqlh))
      (sqlh 'name)))

(define (sqli-driver-version sqlh)
  (if (sqli? sqlh)
      (cadddr (cddddr sqlh))
      (sqlh 'version)))
;=verbatim
;
;Transactions are simply left to the sql driver to handle. The sqli
;driver is called with the symbols 'begin, 'commit or 'rollback.
;Each function returns F<sqli-error?>, which then reports about
;this last "query" to the driver.
;
;These functions can be called recursively. Only the first begin will
;be given to the driver. Only the last commit will be given to the
;driver. Only the last rollback will be given to the driver.
;
;=verbatim scm,8
(define-syntax gcount
  (syntax-rules ()
    ((_ handle)
     (cdr (cddddr handle)))))

(define (sqli-begin handle)
  (check-handle 'sqli-begin handle)
  (let ((count (gcount handle)))
    (if (= (car count) 0)
        ((cadr handle) 'begin))
    (if (not (sqli-error? handle))
        (set-car! count (+ (car count) 1)))
    (sqli-error? handle)))

(define (sqli-commit handle)
  (check-handle 'sqli-commit handle)
  (let ((count (gcount handle)))
    (set-car! count (- (car count) 1))
    (if (= (car count) 0)
        ((cadr handle) 'commit)
        (if (< (car count) 0)
            (set-car! count 0)))
    (sqli-error? handle)))

(define (sqli-rollback handle)
  (check-handle 'sqli-rollback handle)
  (let ((count (gcount handle)))
    (set-car! count (- (car count) 1))
    (if (= (car count) 0)
        ((cadr handle) 'rollback)
        (if (< (car count) 0)
            (set-car! count 0)))
    (sqli-error? handle)))

(define (sqli-transaction-count handle)
  (car (gcount handle)))

;=verbatim
;
;Fetching rows from a last query is done by calling the sql driver
;using symbol 'fetchrow. A conversion function is conditionally
;called for the fetched row, but only if the call to the sql driver
;does not return C<#f> and the conversion function does not equal
;'nil-converter. I.e., the procedure? predicate must apply to
;the converter.
;
;Note! The default format for rows is a list of elements. If the
;conversion function converts a list of elements to a single atom,
;and this atom equals C<#f>, the C<fetchall> function will fail
;to retreive all rows.
;
;The functions for fetching all rows or C<count> rows are simply
;implemented by subsequental calls to F<sqli-fetchrow>.
;
;=verbatim scm,8
(define (sqli-fetchrow handle)
  (check-handle 'sqli-fetchrow handle)
  (if (eq? (cadddr handle) 'nil-converter)
      ((cadr handle) 'fetchrow)
      (let ((row ((cadr handle) 'fetchrow)))
	(if (eq? row #f) 
	    #f
	    ((cadddr handle) row)))))

(define (sqli-fetchall handle)
  (check-handle 'sqli-all handle)
  (do
      ((l (list))
       (r (sqli-fetchrow handle) (sqli-fetchrow handle)))
      ((eq? r #f) (reverse l))
    (set! l (cons r l))))

(define (sqli-fetch handle count)
  (check-handle 'sqli-fetch handle)
  (do
      ((l (list))
       (i 0 (+ i 1))
       (row (sqli-fetchrow handle) (sqli-fetchrow handle)))
      ((or (>= i count) (eq? row #f)) (reverse l))
    (set! l (cons row l))))
;=verbatim
;
;There are two types of queries. Direct queries, which by default
;don't have ;conversion functions attached to them; and registered
;queries, which can have conversion functions attached to them.
;
;Queries are SQL statements with numbered arguments ($1, $2, ...).
;Arguments given to the F<sqli-query> or F<sqli-exec> functions
;are used to substitute the numbered arguments with. The first
;argument being $1 and counting onwards. Note: arguments are not
;substituted on a positional basis. Multiple $i's are substituted
;all at the same time. E.g.:
;
;In S<C<(sqli-query sqli-handle "SELECT $1 FROM test WHERE $1E<gt>0" 'age)>>,
;all $1 fields are substituted with "age".
;
;Queries can be registered using a name. This name must be a symbol.
;Registered queries are executed using F<sqli-exec>. For two different queries,
;that are registered subsequently under the same symbol, only the last one can
;be retreived.
;
;=verbatim scm,8
(define (sqli-internal-query handle query args)
  ((cadr handle) 'query (sqli-make-query handle (sqli-split-query query) 'nil-converter args))
  (sqli-error? handle))

(define (sqli-query handle query . args)
  (check-handle 'sqli-query handle)
  (_sqli-error-message handle "")
  (sqli-internal-query handle query args))

(define (sqli-standard-converter handle row types)
  (define (convert row types)
    (if (null? row)
        (list)
        (if (null? types)
            row
            (cons (sqli-convert handle (car row) (car types))
                  (convert (cdr row) (cdr types))))))

  (convert row types))
      
(define (sqli-register handle name _query . _converter_or_types)
  (check-handle 'sqli-register handle)
  (let ((converter (if (null? _converter_or_types)
                       'nil-converter
                       (if (symbol? (car _converter_or_types))
                           (lambda (row) (sqli-standard-converter handle row _converter_or_types))
                           (car _converter_or_types))))
        (query (sqli-split-query _query)))

    (define (update queries)
      (if (null? queries)
          (list)
          (if (equal? (caar queries) name)
              (update (cdr queries))
              (cons (car queries) (update (cdr queries))))))

    (begin
      (set-car! (cddr handle)
                (cons (list name converter query) (update (caddr handle))))
      handle)))

(define (sqli-internal-exec handle name args)
  
  (define (get queries)
    (if (null? queries)
        #f
        (if (equal? (caar queries) name)
            (car queries)
            (get (cdr queries)))))

  (define (copy query)
    (if (null? query)
	(list)
	(cons (car query) (copy (cdr query)))))

  (let ((Q (get (caddr handle))))
    (if (eq? Q #f)
        (begin
          (_sqli-error-message handle (format "Cannot find query ~s" name))
          #f)
        (let* ((converter (cadr Q))
               (query (caddr Q)))
          ((cadr handle) 'query (sqli-make-query handle (copy query) converter args))))
    
    (sqli-error? handle)))

(define (sqli-exec handle name . args)
  (check-handle 'sqli-exec handle)
  (_sqli-error-message handle "")
  (sqli-internal-exec handle name args))
;=verbatim
;
;The F<sqli-convert> function can be used to
;convert results from queries from database format to
;scheme format. It currently converts for the
;scheme type 'date, 'boolean, 'integer, 'number and 'string,
;'symbol and 'scheme-object. A 'scheme-object type can be
;made with a write to a string port.
;
;The F<sqli-2db> function can be used to
;convert values to database format. It supports
;all scheme types in a generic way, but has
;special arrangements for 'data, 'boolean, 'number, 'string
;and 'symbol.
;
;=verbatim scm,8

;#+ mzscheme
(define-syntax string->integer
  (syntax-rules ()
    ((_ s) (inexact->exact (round (string->number s))))))
;##

(define (sqli-convert handle str type)
  (cond
   ((eq? type 'date) ((cadr handle) 'db2date str))
   ((eq? type 'boolean) ((cadr handle) 'db2bool str))
   ((eq? type 'integer) (string->integer str))
   ((eq? type 'number) (string->number str))
   ((eq? type 'symbol) (string->symbol str))
   ((eq? type 'string) str)
   ((eq? type 'scheme-object) (let ((fh (open-input-string str)))
                                (let ((scheme-object (read fh)))
                                  (close-input-port fh)
                                  scheme-object)))
   (else (ierr "Unknown type given to sqli-convert"))))

(define (sqli-2db handle arg)
  (let ((sqld (cadr handle)))
    (cond
     ((symbol? arg) (symbol->string arg))    ; A not portable way to distinguish
					; between fields and strings
     ((boolean? arg) (sqld 'bool2db arg))
     ((string? arg) (sqld 'string2db arg))
     ((list? arg)
      (let ((s (sqld 'string2db (car arg))))
        (substring s 1 (- (string-length s) 1))))
     ((vector? arg)
      (vector-ref arg 0))
     ((integer? arg) (sqld 'int2db arg))
     ((number? arg) (sqld 'number2db arg))
     ((srfi:date? arg) (sqld 'date2db arg))
     (else
      (let ((str (open-output-string)))
        (write arg str)
        (let ((s (get-output-string str)))
          (close-output-port str)
          (sqli-2db handle s))
        )))))
;=verbatim
;
;The C<sqli-last-query> function returns the last query executed by the sqlid driver.
;
;=verbatim scm,8
(define (sqli-last-query handle)
  (check-handle 'sqli-last-query handle)
  (cadddr (cdr handle)))
;=verbatim
;
;The C<sqli-version> function returns the current version
;of SQLI.
;
;=verbatim scm,8
(define (sqli-version)
  (let* ((V "1.3"))
    (inexact->exact (round (* V 100)))))
;=verbatim
;
;The C<sqli-debug?> function returns if sqli is currently
;in debugging mode. With C<sqli-debug!> one can set debugging
;mode.
;
;=verbatim scm,8
(define (sqli-debug? handle)
  (cadr (cddddr (cddddr handle))))

(define (sqli-debug! handle d)
  (set-car! (cdr (cddddr (cddddr handle))) d)
  (sqli-debug? handle))

(define-syntax debug
  (syntax-rules ()
    ((_ handle str) (if (sqli-debug? handle)
                        (begin
                          (display "SQLI<debug>:")
                          (display str)
                          (newline))))))
;=verbatim
;
;With the F<sqli-closure> function, a closure can be made from the
;sqli-handle. This can be convenient, if one wants to have a more
;object oriented interface to the SQLI handle. For the sqli closure,
;all functions in sqli-E<lt>functionE<gt>, are replaced by 'function.
;Example:
;
;For C<(define a (sqli-closure sqli-handle))>: C<(a 'exec 'test)> does
;the same as C<(sqli-exec sqli-handle 'test)>.
;
;=verbatim scm,8
(define (sqli-closure handle)
  (check-handle 'sqli-closure handle)
  (let ((sqli handle))
    (lambda (cmd . args)
      (cond
       ((eq? cmd 'convert) (sqli-convert handle (car args) (cadr args)))

       ((eq? cmd 'fetchrow) (sqli-fetchrow handle))
       ((eq? cmd 'fetch) (sqli-fetch handle (car args)))
       ((eq? cmd 'fetchall) (sqli-fetchall handle))
       ((eq? cmd 'error?) (sqli-error? handle))

       ((eq? cmd 'exec) (sqli-internal-exec handle (car args) (cdr args)))
       ((eq? cmd 'query) (sqli-internal-query handle (car args) (cdr args)))

       ((eq? cmd 'begin) (sqli-begin handle))
       ((eq? cmd 'commit) (sqli-commit handle))
       ((eq? cmd 'rollback) (sqli-rollback handle))
	   ((eq? cmd 'register) (sqli-register handle 
                                               (car args)
                                               (cadr args)
                                               (caddr args)))

           ((eq? cmd 'error-message) (sqli-error-message handle))

           ((eq? cmd 'disconnect) (sqli-disconnect handle))

           (else (ierr (string-append "sqli-closure: Unknown command "
                                      (symbol->string cmd)
                                      " given")))))))
;=verbatim
;
;=head2 Internal functions
;
;These functions are used to facilitate exported functions.
;
;The F<sqli-make-query> function prepares a query by substituting
;all numbered arguments using the provided arguments. Also, it
;converts strings, integers, numbers and dates to database
;format. It works on a previously splitted query.
;
;=verbatim scm,8
(define (sqli-make-query handle query converter args)

  (let ((sqld (cadr handle)))

    (define (replace l i val)
      (if (null? l)
          #t
          (begin
            (if (number? (car l))
                (if (= (car l) i)
                    (set-car! l val)))
            (replace (cdr l) i val))))

    (define (convert arg)
      (let ((R (sqli-2db handle arg)))
        (debug handle (format "converting from ~s to ~s" arg R))
        R))

    (define (make-string l)
      (if (null? l)
          ""
          (string-append (car l) (make-string (cdr l)))))

    (define (make-query query args i)
      (if (null? args)
          (make-string query)
          (begin
            (replace query i (convert (car args)))
            (make-query query (cdr args) (+ i 1)))))

    (begin
      (set-car! (cdddr handle) converter)
      (set-car! (cddddr handle) (make-query query args 1))
      (car (cddddr handle)))))
;=verbatim
;
;The C<sqli-split-query> function is used to split a query
;with $n arguments in components.
;
;=verbatim scm,8
(define (sqli-split-query query)
  (do
	  ((l (list))
           (s 0)
           (k 0)
           (j 0)
           (N (string-length query))
           (i 0 (+ i 1)))
          ((>= i N)
           (reverse
            (if (= s 2)
                (cons (string->number (substring query (+ k 1) i)) (cons (substring query j k) l))
                (cons (substring query j i) l))))
          (let ((c (string-ref query i)))

            (if (= s 1)
                (if (and (char>=? c #\0) (char<=? c #\9))
                    (set! s 2)
                    (set! s 0))
                (if (= s 2)
                    (if (not (and (char>=? c #\0) (char<=? c #\9)))
                        (begin
                          (set! l (cons (string->number (substring query (+ k 1) i)) (cons (substring query j k) l)))
                          (set! s 0)
                          (set! j i)))))

            (if (= s 0)
              (if (char=? c #\$)
                  (begin
                    (set! s 1)
                    (set! k i)))))))
;=verbatim
;
;The F<ierr> function prints an error and returns C<#f>. This
;function is used to report errornous use of functions or
;other problems.
;
;=verbatim scm,8
(define (ierr . args)
  (define (f args)
    (if (null? args)
	(display "")
	(begin
	  (display (car args))
	  (f (cdr args)))))
  (display "ERROR: ")
  (f args)
  (newline)
  #f)
;=verbatim
;
;=cut

(provide  sqli-connect
	  sqli-disconnect
	  sqli-begin
	  sqli-query
	  sqli-fetchrow
	  sqli-fetchall
	  sqli-fetch
	  sqli-commit
	  sqli-rollback
      sqli-transaction-count
	  sqli? 
	  sqli-register
	  sqli-exec
	  sqli-closure
	  sqli-error-message
	  sqli-error? 
	  sqli-driver-name
	  sqli-driver-version
      sqli-last-query
      sqli-version
	  sqli-convert
      sqli-debug!
      sqli-debug?
	  sqli-2db))
;##