Contents

Name

SQLI - SQL Interface module

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.

Author

Hans Oesterholt-Dijkema <hans-at-elemental-programming%dt#org>.

License

This module is distributed under the LGPL. (c) 2004/2005 Hans Oesterholt-Dijkema.

Version

$Id: sqli.scm,v 1.8 2007/04/29 12:26:19 hoesterholt Exp $

Synopsis

 (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))

API

Connection handling

(sqli-connect sql-driver) : sqli-handle

This function connects to the database represented by sql-driver. It returns a handle to the driver, or #f, if something went wrong.

(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.

Queries

(sqli-query sqli-handle query . args) : boolean

sqli-query executes an SQL query to the sqli-handle. An SQL statement can contain arguments of form '$n', where 1<=n<=(length args) Arguments are interpreted:

  • Symbols are converted to strings, without quotes (usefull for column names, operations, etc.).

  • strings are converted to quoted strings ((var)char, etc. in SQL).

  • 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.

  • integers and numbers are converted accordingly.

  • date types are converted to Database dependent timestamps.

  • boolean types are converted to Database dependent booleans.

This function returns #t, if an error occured, #f, otherwise.

(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.

(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:

'string

Type string is expected.

'int

Type integer (exact number) is expected.

'number

Type number (exact or inexact) is expected.

'date

Type date is expected.

'bool

Type boolean is expected.

(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 sqli-query.

Transaction processing

Please note: The transaction functions can be called recursively, but one must not intermix commits and rollbacks!

(sqli-begin sqli-handle) : boolean

This function starts a transaction on the given sqli-handle. It returns #t, if something went wrong, #f, othwerwise. This function can be called recursively. Only the first begin will be given to the sqld driver.

(sqli-commit sqli-handle) : boolean

This function commits a transaction on the given sqli-handle. It returns #t, if something went wrong, #f, othwerwise. This function can be called recursively. Only the last commit will be given to the sqld driver.

(sqli-rollback sqli-handle) : boolean

This function rolls back a transaction on the given sqli-handle. It returns #t, if something went wrong, #f, othwerwise. This function can be called recursively. Only the last rollback will be given to the sqld driver.

Fetching

(sqli-fetchrow sqli-handle) : list

sqli-fetchrow fetches a row from the last query executed. It returns #f, if no (more) rows can be fetched, returns a list of results (depending on the conversion function, this can be different) otherwise (see synopsis).

(sqli-fetch sqli-handle count) : list

sqli-fetch fetches at most count rows from the last query for sqli-handle. Returns a list of rows (or the empty list, if no row could be fetched).

(sqli-fetchall sqli-handle) : list

sqli-fetchall fetches all rows from the last query for sqli-handle. Returns a list of rows like sqli-fetch. If the first sqli-fetch returns #f, the list will be empty (null? property).

Closure interface

(sqli-closure sqli-handle) : procedure

Makes a closure from sqli-handle, that can be treated like an object interface for the sqli-handle. All functions are called through this closure, using a symbol that denotes the function. E.g.:

(sqli-closure 'fetchrow), fetches a row.

Predicates, error handling and conversions

(sqli? obj) : boolean

Returns #t, if obj is of type sqli. Note: (list? sqli-handle) will return #t also.

(sqli-error? handle)

Returns #t, if the last query reported an error. Returns #f, otherwise. SQL error: database is locked

(sqli-error-message handle)

Returns the error message that complements the sqli-error? indication.

(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.

(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 sql-driver and the sqli connection handle.

(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 sql-driver and the sqli connection handle.

(sqli-last-query) : string

Returns the last query that has been executed by the sqlid driver.

(sqli-version) : integer

Returns the version of SQLI as an integer. Major part*100+minor part.

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")

Literate part

Module descriptor

This SQLI module has been designed for use with mzscheme. The sqli module begins with a module description.

(module sqli mzscheme
        (require (lib "time.ss" "srfi" "19"))

In the module description, all exported functions are defined.

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 sql-driver that has been given to the sqli-connect function. This is a closure that handles commands. See the interface description 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 'nil-converter indicates that there is no conversion function.

Exported functions

The sqli? function returns true, if obj is a list, not null and the car of this list is equal to 'sqli.

(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))))

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.

(define (sqli-connect sql-driver)
  (list 'sqli (sql-driver 'connect) (list) 'nil-converter "" 0 
        (sql-driver 'name) (sql-driver 'version) "" #f))

(define (sqli-disconnect handle)
  (check-handle 'sqli-disconnect handle)
  ((cadr handle) 'disconnect))

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

Error handling is very simple. The 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.

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

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

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.

(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)))

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 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.

(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)))

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 #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 #f, the fetchall function will fail to retreive all rows.

The functions for fetching all rows or count rows are simply implemented by subsequental calls to sqli-fetchrow.

(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))))

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 sqli-query or 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 (sqli-query sqli-handle "SELECT $1 FROM test WHERE $1>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 sqli-exec. For two different queries, that are registered subsequently under the same symbol, only the last one can be retreived.

(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))

The 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 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.


;#+ 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))
        )))))

The sqli-last-query function returns the last query executed by the sqlid driver.

(define (sqli-last-query handle)
  (check-handle 'sqli-last-query handle)
  (cadddr (cdr handle)))

The sqli-version function returns the current version of SQLI.

(define (sqli-version)
  (let* ((V "1.3"))
    (inexact->exact (round (* V 100)))))

The sqli-debug? function returns if sqli is currently in debugging mode. With sqli-debug! one can set debugging mode.

(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))))))

With the 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-<function>, are replaced by 'function. Example:

For (define a (sqli-closure sqli-handle)): (a 'exec 'test) does the same as (sqli-exec sqli-handle 'test).

(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")))))))

Internal functions

These functions are used to facilitate exported functions.

The 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.

(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)))))

The sqli-split-query function is used to split a query with $n arguments in components.

(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)))))))

The ierr function prints an error and returns #f. This function is used to report errornous use of functions or other problems.

(define (ierr . args)
  (define (f args)
    (if (null? args)
        (display "")
        (begin
          (display (car args))
          (f (cdr args)))))
  (display "ERROR: ")
  (f args)
  (newline)
  #f)