Programmatic SQL
Programmatic SQL
Programmatic SQL
Appendix
E
Objectives
Programmatic SQL
In Chapters 5 and 6 we discussed in some detail the Structured Query Language (SQL)
and, in particular, the data manipulation and data definition facilities. In Section 5.1.1 we
mentioned that the 1992 SQL standard lacked computational completeness: it contained
no flow of control commands such as IF . . . THEN . . . ELSE, GO TO, or DO . . . WHILE.
To overcome this and to provide more flexibility, SQL allows statements to be embedded
in a high-level procedural language, as well as being able to enter SQL statements inter-
actively at a terminal. In the embedded approach, flow of control can be obtained from the
structures provided by the programming language. In many cases, the SQL language is
identical, although the SELECT statement, in particular, requires more extensive treatment
in embedded SQL.
In fact, we can distinguish between two types of programmatic SQL:
n Embedded SQL statements SQL statements are embedded directly into the program
source code and mixed with the host language statements. This approach allows users
to write programs that access the database directly. A special precompiler modifies the
source code to replace SQL statements with calls to DBMS routines. The source code
can then be compiled and linked in the normal way. The ISO standard specifies embedded
support for Ada, ‘C’, COBOL, Fortran, MUMPS, Pascal, and PL/1 programming languages.
n Application Programming Interface (API) An alternative technique is to provide the
programmer with a standard set of functions that can be invoked from the software. An
API can provide the same functionality as embedded statements and removes the need
for any precompilation. It may be argued that this approach provides a cleaner interface
and generates more manageable code. The best-known API is the Open Database
Connectivity (ODBC) standard.
..
DS4_Web1.qxd 23/04/2004 18:39 Page 2
Most DBMSs provide some form of embedded SQL, including Oracle, INGRES,
Informix, and DB2; Oracle also provides an API; Access provides only an API (called
ADO – ActiveX Data Objects – a layer on top of ODBC).
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 3
Figure E.1
Embedded SQL
program to create
Viewing table.
..
DS4_Web1.qxd 23/04/2004 18:39 Page 4
In Oracle, we need not follow a data definition statement with a COMMIT statement
because data definition statements issue an automatic COMMIT before and after executing.
Therefore, the COMMIT statement in this example program (Figure E.1) could have been
safely omitted. In addition, the RELEASE option of the COMMIT statement causes the sys-
tem to free all Oracle resources, such as locks and cursors, and to disconnect from the database.
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 5
n An SQLCODE of zero indicates that the statement executed successfully (although there
may be warning messages in sqlwarn).
n A negative SQLCODE indicates that an error occurred. The value in SQLCODE indicates
the specific error that occurred.
n A positive SQLCODE indicates that the statement executed successfully, but an exceptional
condition occurred, such as no more rows returned by a SELECT statement (see below).
In Example E.1 we checked for a negative SQLCODE (sqlca.sqlcode < 0) for unsuccessful
completion of the CONNECT and CREATE TABLE statements.
The WHENEVER statement consists of a condition and an action to be taken if the con-
dition occurs, such as continuing with the next statement, calling a routine, branching to a
labeled statement, or stopping. The condition can be one of the following:
n SQLERROR tells the precompiler to generate code to handle errors (SQLCODE < 0).
n SQLWARNING tells the precompiler to generate code to handle warnings (SQLCODE > 0).
n NOT FOUND tells the precompiler to generate code to handle the specific warning that
a retrieval operation has found no more records.
The action can be:
n CONTINUE, to ignore the condition and proceed to the next statement.
n DO, to transfer control to an error handling function. When the end of the routine is
reached, control transfers to the statement that follows the failed SQL statement (unless
the function terminates program execution).
n DO BREAK, to place an actual ‘break’ statement in the program. This is useful if used
within a loop to exit that loop.
n DO CONTINUE, to place an actual ‘continue’ statement in the program. This is useful
if used within a loop to continue with the next iteration of the loop.
n GOTO label, to transfer control to the specified label.
n STOP, to rollback all uncommitted work and terminate the program.
For example, the WHENEVER statement in the code segment:
EXEC SQL WHENEVER SQLERROR GOTO error1;
EXEC SQL INSERT INTO Viewing VALUES (‘CR76’, ‘PA14’, ‘12-May-2004’,
‘Not enough space’);
EXEC SQL INSERT INTO Viewing VALUES (‘CR77’, ‘PA14’, ‘13-May-2004’,
‘Quite like it’);
..
DS4_Web1.qxd 23/04/2004 18:39 Page 6
Indicator variables
Most programming languages do not provide support for unknown or missing values, as
represented in the relational model by nulls (see Section 3.3.1). This causes a problem
when a null has to be inserted or retrieved from a table. Embedded SQL provides indicator
variables to resolve this problem. Each host variable has an associated indicator variable
that can be set or examined. The meaning of the indicator variable is as follows:
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 7
CHAR char
CHAR(n), VARCHAR2(n) char[n + 1]
NUMBER(6) int
NUMBER(10) long int
NUMBER(6, 2) float
DATE char[10]
n An indicator value of zero means that the associated host variable contains a valid value.
n A value of −1 means that the associated host variable should be assumed to contain a
null (the actual content of the host variable is irrelevant).
n A positive indicator value means that the associated host variable contains a valid value,
which may have been rounded or truncated (that is, the host variable was not large
enough to hold the value returned).
..
DS4_Web1.qxd 23/04/2004 18:39 Page 8
is more complicated if the query produces more than one row. The complication results
from the fact that most high-level programming languages can process only individual
data items or individual rows of a structure whereas SQL processes multiple rows of data.
To overcome this impedance mismatch (see Section 25.2), SQL provides a mechanism for
allowing the host language to access the rows of a query result one at a time. Embedded
SQL divides queries into two groups:
n single-row queries, where the query result contains at most one row of data;
n multi-row queries, where the query result may contain an arbitrary number of rows,
which may be zero, one, or more.
Single-row queries
In embedded SQL, single-row queries are handled by the singleton select statement,
which has the same format as the SELECT statement presented in Section 5.3, with an
extra INTO clause specifying the names of the host variables to receive the query result.
The INTO clause follows the SELECT list. There must be a one-to-one correspondence
between expressions in the SELECT list and host variables in the INTO clause. For ex-
ample, to retrieve details of owner CO21, we write:
EXEC SQL SELECT fName, lName, address
INTO :firstName, :lastName, :address :addressInd
FROM PrivateOwner
WHERE ownerNo = ‘CO21’;
In this example, the value for column fName is placed into the host variable firstName, the
value for lName into lastName, and the value for address into address (together with the
null indicator into addressInd ). As previously discussed, we have to declare all host vari-
ables beforehand using a BEGIN DECLARE SECTION.
If the singleton select works successfully, the DBMS sets SQLCODE to zero; if
there are no rows that satisfies the WHERE clause, the DBMS sets SQLCODE to NOT
FOUND. If an error occurs or there is more than one row that satisfies the WHERE clause,
or a column in the query result contains a null and no indicator variable has been specified
for that column, the DBMS sets SQLCODE to some negative value depending on the
particular error encountered. We illustrate some of the previous points concerning host
variables, indicator variables, and singleton select in the next example.
Produce a program that asks the user for an owner number and prints out the owner’s
name and address.
The program is shown in Figure E.3. This is a single-row query: we ask the user for an
owner number, select the corresponding row from the PrivateOwner table, check that the
data has been successfully returned, and finally print out the corresponding columns. When
we retrieve the data, we have to use an indicator variable for the address column, as this
column may contain nulls.
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 9
Figure E.3
Single-row query.
..
DS4_Web1.qxd 23/04/2004 18:39 Page 10
Multi-row queries
When a database query can return an arbitrary number of rows, embedded SQL uses
cursors to return the data. As discussed in the context of Oracle’s PL/SQL language in
Section 8.2.5, a cursor allows a host language to access the rows of a query result one at
a time. In effect, the cursor acts as a pointer to a particular row of the query result. The
cursor can be advanced by one to access the next row. A cursor must be declared and
opened before it can be used, and it must be closed to deactivate it after it is no longer
required. Once the cursor has been opened, the rows of the query result can be retrieved
one at a time using a FETCH statement, as opposed to a SELECT statement.
The DECLARE CURSOR statement defines the specific SELECT to be performed and
associates a cursor name with the query. The format of the statement is:
For example, to declare a cursor to retrieve all properties for staff member SL41, we write:
The OPEN statement executes the query and identifies all the rows that satisfy the query
search condition, and positions the cursor before the first row of this result table. In Oracle,
these rows form a set called the active set of the cursor. If the SELECT statement contains
an error, for example a specified column name does not exist, an error is generated at this
point. The format of the OPEN statement is:
For example, to open the cursor for the above query, we write:
The FETCH statement retrieves the next row of the active set. The format of the FETCH
statement is:
where cursorName is the name of a cursor that is currently open. The number of host vari-
ables in the INTO clause must match the number of columns in the SELECT clause of the
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 11
corresponding query in the DECLARE CURSOR statement. For example, to fetch the next
row of the query result in the previous example, we write:
The FETCH statement puts the value of the propertyNo column into the host variable
propertyNo, the value of the street column into the host variable street, and so on. Since
the FETCH statement operates on a single row of the query result, it is usually placed
inside a loop in the program. When there are no more rows to be returned from the query
result table, SQLCODE is set to NOT FOUND, as discussed above for single-row queries.
Note, if there are no rows in the query result table, the OPEN statement still positions the
cursor ready to start the successive fetches, and returns successfully. In this case, it is the
first FETCH statement that detects there are no rows and returns an SQLCODE of NOT
FOUND.
The format of the CLOSE statement is very similar to the OPEN statement:
where cursorName is the name of a cursor that is currently open. For example,
Once the cursor has been closed, the active set is undefined. All cursors are automatically
closed at the end of the containing transaction. We illustrate some of these points in
Example E.3.
Produce a program that asks the user for a staff number and prints out the properties
managed by this member of staff.
The program is shown in Figure E.4. In this example, the query result table may contain
more than one row. Consequently, we must treat this as a multi-row query and use a
cursor to retrieve the data. We ask the user for a staff number and set up a cursor to select
the corresponding rows from the PropertyForRent table. After opening the cursor, we loop
over each row of the result table and print out the corresponding columns. When there are
no more rows to be processed, we close the cursor and terminate. If an error occurs at any
point, we generate a suitable error message and stop.
..
DS4_Web1.qxd 23/04/2004 18:39 Page 12
Figure E.4
Multi-row query.
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 13
The FOR UPDATE OF clause must list any columns in the table named in the
selectStatement that may require updating; furthermore, the listed columns must appear in
the SELECT list. The format of the cursor-based UPDATE statement is:
where cursorName is the name of an open, updatable cursor. The WHERE clause serves
only to specify the row to which the cursor currently points. The update affects only data
in that row. Each column name in the SET clause must have been identified for update in
the corresponding DECLARE CURSOR statement. For example, the statement:
updates the staff number, staffNo, of the current row of the table associated with the cursor
propertyCursor. The update does not advance the cursor, and so another FETCH must be
performed to move the cursor forward to the next row.
It is also possible to delete rows through an updatable cursor. The format of the cursor-
based DELETE statement is:
..
DS4_Web1.qxd 23/04/2004 18:39 Page 14
where cursorName is the name of an open, updatable cursor. Again, the statement works on
the current row, and a FETCH must be performed to advance the cursor to the next row.
For example, the statement:
EXEC SQL DELETE FROM PropertyForRent
WHERE CURRENT OF propertyCursor;
deletes the current row from the table associated with the cursor, propertyCursor. Note that
to delete rows, the FOR UPDATE OF clause of the DECLARE CURSOR statement need
not be specified. In Oracle, there is a restriction that CURRENT OF cannot be used on
an index-organized table.
Cursors
The ISO standard specifies the definition and processing of cursors slightly differently from
how we presented them above. The ISO DECLARE CURSOR statement is as follows:
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 15
..
DS4_Web1.qxd 23/04/2004 18:39 Page 16
between the two types of embedded SQL is that static SQL does not allow host vari-
ables to be used in place of table names or column names. For example, in static SQL we
cannot write:
EXEC SQL BEGIN DECLARE SECTION;
char TableName[20];
EXEC SQL END DECLARE SECTION;
EXEC SQL INSERT INTO :TableName
VALUES (‘CR76’, ‘PA14’, ‘05-May-2004’, ‘Not enough space’);
as static SQL is expecting the name of a database table in the INSERT statement and not
the name of a host variable. Even if this were allowed, there would be an additional prob-
lem associated with the declaration of cursors. Consider the following statement:
EXEC SQL DECLARE cursor1 CURSOR FOR
SELECT *
FROM :TableName;
The ‘*’ indicates that all columns from the table, TableName, are required in the result
table, but the number of columns will vary with the choice of table. Furthermore, the data
types of the columns will vary between tables as well. For example, in Figure 3.3 the
Branch and Staff tables have a different number of columns, and the Branch and Viewing
tables have the same number of columns but different underlying data types. If we do not
know the number of columns and we do not know their data types, we cannot use the
FETCH statement described in the previous section, which requires the number and the
data types of the host variables to match the corresponding types of the table columns.
In this section we describe the facilities provided by dynamic SQL to overcome these
problems and allow more general-purpose software to be developed.
This command allows the SQL statement stored in hostVariable, or in the literal,
stringLiteral, to be executed. For example, we could replace the static SQL statement:
EXEC SQL BEGIN DECLARE SECTION;
float increment;
EXEC SQL END DECLARE SECTION;
EXEC SQL UPDATE Staff SET salary = salary + :increment
WHERE staffNo = ‘SL21’;
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 17
Figure E.5
Comparison of
SQL processing
by DBMS:
(a) static SQL;
(b) dynamic SQL.
..
DS4_Web1.qxd 23/04/2004 18:39 Page 18
executed, the program need only specify the name of the statement to execute it. The
format of the PREPARE statement is:
These two statements used together not only improve the performance of executing
an SQL statement that is used more than once, but also provide additional functionality
through the provision of the USING hostVariable clause of the EXECUTE statement.
We discuss the USING DESCRIPTOR clause shortly. The USING hostVariable clause
allows parts of the prepared statement to be unspecified, replaced instead by placeholders
(sometimes called parameter markers). A placeholder is a dummy host variable that can
appear anywhere in the hostVariable/stringLiteral of the PREPARE statement where a
constant can appear. A placeholder does not need to be declared and can be given any
name. It signals to the DBMS that a value will be supplied later, in the EXECUTE state-
ment. The program can supply different parameter values each time the dynamic statement
is executed. For example, we could prepare and execute an UPDATE statement that has
the values for the SET and WHERE clause unspecified:
EXEC SQL BEGIN DECLARE SECTION;
char buffer[100];
float newSalary;
char staffNo[6];
EXEC SQL END DECLARE SECTION;
sprintf(buffer, “UPDATE Staff SET salary = :sal WHERE staffNo = :sn”);
EXEC SQL PREPARE stmt FROM :buffer;
do {
printf(“Enter staff number: ”);
scanf(“%s”, staffNo);
printf(“Enter new salary: ”);
scanf(“%f”, newSalary);
EXEC SQL EXECUTE stmt USING :newSalary, :staffNo;
printf(“Enter another (Y/N)? ”);
scanf(“%c”, more);
}
until (more ! = ‘Y’);
In this example, the dummy host variables sal and sn are placeholders. The DBMS parses,
validates, and optimizes the statement, then builds an application plan for the statement
once when the PREPARE statement is performed, as illustrated in Figure E.5. This plan
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 19
can then be used for every subsequent invocation of the EXECUTE statement. This is sim-
ilar to the way in which static embedded SQL works.
n DESCRIBE BIND VARIABLES (host variables are also known as bind variables) fills
in an SQLDA for any bind variables specified in the query.
n DESCRIBE SELECT LIST fills in an SQLDA for column data when we wish to
dynamically retrieve data and the number of columns to be retrieved or the types of the
columns are not known.
Some of the fields in the SQLDA (such as N, M, and Y) are initialized when space for
the structure is allocated by the Oracle function SQLSQLDAAlloc() (see below). Other fields
Figure E.6
Oracle SQL
Descriptor
Area (SQLDA).
..
DS4_Web1.qxd 23/04/2004 18:39 Page 20
(such as T, F, S, C, X, and Z) are assigned values when the appropriate DESCRIBE state-
ment is executed. The actual values of the columns being retrieved (fields V, L, and I) are
assigned values when the FETCH statement is executed. We now briefly describe these fields.
n The N field The maximum number of placeholders or SELECT list columns that
can be described. This field is set by the application program using the function
SQLSQLDAAlloc(). After the DESCRIBE statement, the N and F fields are set to the actual
number of elements.
n The F field The actual number of placeholders or SELECT list columns found by the
DESCRIBE statement.
n The V field A pointer to an array of addresses to data buffers that store the input host
variables or the SELECT list columns. SQLSQLDAAlloc() reserves a pointer location for
each host variable but does not allocate the full space, which is the responsibility of the
application program. For placeholders, the data buffers must be allocated and the array
set prior to the OPEN statement; for SELECT list columns, the data buffers must be
allocated and the array set prior to the first FETCH statement.
n The L field A pointer to an array of lengths of input host variables or SELECT list
columns. For placeholders, the lengths must be set prior to the OPEN statement; for
SELECT list columns, the DESCRIBE statement sets the maximum length for each column
value, which can be modified if required. For the NUMBER data type, the length con-
tains the scale and precision, which can be individually accessed using the Oracle func-
tion SQLNumberPrecV6(). If NUMBER is coerced to a ‘C’ char string, the length would be
set to the precision of the number plus 2 (one for the sign and one for the decimal point).
n The T field A pointer to an array of data type codes for input host variables or SELECT
list columns. Oracle recognizes two kinds of data types: internal and external. Internal
data types specify how Oracle stores column values in database tables; external data
types specify the formats used to store values in host variables. The external data types
include all the internal data types plus several data types that closely match ‘C’ con-
structs. For example, the STRING external data type refers to a ‘C’ null-terminated string.
The DESCRIBE statement for placeholders sets the array to zero. The appropriate
external data type codes must be set prior to the OPEN statement. Some of the Oracle
external codes are shown in Table E.2. The DESCRIBE statement for SELECT list
columns sets the codes to the Oracle internal data type codes. For display purposes, it
may be preferable to reset some of the internal codes prior to the FETCH statement.
Note, Oracle does any necessary conversion between internal and external data types
either at OPEN time (in the case of a DESCRIBE for placeholders) or at FETCH time
(in the case of a DESCRIBE for a SELECT list). For example:
n By default, NUMBER values are returned in their internal format. It would probably
be better to reset the code from 2 to 1 (VARCHAR2), 3 (INTEGER), 4 (FLOAT),
which corresponds to the float data type in ‘C’, or 5 (STRING).
n By default, DATE values are returned in their seven-byte internal format. To get the
date in character format (DD-MMM-YY), the data type code should be reset from 12
to 1 (VARCHAR2) or 5 (STRING), and the corresponding length field (L) from 7 to
9 or 10, respectively.
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 21
VARCHAR2 1 char[n]
NUMBER 2 char[n]
INTEGER 3 int
FLOAT 4 float
STRING 5 char[n+1]
CHAR 96 char[n]
Note, for SELECT list values, the high bit of the data type code is set to indicate the
NULL status of the value. This must be cleared prior to the OPEN or FETCH statement,
which can be achieved using the Oracle SQLColumnNullCheck() function.
n The I field A pointer to an array of addresses of buffers that store indicator variable
values. For placeholders, these values must be set prior to the OPEN statement; for
SELECT list columns, these values must be set prior to the FETCH statement.
n The S field A pointer to an array of addresses of data buffers to store placeholder
names or SELECT list names. The data buffers are allocated and their addresses stored
in S by the Oracle SQLSQLDAAlloc() function. The DESCRIBE statement stores the
names in these buffers.
n The M field A pointer to an array of maximum lengths of data buffers to store place-
holder names or SELECT list names. Set by the Oracle SQLSQLDAAlloc() function.
n The C field A pointer to an array of current lengths of placeholder names or SELECT
list names. Set by the DESCRIBE statement.
n The X field A pointer to an array of addresses of data buffers to store indicator vari-
able names. This field applies only to placeholders. The data buffers are allocated and
their addresses stored in X by the Oracle SQLSQLDAAlloc() function. The DESCRIBE
statement stores the names in these buffers.
n The Y field A pointer to an array of maximum lengths of data buffers to store
indicator variable names. Again, this field applies only to placeholders. Set by the
Oracle SQLSQLDAAlloc() function.
n The Z field A pointer to an array of current lengths of indicator variable names. As
with X and Y, this field applies only to placeholders. Set by the DESCRIBE statement.
..
DS4_Web1.qxd 23/04/2004 18:39 Page 22
With a non-SELECT statement, DESCRIBE sets the F field of an SQLDA to zero. The
format of the DESCRIBE statements are:
Figure E.7
SQLDA and data
buffers following
DESCRIBE/FETCH
statements.
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 23
The dynamic OPEN statement allows values for the placeholders to be substituted using
one or more hostVariables in a USING clause or passing the values using a descriptorName
(that is, an SQLDA) in a USING DESCRIPTOR clause. The main difference is with the
dynamic form of the FETCH statement, which uses descriptorName to receive the rows of
the query result table (or one or more hostVariables/indicatorVariables). If an SQLDA is
used, the application program must provide data areas to receive the retrieved data and
indicator variables as described above before the dynamic FETCH statement is called. If
no indicator variable is needed for a particular column, the appropriate elements of the I
field should be set to zero. When the application program closes a cursor, it may also wish
to deallocate the SQLDA used by the query and the data areas reserved for the results of
the query.
The basic steps then for a general dynamic SQL statement are as follows:
(1) Declare a host string in the DECLARE SECTION to hold the text of the query.
(2) Declare a select SQLDA and, if required, a bind SQLDA.
(3) Allocate storage space for the SQLDA(s).
(4) Set the maximum number of columns in the select SQLDA and, if the query can have
placeholders, the maximum number of placeholders in the bind SQLDA.
(5) Put the query text into the host string.
(6) PREPARE the query from the host string.
(7) DECLARE a cursor for the query.
(8) If the query can have placeholders:
(a) DESCRIBE the bind variables into the bind SQLDA.
(b) Reset the number of placeholders to the number actually found by the DESCRIBE.
(c) Get values and allocate storage space for the bind variables found by the
DESCRIBE.
(9) OPEN the cursor using the bind SQLDA, or if no bind SQLDA has been used,
using the select SQLDA.
..
DS4_Web1.qxd 23/04/2004 18:39 Page 24
Produce a program that takes an arbitrary SQL statement and executes it.
Figure E.8 provides the sample dynamic SQL code for the program demonstrating the con-
cepts we have discussed above. For simplicity, most error checking has been omitted.
Figure E.8
Sample dynamic /* Program to execute an arbitrary SQL statement */
SQL code. #include <stdio.h>
#include <stdlib.h>
#define MAX_ITEMS 40
#define MAX_VNAME_LEN 30
#define MAX_INAME_LEN 30
EXEC SQL INCLUDE sqlca;
EXEC SQL INCLUDE sqlda;
#include <sqlcpr.h>
SQLDA *selectPtr;
/* extern SQLDA *SQLSQLDAAlloc(); */
void sql_error();
/* Procedure to dynamically allocate the area for the SQLDA and initialize number of elements */
void initSqlda(numItems, maxVNameLen, maxINameLen)
short int numItems, maxVNameLen, maxINameLen;
{
int i;
/* If the SQLDA for the select descriptor is allocated, deallocate it first of all. */
if (selectPtr)
free((char *)selectPtr);
if (( selectPtr = SQLSQLDAAlloc (SQL_SINGLE_RCTX, numItems, maxVNameLen, 0) ) ==
(SQLDA *)0 ) {
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 25
Figure E.8
printf(“Error allocating memory for select descriptor\n”);
(cont’d )
exit(-1);
}
selectPtr->N = numItems;
/* Now allocate the dynamic memory for the indicator variables and the data */
for (i = 0; i < numItems; i++) {
selectPtr->I[i] = (short *)malloc(sizeof(short));
selectPtr->V[i] = (char *)malloc(sizeof(1));
}
}
/* Procedure to set up the dynamic part of SQLDA to receive data from FETCH */
/* based on information set up in the SQLDA by DESCRIBE */
void setupSqlda()
{
int i, nullOK, precision, scale;
switch (selectPtr->T[i]) {
/* CHAR data type - no change in length required */
case 1:
break;
/* NUMBER data type - get precision and scale */
case 2:
SQLNumberPrecV6(SQL_SINGLE_RCTX, &(selectPtr->L[i]), &precision, &scale);
if (precision == 0) precision = 40;
if (scale > 0)
selectPtr->L[i] = sizeof(float);
else
selectPtr->L[i] = sizeof(int);
break;
..
DS4_Web1.qxd 23/04/2004 18:39 Page 26
Figure E.8
/* LONG data type */
(cont’d )
case 8:
selectPtr->L[i] = 240;
break;
/* ROWID data type */
case 11:
selectPtr->L[i] = 18;
break;
/* DATE data type */
case 12:
selectPtr->L[i] = 9;
break;
/* RAW data type */
case 23:
break;
/* LONG RAW data type */
case 24:
selectPtr->L[i] = 240;
break;
}
/* Allocate space for each of the columns. Previous call to SQLSQLDAAlloc() reserves */
/* a pointer location for each column, but does not allocate the full space. */
if (selectPtr->T[i] != 2)
selectPtr->V[i] = (char *)realloc(selectPtr->V[i], selectPtr->L[i] + 1);
else
selectPtr->V[i] = (char *)realloc(selectPtr->V[i], selectPtr->L[i]);
/* Coerce all data types apart from NUMBER and LONG RAW to character */
if (selectPtr->T[i] != 24 && selectPtr->T[i] != 2)
selectPtr->T[i] = 1;
/* Coerce NUMBER data type to FLOAT or INT depending on scale */
if (selectPtr->T[i] == 2)
if (scale > 0)
selectPtr->T[i] = 4; /* FLOAT */
else
selectPtr->T[i] = 3; /* INT */
} /* next column */
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 27
Figure E.8
printf(“\n\n”);
(cont’d )
} /* end of setupSqlda */
/* Procedure to print out a row of data from the query result table */
void printRow()
{
int i;
/* By now, each field returned has been coerced to a character string, float, or int. */
for (i = 0; i < selectPtr->F; i++) {
if (*selectPtr->I[i] < 0)
if (selectPtr->T[i] == 4) /* FLOAT */
printf(“%-.*c ”, (int)selectPtr->L[i] + 3, " ");
else
printf(“%-.*c ”, (int)selectPtr->L[i], " ");
else
if (selectPtr->T[i] == 3) /* INT */
printf(“%*d ”, (int)selectPtr->L[i], *(int *)selectPtr->V[i]);
else if (selectPtr->T[i] == 4) /* FLOAT */
printf(“%*.2f ”, (int)selectPtr->L[i], *(float *)selectPtr->V[i]);
else /* Character */
printf(“%-*s ”, (int)selectPtr->L[i], selectPtr->V[i]);
} /* next column */
printf(“\n”);
} /* end of printRow */
main()
{
char string[10];
..
DS4_Web1.qxd 23/04/2004 18:39 Page 28
Figure E.8
EXEC SQL BEGIN DECLARE SECTION;
(cont’d )
char *username = “Manager/Manager”;
char *connectString = “DreamHome”;
char query[100]; /* query buffer */
EXEC SQL END DECLARE SECTION;
EXEC ORACLE OPTION (ORACA = YES);
oraca.oradbgf = 1;
oraca.oracchf = 1;
oraca.orastxtf = 3;
/* initialize the SQLDA */
initSqlda(MAX_ITEMS, MAX_VNAME_LEN, MAX_INAME_LEN);
/* Connect to database */
EXEC SQL CONNECT :username USING :connectString;
if (sqlca.sqlcode < 0) {
printf(“Cannot connect to database”);
gets(string);
exit(-1);
}
printf(“Connection successful\n”);
/* Get next statement */
while (getStatement(query)) {
/* Establish SQL error handling */
EXEC SQL WHENEVER SQLERROR GOTO error1;
EXEC SQL WHENEVER NOT FOUND GOTO closeCsr;
EXEC SQL SET TRANSACTION READ WRITE;
EXEC SQL AT :connectString DECLARE stmt STATEMENT;
/* Prepare and describe the query */
EXEC SQL PREPARE stmt FROM :query;
EXEC SQL DESCRIBE SELECT LIST FOR stmt INTO selectPtr;
/* Check if the statement is a non-select */
if (selectPtr->F == 0) {
/* Non-SELECT statement n */
EXEC SQL SET TRANSACTION READ WRITE;
EXEC SQL EXECUTE IMMEDIATE :query;
EXEC SQL COMMIT;
*/
}
else {
/* Check if the SQLDA is big enough, and if not, reinitialize and DESCRIBE statement again */
if (selectPtr->F < 0) {
initSqlda(abs(selectPtr->F), MAX_VNAME_LEN, MAX_INAME_LEN);
EXEC SQL DESCRIBE SELECT LIST FOR stmt INTO selectPtr;
}
/* SQLDA now big enough; setup data area and declare cursor for query */
setupSqlda();
EXEC SQL DECLARE selectCursor CURSOR FOR stmt;
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 29
Figure E.8
/* Open the cursor to start of selection */
(cont’d )
EXEC SQL OPEN selectCursor USING DESCRIPTOR selectPtr;
/* Loop to fetch each row of the result table */
for ( ; ; ) {
/* Fetch next row of the result table */
EXEC SQL FETCH selectCursor USING DESCRIPTOR selectPtr;
/* Display data */
printRow();
}
}
/* Close the cursor before completing */
closeCsr:
EXEC SQL CLOSE selectCursor;
}
goto finish;
/* Error conditions - print out error */
error1:
printf(“SQL error %d\n”, sqlca.sqlcode);
sql_error(“ORACLE error - \n”);
finish:
freeSQLDA();
EXEC SQL WHENEVER SQLERROR continue;
EXEC SQL COMMIT WORK RELEASE;
gets(string); gets(string);
}
void sql_error(msg)
char *msg;
{
char err_msg[128];
int buf_len, msg_len;
..
DS4_Web1.qxd 23/04/2004 18:39 Page 30
SQLDA
The SQL Descriptor Area in the ISO standard is treated very much like a variable of an
abstract data type in the object-oriented sense. The programmer has access only to the
SQLDA using a set of methods (or functions). An SQLDA is allocated and deallocated
using the statements:
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 31
..
DS4_Web1.qxd 23/04/2004 18:39 Page 32
for (;;) {
EXEC SQL FETCH staffCursor INTO DESCRIPTOR ‘outSQLDA’;
EXEC SQL GET DESCRIPTOR ‘outSQLDA’ VALUE 1 :staffNoData = DATA;
printf(“Staff No: %s\n”, staffNoData);
}
...
Note how much simpler this approach is than the alternative (non-standard) Oracle approach.
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 33
n data can be sent and received in a format that is convenient to the application;
n ODBC is designed in conjunction with the X/Open and ISO Call-Level Interface (CLI)
standards;
n there are ODBC database drivers available today for many of the most popular DBMSs.
In Section 29.7 we examine JDBC, the most prominent and mature approach for
accessing relational DBMSs from Java that is modeled after the ODBC specification.
..
DS4_Web1.qxd 23/04/2004 18:39 Page 34
Figure E.9
ODBC architecture:
(a) multiple drivers;
(b) single driver.
n DDL: ALTER TABLE, CREATE INDEX, DROP INDEX, CREATE VIEW, DROP
VIEW, GRANT, and REVOKE.
n DML: full SELECT.
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 35
Produce a program that prints out the properties managed by staff member SL41.
Figure E.10 provides sample ODBC code for the program. For simplicity, most error
checking has been omitted. This example illustrates the basic operations of a typical
ODBC-based application:
n Allocate an environment handle through the call to SQLAllocEnv(), which allocates
memory for the handle and initializes the ODBC Call-Level Interface for use by the
application. An environment handle references information about the global context
of the ODBC interface, such as the environment’s state and the handles of connections
currently allocated within the environment.
n Allocate a connection handle through the call to SQLAllocConnect(). A connection con-
sists of a driver and a data source. A connection handle identifies each connection and
identifies which driver to use and which data source to use with that driver. It also
references information such as the connection’s state and the valid statement handles on
the connection.
n Connect to the data source using SQLConnect(). This call loads a driver and establishes
a connection to the named data source.
n Allocate a statement handle using SQLAllocStmt(). A statement handle references state-
ment information such as network information, SQLSTATE values and error messages,
cursor name, number of result set columns, and status information for SQL statement
processing.
n On completion, all handles must be freed and the connection to the data source
terminated.
n In this particular application, the program builds an SQL SELECT statement and
executes it using the ODBC function SQLExecDirect(). The driver modifies the SQL
..
DS4_Web1.qxd 23/04/2004 18:39 Page 36
Figure E.10
Sample ODBC
application.
.. ..
DS4_Web1.qxd 23/04/2004 18:39 Page 37
Appendix Summary | 37
statement to use the form of SQL used by the data source before submitting it to the
data source. The application can include one or more placeholders if required, in which
case it would need to call the ODBC function SQLBindParameter() to bind each of the
markers to a program variable. Successive calls to SQLBindCol() assigns the storage and
data type for each column in the result set. Repeated calls to SQLFetch() then returns each
row of the result set.
This structure is appropriate for SQL statements that are executed once. If we intend to
execute an SQL statement more than once in the application program, it may be more
efficient to call the ODBC functions SQLPrepare() and SQLExecute(), as discussed in Sec-
tion E.2.1.
Appendix Summary
n SQL statements can be embedded in high-level programming languages. The embedded statements are
converted into function calls by a vendor-supplied precompiler. Host language variables can be used in
embedded SQL statements wherever a constant can appear. The simplest types of embedded SQL statements
are those that do not produce any query results and the format of the embedded statement is almost identical
to the equivalent interactive SQL statement.
n A SELECT statement can be embedded in a host language provided the result table consists of a single row.
Otherwise, cursors have to be used to retrieve the rows from the result table. A cursor acts as a pointer to a
particular row of the result table. The DECLARE CURSOR statement defines the query; the OPEN statement
executes the query, identifies all the rows that satisfy the query search condition, and positions the cursor
before the first row of this result table; the FETCH statement retrieves successive rows of the result table;
the CLOSE statement closes the cursor to end query processing. The positioned UPDATE and DELETE state-
ments can be used to update or delete the row currently selected by a cursor.
n Dynamic SQL is an extended form of embedded SQL that allows more general-purpose application programs
to be produced. Dynamic SQL is used when part or all of the SQL statement is unknown at compile-time, and
the part that is unknown is not a constant. The EXECUTE IMMEDIATE statement can be used to execute
SQL statements that do not involve multi-row queries. If the statement is going to be run more than once, the
PREPARE and EXECUTE statements can be used to improve performance. Placeholders can be used to pass
values to the EXECUTE/FETCH statements.
n The SQL Descriptor Area (SQLDA) is a data structure that can be used to pass or retrieve data from dynamic
SQL statements. The DESCRIBE statement returns a description of a dynamically prepared statement into an
SQLDA. If the F field of the SQLDA is zero, the statement is a non-SELECT statement. Dynamic cursors
are used to perform SELECTs that return an arbitrary number of rows.
n The Microsoft Open Database Connectivity (ODBC) technology provides a common interface for access-
ing heterogeneous SQL databases. ODBC is based on SQL as a standard for accessing data. This interface
(built on the ‘C’ language) provides a high degree of interoperability: a single application can access
different SQL DBMSs through a common set of code. This enables a developer to build and distribute
a client–server application without targeting a specific DBMS. Database drivers are then added to link the
application to the user’s choice of DBMS. ODBC has now emerged as a de facto industry standard.
..
DS4_Web1.qxd 23/04/2004 18:39 Page 38
Review Questions
E.1 Discuss the differences between interactive E.3 Describe what indicator variables are and give
SQL, static embedded SQL, and dynamic an example of their use.
embedded SQL. E.4 Describe what placeholders are and give an
E.2 Describe what host language variables are and example of their use.
give an example of their use. E.5 Describe the functions of the SQLCA and SQLDA.
Exercises
Answer the following questions using the relational schema from the Exercises at the end of Chapter 3:
E.6 For each of the following SQL statements, illustrate the contents of the SQLDA following a call to the
DESCRIBE SELECT LIST FOR and DESCRIBE BIND VARIABLES FOR statements:
(a) “SELECT * FROM Hotel”
(b) “SELECT hotelNo, hotelName FROM Hotel WHERE hotelNo = :hn”
(c) “SELECT MIN(price) FROM Room WHERE hotelNo = :hn AND type = :t”
E.7 Write a program that prompts the user for guest details and inserts the record into the guest table.
E.8 Write a program that prompts the user for booking details, checks that the specified hotel, guest, and room
exists, and inserts the record into the booking table.
E.9 Write a program that increases the price of every room by 5%.
E.10 Write a program that calculates the account for every guest checking out of the Grosvenor Hotel on a specified
day.
E.11 Write a program that allows the user to insert data into any user-specified table.
E.12 Investigate the embedded SQL functionality of any DBMS that you use. Discuss how it differs from the ISO
standard for embedded SQL.
..