SrPersist Tutorial
==================

If you look at the doc.txt file in the SrPersist collection,
you soon realize that the ODBC standard is quite complex.
It is not necessary to master the standard before using it.
Performing simple data retrievals and insertions is not very 
difficult.  This tutorial provides some simple examples that
you can start with when writing your own code.

This tutorial does not address issues of compiling and 
installing SrPersist.  See the README file in the
SrPersist source directory for such information.

See the section "Loading" in doc.txt to learn how to load
SrPersist into Racket or DrRacket.  From Help Desk,
search for "SrPersist" and scroll down to that section.

Allocating handles
------------------

Before you can connect to a database, you need to allocate
an environment handle, and a connection handle:

    (define henv (alloc-env))
    (define hdbc (alloc-connect henv))

We bind these identifiers to the handle values, so that
we can refer to the handles at later points in the program.

Getting a connection
--------------------

SrPersist provides three procedures to connect to a database,
two of which we mention here.

When getting started, you can use

    (driver-connect hdbc "" 'sql-driver-prompt) 

where `hdbc' is the connection handle just allocated.
This procedure displays a dialog box, or series of them.
From the dialog boxes, you should be able to choose 
the database system and a particular database to
connect to.  This procedure returns a string, which
you can use in place of the empty string the next
time you need to call this procedure.  The string 
contains information about the database system and
database you chose through the dialogs.  Using that 
returned string, this procedure will not show a 
dialog box.

Alternatively, you can use

    (connect hdbc dbms name password) 	   	       

where again `hdbc' is the connection handle you've
allocated.  `dbms', `name', and `password' are strings
indicating a database system (a "data source" in ODBC 
parlance), a login name, and login password.  Unlike
driver-connect, you have to know the name of the
database system, which may not be obvious.  To find out
this information, you can call

    (data-sources henv 'sql-fetch-first)

to get a data source name and its description.  Calling
data-sources with 'sql-fetch-next gets the next data source;
you can continue making such calls until you've enumerated
all possible data sources.

Making a statement
------------------

Once your program is connected to a database system,
you'll want to submit queries in the form of SQL. 
Be patient, for it takes several steps to submit such 
a query.

First you'll need to allocate a statement handle
using the existing connection handle: 

    (define hstmt (alloc-stmt hdbc))

We'll see that we can reuse this statement handle
for several SQL queries.

When you connected to the database system, you 
chose some particular database.  The database system 
may contain several databases.  SQL has the USE statement 
to choose among them.  In SrPersist, we write:

    (prepare hstmt "USE test_db")
    (sql-execute hstmt)

Note that some database systems, such as Microsoft Access, 
do not allow you to switch databases in this way.  You can 
think of prepare as performing a compilation step; sql-execute 
runs the resulting code.

Now suppose the database test_db contains a table 
"people" that has columns for name, a string, and 
age, an integer. 

We can make a query to get the desired data from the 
database.

    (prepare hstmt "SELECT name,age FROM people")
    (sql-execute hstmt)

Conceptually, the statement above creates a new table,
consisting of rows of data.  We need some location
in our program to store the data.  ODBC uses buffers 
for data storage.  SrPersist associates an ODBC C type 
with each buffer.

Assume that the name column consists of strings 
no longer than 50 characters.  We create a 
buffer to hold results:

    (define name-buffer (make-buffer '(sql-c-char 50)))

For the age column:

    (define age-buffer (make-buffer 'sql-c-slong))

There are ways to find out the types associated with columns,
but unfortunately, it's a complicated business.  There are
actually distinct types (SQL types) for the columns themselves, 
and separate C types for buffers that receive their data.
But 'sql-c-char is probably what you want for string buffers,
and 'sql-c-slong for integer buffers.

We'll need another kind of buffer, an "indicator":

    (define name-indicator (make-indicator))
    (define age-indicator (make-indicator))

These indicators do not hold data, just status information.
We can safely ignore their role for the remainder of this
tutorial.

Next, we wish to associate the buffers we've created with 
the database columns:

    (bind-col hstmt 1 name-buffer name-indicator)
    (bind-col hstmt 2 age-buffer age-indicator)

Columns are numbered from 1.  Although the people table
may have had the name and age at any position, our 
query above created the name column as column 1,
and the age column as column 2.

Now we can retrieve the data and print it out:

    (with-handlers 
     ([(lambda (exn) (exn-no-data? exn)) 
       (lambda (exn) (printf "** End of data **\n"))])
     (let loop ()
       (fetch hstmt)
       (printf "Name: ~a  Age: ~a\n" 
	       (read-buffer name-buffer)
	       (read-buffer age-buffer))
       (loop)))

The code loops through each row and prints the 
values stored in the buffers.  When all the 
data has been read, the call to fetch raises 
the exn-no-data exception.

Suppose we want to insert a new record into the table people.
Assume that the table consists of the columns name, address,
and age.  To perform the insertion, we simply write the 
appropriate SQL, and run it:

    (prepare hstmt 
             (string-append "INSERT INTO people SET "
                            "name=\"Joe Bloggs\","
                            "address=\"123 Main Street\","
                            "age=42")) 
    (sql-execute hstmt)			    

If you now perform the SELECT query above, and run the 
given loop over the results, you should see the effect 
of the insertion.

While there's much more in the ODBC standard, this example
code should give you the flavor of how it works.

