;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;;; Package : sqld-psql.scm ;;; Author : Hans Oesterholt-Dijkema. ;;; Copyright : (c) 2007. ;;; License : LGPL ;;; CVS : $Id: sqld-psql-internal.scm,v 1.1 2007/07/05 20:12:43 hoesterholt Exp $ ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;=pod ; ;=head1 Name ; ;sqld-psql - SQL Driver for PostgreSQL ; ;=head1 Description ; ;This is an FFI PostgreSQL driver for SQLI. It solves some problems ;of the sqld-psql driver that is used through the C<spgsql.plt> planet ;package. ; ;It is a simple driver, that has no optimizations like cursor operations, ;connection pools, etc. However, it is an asynchronous driver. This means, ;that scheme will be able to execute threads while queries are active. ; ;This driver conforms to ;L<the interface description for drivers|SQLD - Interface description for SQLI drivers>. ; ;The driver must be used through SQLI. ; ;=head1 API ; ;=head2 C<(sqld-psql-new connection-info) : closure> ; ;Calling this function with a valid PostgreSQL connection string ;(containing host, dbname, user, etc.), ;will instantiate a new driver, that can be given to a new ;instance of SQLI. ; ;=head1 Synopsis ; ;=syn scm,8 ; ; (module test mzscheme ; (require (planet "sqli.scm" ("oesterholt" "sqlid.plt" 1 0))) ; (require (planet "sqld-psql.scm" ("oesterholt" "sqld-psql-ffi.plt" 1 0))) ; (provide main) ; ; (define (main) ; (let* ((sqld (sqld-psql-new "user=test hostname=localhost dbname=test")) ; (sqli (sqli-connect sqld))) ; (...) ; ;=head1 Literate section ; ;This module L<interfaces with a C part|SQLD-PSQL C part> that interfaces to the ;psql library. B<The interface is built for PostgreSQL version E<gt>=8.0>. ; ;=head2 Module definition ; ;The module definition is as follows: ; ;=verbatim scm,8 (module sqld-psql-internal mzscheme (require (lib "time.ss" "srfi" "19")) (require "c-sqld-psql.scm") ;=verbatim ; ;As can be seen, only one function is exported, the C<sqld-psql-new> function. ;All other function definitions are interface definitions for C functions that ;are called from this module. ; ;=head2 Supportive functions ; ;In the next section, supportive functions and definitions are described. ; ;The C<ierr> function displays a message and returns C<#f>. This function is ;simply used to report errors to the current output port. ; ;=verbatim scm,8 (define (ierr msg) (display msg) (newline) #f) ;=verbatim ; ;=head2 Conversion functions ; ;Conversion functions are used to convert between database representations ;of types and scheme representations of types. They are all straightforward. ; ;Psql is SQL92 compliant, so for all strings, the single quote must be ;escaped. A simple C<pregexp-replace*> call is used to escape the single ;quotes. This function could be made more efficient, using a loop, or ;even a C function to do the same. ; ;=verbatim scm,8 (define (string2db conn s debug) (string-append "'" (pg-escape conn s debug) "'")) ;=verbatim ; ;A PostgreSQL date type is constructed ;from the bigloo date type, using a the predescribed PostgreSQL encoding, ;without a zone part. ; ;The interpretation back from the database is done by expecting the ;same encoding. No checking is done for the parts of the ;strings; so, the ;precondition for the use of this function is, that the given string ;conforms to the previous definition. ; ;=verbatim scm,8 ;#+ mzscheme (define-syntax integer->string (syntax-rules () ((integer->string s) (number->string s)))) (define-syntax string->integer (syntax-rules () ((string->integer s) (string->number s)))) ;## (define (pre-zero2 n) (if (< n 10) (string-append "0" (integer->string n)) (integer->string n))) (define (date2db dt) (string-append "'" (date->string dt "~Y~m~dT~H~M~S") "'")) (define (db2date dt) (string->date dt "~Y-~m-~d ~H:~M:~S") ) ;=verbatim ; ;All other conversions are done using the standard scheme primitives. ; ;=head2 Connecting ; ;The connection function is called from the closure provided ;by C<sqld-psql-new>, when it is called with the C<'connect> ;argument. It returns a closure that is used for further ;command processing and that has a connection to the Psql ;database. ; ;The commands to be processed are placed in a C<cond> structure, ;with the probably most commonly used commands at front. ; ;Supportive functions are defined within the closure, to handle ;the interfacing for queries to the C part and fetches. ; ;=verbatim scm,8 (define (sqld-psql-connect connection-info null-value debug sync) (let ((db (pg-connect connection-info debug)) (current-query-result #f) (valid-handle #t) (nrows 0) (ncols 0) (row 0) ) (define (query q) (begin (set! current-query-result (pg-query db q debug)) (set! row -1) (set! ncols (pg-ncols current-query-result)) (set! nrows (pg-nrows current-query-result)))) (define (fetch) (set! row (+ row 1)) (pg-row current-query-result row null-value)) (lambda (cmd . args) (if (eq? valid-handle #f) (ierr "ERROR: disconnected handle") (cond ((eq? cmd 'string2db) (string2db db (car args) debug)) ((eq? cmd 'int2db) (integer->string (car args))) ((eq? cmd 'number2db) (number->string (car args))) ((eq? cmd 'date2db) (date2db (car args))) ((eq? cmd 'bool2db) (if (eq? (car args) #t) "'t'" "'f'")) ((eq? cmd 'db2date) (db2date (car args))) ((eq? cmd 'db2bool) (if (string=? (car args) "t") #t #f)) ((eq? cmd 'fetchrow) (if (eq? current-query-result #f) #f (fetch))) ((eq? cmd 'lasterr) (pg-error-message (if (eq? current-query-result #f) db current-query-result))) ((eq? cmd 'begin) (query "BEGIN;")) ((eq? cmd 'commit) (query "COMMIT;")) ((eq? cmd 'rollback) (query "ROLLBACK;")) ((eq? cmd 'query) (query (car args))) ((eq? cmd 'null-value) (begin (if (not (null? args)) (set! null-value (car args))) null-value)) ((eq? cmd 'debug) (begin (if (not (null? args)) (if (eq? (car args) #f) (set! debug pg-nodebug) (if (procedure? (car args)) (set! debug (car args)) (set! debug pg-debug)))) debug)) ((eq? cmd 'disconnect) (begin (pg-finish db debug) (set! valid-handle #f))) (else (ierr "Unknown command"))))))) ;=verbatim ; ;=head2 The main entry function ; ;Now for the main function that this driver provides: C<sqld-psql-new>. ;This function takes C<connection-info> as an argument, which must be ;an Psql database. It returns a closure that handles the C<'connect>, ;C<'clean>, C<'name> and C<'version> calls. It is a very simple function. ; ;The C<'version> call returns the major version number of PostgreSQL * 100 + ;the middle version number * 10 + the minor version number. ; ;=verbatim scm,8 (define (sqld-psql-new _connection-info) (let ((connection-info _connection-info) (null-value "") (debug pg-nodebug) (synchronous #t)) (lambda (cmd . args) (cond ((eq? cmd 'connect) (sqld-psql-connect connection-info null-value debug synchronous)) ((eq? cmd 'clean) #t) ((eq? cmd 'name) "psql-ffi") ((eq? cmd 'version) (pg-version)) ((eq? cmd 'null-value) (begin (if (not (null? args)) (set! null-value (car args))) null-value)) ((eq? cmd 'debug) (begin (if (not (null? args)) (if (eq? (car args) #f) (set! debug pg-nodebug) (if (procedure? (car args)) (set! debug (car args)) (set! debug pg-debug)))) debug)) ((eq? cmd 'async) (begin (set! synchronous #f) synchronous)) ((eq? cmd 'sync) (begin (set! synchronous #t) synchronous)) (else (ierr "ERROR: Connect to the datebase first")))))) ;=verbatim ; ;=head2 Setting the null value ; ;The default value returned for null values is the empty string (i.e. ""). ;If an other value is needed, e.g. C<'null>, one can set an alternative value ;using the C<sqld-psql-null-value> function on an C<sqld> object. ; ;=verbatim scm,8 (define (sqld-psql-null-value sqld . v) (apply sqld (cons 'null-value v))) ;=verbatim ; ;=head2 Debugging info ; ;If you want to have some information on what's going on with the server, ;turn debugging on, e.g. using C<(sqld-psql-debug sqld #t)>. ; ;=verbatim scm,8 (define (sqld-psql-debug sqld . v) (apply sqld (cons 'debug v))) ;=verbatim ; ;=head2 Asynchronous connection ; ;If you want to connect asynchronously to the database, call this function ;with #t, otherwise with #f. sqld-psql-ffi defaults to synchronous connecting. ;Asynchronous connecting doesn't work on all platforms. ; ;=verbatim scm,8 (define (sqld-psql-async-connect sqld yes) (if yes (sqld 'async) (sqld 'sync))) ;=verbatim ; ;=cut (provide sqld-psql-new sqld-psql-null-value sqld-psql-async-connect sqld-psql-debug))