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.