Using the Low-Level Interface
Using the Low-Level Interface
You can use the low-level interface to establish communication (initiate a
session) with a data server, send a query to the data server, execute the query,
and retrieve any data requested by the query. You call one or more low- level
routines to accomplish each of these tasks.
Applications that implement this type of data access must provide user
control and feedback, as described in the section entitled,
General Guidelines for the User Interface . When the data source is
ready to return data, you can retrieve it all and then display it to the user, or
you can display the data as it arrives. If the data arrives slowly, it's best to
display it one record at a time as it arrives. This way the user can preview the
data, decide if it's the desired information, and cancel the query if not.
The Figure below is a flowchart of a typical session using the low- level
interface.
As illustrated, you must follow this procedure to use the low-level interface:
1. Call the InitDBPack function to initialize the
Data Access Manager.
2. Call the DBInit function to establish communication with the data
server. The DBInit function returns an identification number, called
a session ID. This session ID is unique; no other current session, for
any database extension, has the same session ID. You must specify the
session ID any time you want to send data to or retrieve data from this
session.
The DBInit function requires as input parameters the name of the database
extension and character strings for the host system, user name, password, and
connection string. All of these parameters depend on the user and the user's
computer system, including the specific database extension, host computer,
data server, and database management software in use. You will not know the
user name and password when you are writing an application, and you might
not know the values of any of these parameters. Therefore, you must display a
dialog box that prompts the user for the necessary information.
A flowchart of a session using the low-level interface
Depending on the database extension you are using, the DBInit function might
return a session ID of zero if it fails to initiate a session, or it might return a
nonzero session ID and a result code other than noErr. In the latter case, you
can pass the session ID to the DBGetErr function to determine the cause of the
error. If the DBInit function returns a nonzero session ID and a result code
other than noErr, you must call the DBEnd function before making another
attempt to open the session.
3. Prepare a query, and send it to the data server by calling the
DBSend and DBSendItem functions one or more times.
An application that uses the low-level interface must be capable of creating a
query for the data server in the language and format required by that data
server.
The DBSend function sends a query or a portion of a query to the data server.
The data server appends this portion of the query to any portion you sent
previously. Because the Data Access Manager and data server do not modify
the string you send in any way, they do not insert any delimiter between
fragments of queries that you send to the data server. If you want a blank or a
semicolon to be included between query fragments, or if you want to use return
characters to divide the query into lines of text, you must include them in the
character string that you send with the DBSend function. The data string that
you send with the DBSend function can be any length up to 64 KB.
The DBSendItem function sends a single data item to the data server. Use the
DBSendItem function to send data items to the data source in the same
format as they are retrieved from the data source by the DBGetItem function.
You must specify the data type as an input parameter and, for any data type that
does not have an implied length, you must specify the length as well. The
database extension or the data server (depending on how the system is
implemented) converts the data item to a character string and appends it to the
query, just as a query program fragment is appended to the query by the
DBSend function.
You can call the DBSend and DBSendItem functions as many times as you
wish to send your query to the data server.
The code example below sends the Data Access Language query fragment "print
451+222;" to the Data Access Language server.
// Sending a query fragment
// Assuming inclusion of
#include <DatabaseAccess.h>
#include <AppleEvents.h>
#include < string.h>
long value1;
long value2;
char text1[16];
char text2[16];
char text3[16];
long sessID;
strcpy(text1, "print");
value1 = 451;
strcpy (text2, "+");
value2 = 222;
strcpy (text3, ";");
rc = DBSend ( sessID, (Ptr) text1, strlen(text1), nil);
if (!rc)
rc = DBSendItem ( sessID, typeInteger, 0, 0, 0,
(Ptr) & value1, nil);
if (!rc)
rc = DBSend ( sessID, (Ptr) text2,
strlen(text2), nil);
if (!rc)
rc = DBSendItem ( sessID, typeInteger, 0, 0, 0,
(Ptr) & value2, nil);
if (!rc)
rc = DBSend ( sessID, (Ptr) text3,
strlen(text3), nil);
return rc;
4. Use the DBExec function to initiate execution of the query.
Depending on the way the system you are using is implemented, the DBExec
function might return control to your application as soon as the query has
begun execution.
5. Use the DBState function to determine the status of the data source.
The DBState function tells you when the data server has finished executing
the query you just sent. If you have requested data, the data server stores the
data you requested but does not send it to your application until you request it
explicitly. The DBState function tells you when the data is available; if data is
available, go on to step 6. If you wish to send another query, return to step 3.
If you are finished using the data source, skip to step 7.
6. Call the DBGetItem function repeatedly to retrieve the data.
The DBGetItem function retrieves the next data item from the data server.
You can also use this function to obtain information about the next data item
without retrieving the data. When you use the DBGetItem function to retrieve
a data item, you must specify the location and size of the buffer into which the
function is to place that item. If you know beforehand what kind of data to
expect, you can allocate a buffer of the exact size you need. If you do not know
what type of data to expect, you can first call the DBGetItem function with a
NIL pointer to the data buffer. The DBGetItem function then returns
information about the next data item without actually retrieving it. You can
then allocate the appropriate buffer and call DBGetItem again.
Alternatively, to avoid calling DBGetItem twice for each data item, you can
allocate a buffer that you expect to be of sufficient size for any data item and
call the DBGetItem function. If the buffer is not large enough for the data
item, the DBGetItem function returns the rcDBError result code, but still
returns information about the data item. You can then allocate the necessary
buffer, call the DBUnGetItem function to go back one data item, and call the
DBGetItem function again to retrieve the data item a second time.
The DBGetItem function includes a timeout parameter that you can use to
specify the maximum amount of time that the database extension should wait to
receive results from the data server before canceling the command. If the
database extension you are using does not support asynchronous execution of
routines, you can use the timeout parameter to return control to your
application while a query is executing. To use the timeout parameter in this
way, call the DBGetItem function periodically with a short value set for the
timeout parameter. Your application can then retrieve the next data item as
soon as execution of the query is complete without having to call the DBState
function to determine when data is available. The DBGetItem function ignores
the timeout parameter if you make an asynchronous call to this function.
7. When you are finished using the data source, you must use the
DBEnd function to terminate the session. You must call the DBEnd function
after the DBInit function has returned a nonzero session ID, even if it also
returned an error.
The Listing below uses the low-level interface to send a Data Access Language
routine to the Data Access Language server on a remote computer, and
retrieves the results. The code initiates a session with a remote database and
calls the MySendFragment routine to send a query. Next, it executes the query,
checks the status of the remote database server, and retrieves the data when
it's available. This example retrieves only one data item. To retrieve more
than one data item, put the data-retrieval code in a loop.
The Listing below assumes that the database extension does not support
asynchronous execution of Data Access Manager routines.
// Using the low-level interface
// Assuming inclusion of
#include <DatabaseAccess.h>
#include < string.h>
void MyLoLevel (long * thisSession, OSErr * sessErr);
OSErr MySendFragment (long thisSession);
void GoDoSomething (void);
Ptr GimmeMySpace ( short len);
void MyLoLevel (long * thisSession, OSErr * sessErr)
{
Str63 theDDevName;
Str255 theHost, theUser;
Str255 thePasswd, theConnStr;
OSErr packErr, initErr, sendErr, execErr,
stateErr, getErr, endErr;
long myTimeout;
DBType myType;
short len, places, flags;
Ptr myBuffer;
Boolean myDataInfo;
Boolean myDataReturned;
*sessErr = noErr; [TOKEN:12079] Assume everything went fine
packErr = InitDBPack(); [TOKEN:12079] Init the Data Access Mgr
// Set up values for theDDevName, theHost, theUser, thePasswd,
// and theConnStr. You can display a dialog box prompting
// the user to supply some of these parameters.
strcpy ((char *) theDDevName, (char *) "\pDAL");
strcpy ((char *) theHost, (char *) "\pThe Host System Name");
strcpy ((char *) theUser, (char *) "\pJoe User");
strcpy ((char *) thePasswd, (char *) "\psecret");
strcpy ((char *) theConnStr, (char *) "\pextra stuff as needed");
initErr = DBInit( thisSession, (ConstStr63Param) theDDevName,
(ConstStr255Param) theHost, (ConstStr255Param) theUser,
(ConstStr255Param) thePasswd, (ConstStr255Param) theConnStr,
nil);
if (initErr) {
*sessErr = initErr;
if (* thisSession) {
endErr = DBEnd(* thisSession, nil);
return;
}
}
// Send a query or query fragment to the remote data server.
// MySendFragment is such a routine.
sendErr = MySendFragment(* thisSession);
// If there's an error, then probably something went wrong with
// DBSend or DBSendItem. Don't forget to end the session.
if (sendErr) {
*sessErr = sendErr;
endErr = DBEnd(* thisSession, nil);
return;
}
// The query has been sent. This example assumes that
// the query will return data.
execErr = DBExec(* thisSession, nil);
if (!execErr) {
// While waiting for stateErr != rcDBExec you can
// let other apps run by calling WaitNextEvent.
// GoDoSomething does that.
stateErr = rcDBExec;
while (stateErr == rcDBExec) {
GoDoSomething();
stateErr = DBState(* thisSession, nil);
}
// DBState returned a result code other than rcDBExec.
// If it's rcDBValue, there are results to retrieve.
// Otherwise, it's probably an error.
if (stateErr == rcDBValue) {
// Call DBGetItem once to get info on
// the data item and call DBGetItem a second time
// to actually get the data item.
myTimeout = 2*60; // 2*60 ticks = 2 secs
myType = typeAnyType;
myDataInfo = FALSE;
while (!myDataInfo) {
getErr = DBGetItem(* thisSession, myTimeout, &myType,
&len, & places, &flags, nil, nil);
// If you timed out, then give up control. When
// control returns, continue getting the info.
if (getErr == rcDBBreak)
GoDoSomething();
else if ( (!getErr) || (getErr == rcDBValue) )
myDataInfo = TRUE;
else {
*sessErr = getErr;
endErr = DBEnd(* thisSession, nil);
return;
}
} // while
// At this point, you may want to examine the info
// about the data item before calling DBGetItem a
// second time to actually retrieve it.
// GimmeMySpace returns a pointer to where you want
// the data item to go.
myBuffer = GimmeMySpace(len);
myDataReturned = FALSE;
while (!myDataReturned) {
getErr = DBGetItem(* thisSession, myTimeout, &myType,
&len, & places, &flags, myBuffer, nil);
// If you timed out, then give up control. When
// control returns, continue getting the data.
if (getErr == rcDBBreak)
GoDoSomething();
else if ( (!getErr) || (getErr == rcDBValue) )
myDataReturned = TRUE;
else {
*sessErr = getErr;
endErr = DBEnd(* thisSession, nil);
return;
}
} // while
}
else
*sessErr = stateErr;
}
else
*sessErr = execErr;
endErr = DBEnd(* thisSession, nil);
}
Note that, even if you are using the low-level interface to send queries to the
data server, you might want to use the high-level functions to retrieve data
and convert it to text.