|
   
4.
Executing SQL Statements
4.1
The SQL Language
4.2
Executing SQL Statements With ODBC
4.3
Parameters
4.4
Entering Large Data
4.5
Get Options of Statement Handle
4. Executing SQL Statements
This chapter
describes in detail how to use ODBC functions to execute the SQL statements
supported by DBMaker. It provides a brief introduction to the SQL query
language, and then illustrates how to:
allocate and free statement handles by using the functions SQLAllocStmt
and SQLFreeStmt.
execute an SQL statement directly by using the function SQLExecDirect,
and prepare an SQL statement for execution and execute the prepared
statement by using the functions SQLPrepare and SQLExecute.
return the number of rows affected by UPDATE, INSERT or DELETE statements
using the function SQLRowCount.
use parameters to pass a data value to an SQL command at execution time,
instead of preparation time.
return the number of parameters in an SQL statement by using the function
SQLNumParams, and return a description of a parameter marker
associated with a prepared SQL statement by using the function SQLDescribeParam.
bind a buffer to a parameter marker in an SQL statement by using the
function SQLBindParameter, and input large data items in smaller
pieces by using the functions SQLPutData and SQLParamData.
return the current setting of a statement option by using the function
SQLGetStmtOption, and cancel statement processing by using the
function SQLCancel.
Note:
You allocate statement handles differently using DBMaker 3.5 (ODBC 3.0)
from what is described in this chapter. Refer to Appendix E, What's
New in DBMaker 3.5 for more information.
The following
diagram shows the topics of Sections 4-3 ~ 4-6 and their relation to
the state transitions that occur when writing an application that uses
ODBC to access a database.

Figure
4-1: Topics in this chapter and their relation to state transitions
4.1 The SQL Language
Structured
Query Language (SQL) is the industry standard query language used
for defining, organizing, managing, and retrieving data stored in relational
databases. Unlike traditional procedural programming languages such
as C and Pascal, you do not need to explicitly define how to perform
a database operation. You can simply enter a request to the database
using the English-like SQL syntax, and the database will determine the
best method to process the request and return the results to you when
it is finished.
The functions
provided by SQL go beyond simple data retrieval, although that is still
one of its most important functions. SQL is actually divided into three
parts, known as Data Definition Language (DDL), Data Manipulation
Language (DML), and Data Control Language (DCL). Each of
these performs a specific role, and together you can use them to perform
all functions a DBMS provides, including:
Data definition - lets you define the structure and organization
of data and the relationships between data.
Data manipulation -allows you to retrieve existing data from the
database and update the database by adding new data, deleting old data,
and modifying previously stored data.
Data control -allows you to protect data against unauthorized access,
and define integrity constraints to protect data from corruption.
This section
provides a brief overview of SQL. For more detailed information, please
see the SQL Command and Function Reference.
The Role of SQL with ODBC
As shown
in Figure 4-2, when the ODBC driver
gets an SQL statement, it passes the SQL request to the database engine.
The database engine structures, stores and retrieves the data on disk
according to the SQL statement.

Figure
4-2: The role of SQL when using ODBC
Basic SQL Statements
SQL statements
can be divided into DDL, DML, and DCL statements. Only a few of the
basic SQL statements will be discussed in this chapter. For a more detailed
discussion of the entire SQL language and SQL syntax supported by DBMaker,
see the SQL Command and Function Reference.
Data Definition Language (DDL)
The schema
of a database is handled by a set of SQL statements called the SQL Data
Definition Language (DDL). DDL makes use of the CREATE, DROP or
ALTER commands to define, remove or modify the definition of a database
object. We will briefly explain the CREATE TABLE statement.
The CREATE TABLE Command
A database
contains many tables, and each table in a database stores information.
Tables are composed of rows (records) and columns (fields).
You can use the CREATE TABLE statement to create a new table in a database.
The basic syntax of the CREATE TABLE statement is:
CREATE TABLE table-name (column-name data-type, .... )
|
Data Types
The ANSI/ISO
SQL standard specifies a minimal set of data types that a DBMS should
support. Almost all commercial SQL products support these data types
or provide similar data types that have equivalent functionality. The
data types supported by DBMaker are listed in Table
4-1. Below is an example of how the CREATE TABLE command is
used:
CREATE TABLE account (
no serial, /* account number */
lname name, /* account last name */
fname name, /* account first name */
branch integer, /* belong to branch */
balance money, /* account balance */
altno char(12),
stamp image, /* account's stamp image */
photo image, /* account's photo image */
memo text /* account's memo */
);
|
Data Manipulation Language (DML)
Retrieving
or manipulating the data in a database is handled by a set of SQL statements
called the SQL Data Manipulation Language, or DML. The basic DML statements
are SELECT, INSERT, DELETE and UPDATE.
Retrieving Data from the Database (SELECT)
You can
use the SELECT statement to retrieve data from a database and return
a result set to the user. The basic syntax of the SELECT statement is:
SELECT item_list FROM table_list WHERE search_condition;
|
|
Data
Type
|
Description
|
|
CHAR(len)
|
Fixed-length
character string
|
|
VARCHAR(len)
|
Variable-length
character string
|
|
BINARY(len)
|
Binary
data
|
|
OID
|
Object
ID
|
|
FILE
|
BLOB
object (file)
|
|
LONG
VARCHAR
|
BLOB
object (text)
|
|
LONG
VARBINARY
|
BLOB
object (binary)
|
|
SERIAL
[(integer)]
|
Auto-increment
integer
|
|
SMALLINT
|
Small
integer number
|
|
INTEGER
[INT]
|
Integer
number
|
|
FLOAT
|
Low-precision
floating point number
|
|
DOUBLE
|
High-precision
floating point number
|
|
DECIMAL
[DEC] DECIMAL(precision,scale)
|
Decimal
numbers (use default precision and scale) Default precision is
17 and default scale is 6
|
|
DATE
|
Date
|
|
TIME
|
Time
|
|
TIMESTAMP
|
Timestamp
|
|
other
|
domain
|
Table
4-1: Data types in DBMaker
The basic
SELECT statement is made up of three components: SELECT, FROM and WHERE.
The functions of each of these components is listed below:
SELECT - specifies the columns or calculated columns to be retrieved
by the query.
FROM - specifies the tables that contain the items in the SELECT list.
WHERE - specifies the search condition that must be met to select a
row.
The WHERE
clause may contain multiple search conditions. The search conditions
that can be included in the WHERE clause are shown in the following
list. They can include:
Comparison operators (=, >, <, >=, <=, <>, !=)
Ranges (BETWEEN and NOT BETWEEN)
Lists (IN and NOT IN)
String matches (LIKE and NOT LIKE)
BLOB matches (MATCH and NOT MATCH)
Unknown values (IS NULL and IS NOT NULL)
Logical combinations (AND, OR)
Negations (NOT)
For example,
to perform a query to find all customers whose account balance is greater
than $10,000, you would use the following select statement:
SELECT lname, fname, balance FROM account WHERE balance > 10000
|
Modifying Data in the Database
The data
contained in a database can be modified by adding, deleting or updating
rows. For each of these operation, the DBMS will return how many rows
are affected.
Adding Data to the Database
The INSERT
statement is used to add a new row to a table. The basic syntax of the
INSERT statement is:
INSERT INTO table_name(column_names) VALUES value_list
|
The INSERT
statement is made up of two components: INSERT INTO and VALUES. The
function of these components is listed below:
INSERT INTO - specifies the table you want to insert a row into. It
can optionally contain a column list to specify that data should only
be inserted into those columns. Columns not in this list will be inserted
with NULL values.
VALUES - specifies the data value you want to insert. You can insert
values by using constants or parameters.
As stated
above, the value list may contain constants or parameters.
A constant is any numeric, text or date value that can be expressed
in text form, such as `John', `Monday', 123, 54.823, etc. An example
of the INSERT command using constants is shown below. This example adds
a new account for John Smith to the database:
INSERT INTO account (lname, fname, branch, balance)
VALUES ('john', 'smith', 101, 10000)
|
Parameter
data is represented by a question mark (?) in the value list, and values
can be inserted later. Parameters can be used when the data values are
unknown at preparation time, or when you want to save preparation time.
An example of the INSERT command using parameters is shown below. This
example is used to insert rows into the database, but the values are
not currently known. The actual values to be inserted can be bound before
execution:
INSERT INTO account (lname, fname, branch) VALUES (?,?,?)
|
Note:
To learn how to prepare statements and bind parameters to ODBC functions,
please refer to Section 4 of this chapter.
Deleting Data from the Database
The DELETE
statement deletes one or more rows from a table. The basic syntax of
the DELETE statement is:
DELETE FROM table_name WHERE search_condition
|
The DELETE
statement is made up of two components: DELETE FROM and WHERE. The function
of these components is listed below:
DELETE FROM - specifies the table you want to delete rows from.
WHERE - specifies the search conditions that must be met to delete a
row.
The WHERE
clause may contain multiple search conditions. For a list of search
conditions that can be included in the WHERE clause, refer to "Retrieving
Data from the Database".
The following
example will delete the account for John Smith from the database:
DELETE FROM account WHERE fname = 'john' AND lname = 'smith'
|
Updating the Data in the Database
The UPDATE
statement changes data in existing rows in a table. The basic syntax
of the UPDATE statement is:
UPDATE table_name SET column_names expression WHERE search_condition
|
The UPDATE
statement is made up of three components: UPDATE, SET and WHERE. The
function of these components is listed below:
UPDATE - specifies the table you want to update rows in.
SET - specifies the columns you want to change and an expression that
defines the changes to be made for each column.
WHERE - specifies the search conditions that must be met to update a
row.
The WHERE
clause may contain multiple search conditions. For a list of search
conditions that can be included in the WHERE clause, see the SQL Command
and Function Reference.
The following
example will add 6% interest to all accounts with a balance greater
than $1000.
UPDATE account SET balance = balance * 1.06 WHERE balance > 1000
|
Note:
To find out how many rows have been inserted, deleted or updated,refer
to the information about the SQLRowCount function.
4.2 Executing SQL Statements With ODBC
This section
will serve as a guide for writing a simple ODBC program. As the previous
section mentioned, every SQL statement can be executed via ODBC in a
program. For example, suppose we have connected to a database successfully
and want to use the following SQL statements to construct a simple table:
dmSQL> CREATE TABLE account (lname CHAR(15), fname CHAR(10),
2> branch INTEGER);
dmSQL> INSERT INTO account VALUES('Mulder', 'Fox', 11240);
|
Then the
corresponding ODBC statements would be:
retcode = SQLAllocStmt(hdbc, &htmt);
retcode = SQLExecDirect(hstmt,"CREATE TABLE account (lname CHAR(15),
fname CHAR(10), branch integer)", SQL_NTS);
retcode = SQLExecDirect(hstmt,"INSERT INTO account VALUES('Mulder',
'Fox', 11240)", SQL_NTS);
|
SQLAllocStmt
All ODBC
functions that handle SQL statements need a statement handle. A statement
handle is a pointer to a location where all information about an SQL
statement resides. So, before executing an SQL statement via SQLExecDirect,
we need to use SQLAllocStmt to allocate a statement handle.
The prototype
for SQLAllocStmt is:
RETCODE SQLAllocStmt(
HDBC hdbc,
HSTMT FAR *phstmt
);
|
If the
return code is SQL_SUCCESS, you have successfully allocated a valid
statement handle from the driver. You can then proceed to the next ODBC
function, SQLExecDirect.
SQLExecDirect
SQLExecDirect
is used to execute an SQL statement directly. Many ODBC books call this
direct execution, as opposed to another method of execution known
as prepared execution. Prepared execution will be explained later.
The prototype
for SQLExecDirect is:
RETCODE SQLExecDirect(
HSTMT hstmt,
UCHAR FAR *szSqlStr,
SWORD cbSqlStr
);
|
The first
argument, hstmt, is a valid statement handle, the second argument
is the SQL statement string to be executed, and the last argument is
the string length of the SQL statement or SQL_NTS if szSqlStr
points to a null terminated string.
The execution
of SQLExecDirect is performed in two parts. First it compiles
(prepares) the SQL statement by checking referenced object names and
grammar, choosing an access plan and converting the statement into an
internal executable form. Then in the second phase, it executes the
executable form to actually access the database.
If the
SQL statement is a query like SELECT * FROM account, then a result set
of selected rows is produced and you need to use SQLFetch to
get the returned data row by row from the result set (See Chapter
5). If the SQL statement is an INSERT, DELETE, or UPDATE statement,
you can use SQLRowCount to see how many rows were affected.
SQLRowCount
This function
returns the number of rows affected by INSERT, DELETE or UPDATE statements
executed in the statement handle.
The prototype
for SQLRowCount is:
RETCODE SQLRowCount(
HSTMT hstmt,
SDWORD FAR *pcrow
);
|
If hstmt
is associated with an UPDATE statement, the pcrow will return the number
of updated rows after executing the UPDATE statement. For example:
SDWORD count;
SDWORD retcode;
retcode = SQLAllocStmt(hdbc, &hstmt);
retcode = SQLExecDirect(hstmt,"CREATE TABLE account (lname name,
fname name, branch integer, balance money)",
SQL_NTS);
/* insert three records into account table */
retcode = SQLExecDirect(hstmt, "INSERT INTO account VALUES(`Mulder',
`Fox', 11240, 10000.00)", SQL_NTS);
retcode = SQLExecDirect(hstmt, "INSERT INTO account VALUES(`Scully',
`Dana', 11330, 20000.00)", SQL_NTS);
retcode = SQLExecDirect(hstmt, "INSERT INTO account VALUES(`Skinner',
`Walter', 11240, 30000.00)", SQL_NTS);
/* if branch is 11240, add 1000 to balance */
retcode = SQLExecDirect(hstmt, "UPDATE account SET balance = balance
+ 1000.00 WHERE branch = 11240", SQL_NTS);
/* get the number of updated rows from count in the example. */
/* Count will be two. */
retcode = SQLRowCount(hstmt, &count);
|
Example
4-1: Using SQLRowCount
If hstmt
is not associated with an INSERT, DELETE or UPDATE statement, the row
count will be -1 for DBMaker.
SQLFreeStmt
You can
use SQLFreeStmt to close or drop a statement handle.
The prototype
for SQLFreeStmt is:
RETCODE SQLFreeStmt (
HSTMT hstmt,
UWORD fOption
);
|
The first
argument (hstmt) is a valid statement handle and the second is
an option that specifies how the statement handle is freed. Two commonly
used options for freeing the statement handle are SQL_CLOSE and SQL_DROP.
If the
statement is not a select statement, a statement handle can be reused.
For example:
SQLExecDirect(hstmt1, "INSERT ...");
SQLExecDirect(hstmt1, "CREATE ...");
SQLExecDirect(hstmt1, "INSERT ...");
|
If the
statement is a select statement, you need to close the statement handle
before using it again. For example:
retcode = SQLExecDirect(hstmt,"SELECT * FROM account", SQL_NTS);
...
retcode = SQLFreeStmt(htmt,SQL_CLOSE);
retcode = SQLExecDirect(hstmt,"INSERT INTO account VALUES('Mulder',
'Fox', 11240)", SQL_NTS);
|
By using
SQL_CLOSE to close and reuse a statement handle, you don't have to allocate
a new statement handle every time you want to execute a statement after
a selection. However, if in doubt, you can use the SQL_DROP option to
drop the statement handle and allocate a new one. Drop will release
all resources associated with the statement handle. After a drop, you
can NOT reuse the handle.
Prepared Execution: SQLPrepare and SQLExecute
As described
in the section on SQLExecDirect, direct execution is performed
in two parts: preparation and execution. If you want to execute a statement
repeatedly, you can use prepared execution to improve performance.
Prepared
execution divides the execution life of a statement into two parts using
ODBC function calls: preparation (SQLPrepare) and execution (SQLExecute).
The idea is to prepare the statement into executable form only once
and then execute it many times. The prototypes of these two functions
are:
RETCODE SQLPrepare(
HSTMT hstmt,
UCHAR *szSqlStr,
UDWORD cbSqlStr
);
|
and
RETCODE SQLExecute(HSTMT hstmt);
|
In SQLPrepare,
hstmt is a valid statement handle, szSqlStr is the SQL
statement string to be executed and cbSqlStr is the length of
the string szSqlStr or SQL_NTS if the string is null-terminated.
Prepared execution is most useful when combined with parameters, which
is the topic of the next section.
4.3 Parameters
In this
section we will introduce parameters, which are used in SQL statements
to pass a data value to an SQL command at execution time in an ODBC
application program. The concept is similar to the host variables used
in embedded SQL.
What is a Parameter?
A parameter
is used in a SQL statement when:
values of the parameters are unknown at preparation time
applications need to execute the same SQL statment several times with
different parameter values
applications need to convert the parameter values between different
data types
For example,
an application wants to insert five rows into a table named account.
INSERT INTO account (lname, fname, branch) VALUES (?,?,?)
|
In this
statement, ? is the parameter marker. By using parameters, the application
only needs to prepare this statement once, and then execute the prepared
statement five times with different parameter values to insert five
rows into the account table.
Parameter Functions
There are
three ODBC functions for dealing with parameters: SQLBindParameter,
SQLDescribeParam, SQLNumParams.
SQLBindParameter is used to bind a storage location to a parameter
marker and specify the data type, precision and scale of the storage
location.
SQLNumParams is used by an application to get the number of parameters
in an SQL statement. It is especially useful for an interactive dynamic
SQL application.
SQLDescribeParam is used to describe the attributes of a specified
parameter, like length or precision. It is also used for dynamic SQL
applications.
SQLBindParameter
The prototype
for SQLBindParameter is:
RETCODE SQLBindParameter(
HSTMT hstmt,
UWORD ipar,
SWORD fParamType,
SWORD fCType,
SWORD fSqlType,
UDWORD cbColDef,
SWORD ibScale,
PTR rgbValue,
SDWORD cbValueMax,
SDWORD FAR *pcbValue
);
|
An application
needs to pass the following information to SQLBindParameter:
hstmt - statement handle
ipar - ith parameter
fParamType - parameter type, input/output
fCType - parameter host language type
fSqlType - SQL column type
cbColDef - precision of the column
ibScale - scale of the column
rgbValue - address of the storage
cbValueMax - not used in DBMaker
pcbValue - length of the parameter in rgbValue
The following
example is used to illustrate how several rows of data with different
values can be inserted into a database by using SQLBindParameter
to bind the row values to parameters before SQLExecute is called.
Note that when SQLExecute is called for the third time, a NULL
will be inserted into the branch column by setting pcbValue
in SQLBindParameter to SQL_NULL_DATA.
#define LENGTH 18
UCHAR lname[LENGTH], fname[LENGTH];
UDWORD branch_no;
SDWORD retcode, cblname, cbfname, cbbranch;
retcode = SQLPrepare(hstmt,"INSERT INTO account (lname,fname,branch)
VALUES (?,?,?)",SQL_NTS);
err_exit(hstmt, retcode); /* exit if error */
cblname = SQL_NTS; /* null terminated string */
cbfname = 0;
cgbranch = 0;
retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR, LENGTH, 0, lname, 0, &cblname);
retcode = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR, LENGTH, 0, fname, 0, &cbfname);
retcode = SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_LONG,
SQL_INTEGER, 0, 0, branch_no, 0, &cbbranch);
strcpy(lname, "Mulder");
cblname = strlen(lname);
strcpy(fname, "Fox");
cbfname = strlen(fname);
branch_no = 11240;
retcode = SQLExecute(hstmt);
strcpy(lname, "Scully");
cblname = strlen(lname);
strcpy(fname, "Dana");
cbfname = strlen(fname);
branch_no = 11251;
retcode = SQLExecute(hstmt);
/* insert a NULL data to branch column for the third customer whose */
/* branch number is unknown */
strcpy(lname, "Angus");
cblname = strlen(lname);
strcpy(fname, "MacGyver");
cbfname = strlen(fname);
cbbranch = SQL_NULL_DATA; /* indicate NULL for branch column */
retcode = SQLExecute(hstmt);
|
Example
4-2: Using SQLBindParameter
To set
a parameter value, an application should perform the following steps:
Call SQLBindParameter to bind a storage location to a parameter
marker.
Put the parameters value in the storage location.
The first
step can be done before or after calling SQLPrepare, but should
be done before calling SQLExecute. The second step should be
done before SQLExecute since the driver needs the parameter values
to execute the SQL statement.
There are
three parameter types that can be used for fParamType in SQLBindParameter:
SQL_PARAM_INPUT
SQL_PARAM_INPUT_OUTPUT
SQL_PARAM_OUTPUT
Note:
Currently DBMaker only supporst SQL_PARAM_INPUT and SQL_PARAM_INPUT_OUTPUT.
Both are used for input.
The parameter
is stored in the storage location rgbValue. When putting the
parameter values into rgbValue, you should use the C data types
specified in the fCType argument of SQLBindParameter.
The pcbValue
argument in SQLBindParameter is a pointer to a buffer that contains
the parameter length, but it can also be used for several different
purposes. Some of the possible values stored in pcbValue before
calling SQLExecute or SQLExecDirect are:
the length of the parameter, only useful for character or binary C data.
SQL_NTS
to indicate that the parameter value is a null-terminated string.
SQL_NULL_DATA
to indicate that the parameter value is NULL, as shown in the previous
code example.
SQL_DEFAULT_PARAM
to indicate the default value of the column will be used
SQL_DATA_AT_EXEC
or SQL_LEN_DATA_AT_EXEC to indicate the data for the parameter will
be sent with SQLPutData. This will be covered in detail in Section
4.4.
SQLNumParameter
The prototype
for SQLNumParams is:
RETCODE SQLNumParams(
HSTMT hstmt,
SWORD FAR *pcpar
);
|
When this
function is called, the driver will put the number of parameters in
the SQL statement in the buffer pcpar. This number will be zero
if the SQL statement contains no parameters. Note that this function
can only be called after the SQL statement is prepared (i.e. SQLPrepare
has been called).
SQLDescribeParam
The prototype
for SQLDescribeParam is:
RETCODE SQLDescribeParam(
HSTMT hstmt,
UWORD ipar,
SWORD FAR *pfSqlType,
UDWORD FAR *pcbColDef,
SWORD FAR *pibScale,
SWORD FAR *pfNullable
);
|
If an application
has all the information necessary for SQLBindParameter, then
it can call SQLBindParameter directly as shown in Example
4-2. In other cases, applications may lack detailed information
about the parameters before calling SQLBindParameter to set them.
In this situation, SQLDescribeParam can be used to get the information.
For example, in dynamic SQL applications such as a graphical query tool,
the SQL statements to be used are undetermined until run time. This
kind of application needs to obtain detailed information about parameters
at tun time so that they can bind the parameters.
SQLDescribeParam
returns the description of the specified parameter marker associated
with a perpared SQL statement. Figure 4-3
shows the general calling flow of SQLDescribeParam, SQLNumParams
and SQLBindParameter.

Figure
4-3: Program flow when using parameters.
The following
example illustrates the use of SQLDescribeParam, SQLNumParams
and SQLBindParameter in a dynamic SQL application. Note that
the parameter marker number is ordered sequentially from left to right,
starting at 1.
#define BUFFER_LEN 256 /* length of the SQL string buffer */
#define MAX_PARAMS 32 /* maximum number of parameters */
UCHAR str[BUFFER_LEN];
SDWORD retcode;
SWORD i, nparam;
SWORD partype[MAX_PARAMS], parscale[MAX_PARAMS], parnull[MAX_PARAMS];
SWORD parCtype[MAX_PARAMS];
UDWORD parlen[MAX_PARAMS], outlen[MAX_PARAMS];
char *parbuf[MAX_PARAMS];
BEGIN: /* begin label */
getSQLString(&str); /* get SQL statement string */
retcode = SQLPrepare(hstmt,str,SQL_NTS); /* prepare the SQL string */
err_exit(hstmt, retcode); /* exit if error */
retcode = SQLNumParams(cmdp,&nparam); /* get number of parameters */
err_exit(hstmt, retcode); /* exit if error */
if (nparam > 0) /* string has parameters? */
{
printf("There are %d parameters \n",nparam);
for (i = 0; i < nparam; i++) /* describe/set parameters */
{
retcode = SQLDescribeParam(cmdp, i+1, &partype[i], &parlen[i],
&parscale[i], &parnull[i]);
err_exit(hstmt, retcode); /* exit if error */
/* allocate storage location for the parameter according to */
/* the parameter type, length, and scale. Reuse the storage */
/* if possible */
allocParamStorage(partype[i], parlen[i], parscale[i],
&parbuf[i]);
/*get C type corresponding to SQL type */
getSQLCtype(partype, &parCtype);
/* bind the parameter to storage location */
retcode = SQLBindParameter(cmdp, i+1, parCtype[i], partype[i],
parlen[i], parscale[i], parbuf[i],
BUFFER_LEN, &outlen[i]);
err_exit(hstmt, retcode); /* exit if error */
}
for (i = 0; i < nparam; i++)
{
/* get parameter values and store them in bound storage */
getParamValue(nparam, parCtype[i], partype[i], parlen[i],
parscale[i], &parnull[i], parbuf[i]);
}
} /* end of if statement */
retcode = SQLExecute(hstmt); /* execute prepared stmt */
err_exit(hstmt, retcode);
if (user_Quit()) /* user wants to quit */
return;
else
goto BEGIN; /* go to BEGIN */
|
Example
4-3: Using parameters in a dynamic SQL application
When to Use Parameters
Here we
use some examples to explain when applications need to use parameters.
As described in previous sections, an application might use parameters
when:
values of the parameters are unknown at preparation time (like the dynamic
SQL code in Example 4-3.).
applications need to execute the same SQL statement several times with
different parameter values (like Example 4-2
which used the same parameters to insert three rows into a database).
applications need to convert the parameter values between different
data types. For example, an application may need to use character strings
to get all input values for convenience reasons, then insert these values
into tables in the database. In this case, it can use parameter markers
to accept all values, then convert these values to their corresponding
column types so the driver can insert them into the database correctly.
Notice
that when an SQL statement to be executed contains parameters, the parameter
values should be set before each call of SQLExecute like Example
4-2 and Example 4-3.
Using Parameters in SQLExecDirect
As stated
before, when an application wants to execute an SQL statement more than
once, it may call SQLPrepare first, then call SQLExecute
several times to save the effort of preparing the same SQL statement
for each execution and improve performance.
Parameters
can also be used in an SQL statement that is executed only once by using
SQLExecDirect. However, you must bind the parameters and set
the parameter values before calling SQLExecDirect anyway, so
you lose all of the advantages you get when using parameters with SQLPrepare
and SQLExecute.
In general,
there is no need to use parameters with SQLExecDirect unless
the data you want to input is a special case, such as a BLOB. In the
case of a BLOB, you must use the SQL_DATA_AT_EXEC or the SQL_LEN_DATA_AT_EXEC
options to bind the parameter at execution time, but the data values
are not set until after the statement is executed. (Also refer to Section
4.4 for more information on entering
large data items.)
#define LENGTH 18
UCHAR lname[LENGTH], fname[LENGTH];
UDWORD branch_no;
SDWORD retcode, cblname, cbfname, cbbranch;
retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR, LENGTH, 0, lname, 0, &cblname);
retcode = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR, LENGTH, 0, fname, 0, &cbfname);
retcode = SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_LONG,
SQL_INTEGER, 0, 0, branch_no, 0, &cbbranch);
strcpy(lname, "Bill");
cblname = strlen(lname);
strcpy(fname, "Skinner");
cblname = strlen(fname);
branch_no = 11243;
retcode = SQLExecDirect(hstmt, "INSERT INTO account (lname, fname,
branch) VALUES (?,?,?)", SQL_NTS);
err_exit(hstmt, retcode);
|
Example
4-4: Using parameters with SQLExecDirect
Clearing Bound Parameters
After a
storage location is bound by calling SQLBindParameter, it can
be reused again and again, like Example 4-4.
In the example, three storage locations are bound to the three parameter
markers in the INSERT statement, and remain bound until they are explicitly
unbound.
The storage
locations are unbound when the application calls SQLFreeStmt
with the SQL_RESET_PARAMS option or the SQL_DROP option. Notice that
the three storage locations belong to the same statement handle, and
when SQLFreeStmt is called, all the storage locations bound in
the statement handle will be unbound. If the application uses the SQL_RESET_PARAMS
option, it can reset the statement handle and bind a different storage
location.
#define LENGTH 18
UCHAR lname [length], fname[length];
UDWORD branch_no;
SDWORD retcode, cblname, cbfname, cbbranch;
retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR, LENGTH, 0, lname, 0, &cblname);
retcode = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR, LENGTH, 0, fname, 0, &cbfname);
retcode = SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_LONG,
SQL_INTEGER, 0, 0, branch_no, 0, &cbbranch);
... (use the three parameters to execute some SQL commands)
/* reset the parameters */
retcode = SQLFreeStmt(hstmt, SQL_RESET_PARAMS);
|
Example
4-5: Clearing bound parameters
If the
application uses the SQL_DROP option in SQLFreeStmt, then the
statement handle is released and becomes invalid. This means the applications
cannot use that handle anymore, or it may cause some unpredictable errors.
4.4 Entering Large Data
DBMaker
provides two methods to input BLOB data into a database. One method
uses a small, fixed size buffer to read a portion of the BLOB data and
enter it into the database. By repeating this process several times,
the entire BLOB can be input without having to use a single large buffer.
The second method is even more convenient. DBMaker provides a File Object
data type to allow users to store their BLOB data in an external file.
How to Enter Large Data
When you
need to enter large chunks of data into a long varchar or long varbinary
column, you can use the SQLPutData and SQLParamData functions
to enter the data in smaller pieces. This way you don't need a large
buffer to store the whole chunk of data as you would if you entered
it all at once.
The prototype
for SQLParamData is:
RETCODE SQLParamData(
HSTMT hstmt,
PTR FAR *prgbValue
);
|
The prototype
for SQLPutData is:
RETCODE SQLPutData(
HSTMT hstmt,
PTR rgbValue,
SDWORD cbValue
);
|
SQLParamData
is used to check whether there are any parameters that require data,
and the data is entered using SQLPutData. This process continues
until data has been entered for all of the parameters in the SQL statement.
The process flow is shown in Figure 4-4.
The detailed
steps for using SQLParamData and SQLPutData to enter large
data objects into a database are:
Bind the parameters - Set the pcbValue argument in the SQLBindParameter
function to either SQL_DATA_AT_EXEC or SQL_LEN_DATA_AT_EXEC. This lets
the ODBC driver know that you will provide values for this parameter
at execution time with SQLPutData.
Execute the SQL command - Execute the SQL statement with SQLExecDirect
or SQLExecute. When there are parameters that are expecting data
at execution time, the call will return SQL_NEED_DATA.
Find the first parameter expecting data at execution time - Call SQLParamData
to indicate that the first parameter expecting data at execution time
should start to receive data.

Figure
4-4: Process flow for entering large data.
Call SQLPutData - Prepare your next piece of data in the buffer
and call SQLPutData to send it to the database for the parameter
currently waiting for data. Repeat this step until all of the data for
this parameter is sent.
Call SQLParamData - If the return code is SQL_NEED_DATA, the
next parameter that is expecting data at execution time is ready to
receive data, and you should go back to Step 4. If the return code is
SQL_SUCCESS or SQL_SUCCESS_WITH_INFO, then all data for all parameters
expecting data at execution time has been sent and the SQL statement
has completed its execution.
In the
following example, we will insert a record into the account table.
InitUserData() opens a customers data file which includes the
customers photograph, signature and a memo field
to be entered into the account table. GetUserData() retrieves
the next MAX_DATA_SIZE bytes of data from the users data file into the
data buffer InputData until all data has been read. SQLPutData
sends data from the buffer to the database.
#define MAX_DATA_SIZE 2048
SDWORD cbPhoto, cbStamp, cbMemo;
SDWORD DataLen;
PTR pParm, DataFile;
UCHAR InputData[MAX_DATA_SIZE];
SDWORD retcode;
retcode = SQLPrepare(hstmt, (UCHAR *)"INSERT INTO account VALUES(`Mark',
,'Greene',2, 40000.00, 'xxxx', ?, ?, ?)",SQL_NTS);
if (retcode == SQL_SUCCESS)
{
/* Bind the parameters. Set cbPhoto, cbStamp and cbMemo by */
/* SQL_DATA_AT_EXEC to let ODBC know that we will be providing */
/* values of these parameters at statement executing time */
cbPhoto = cbStamp = cbMemo = SQL_DATA_AT_EXEC;
retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_BINARY,
SQL_LONGVARBIANRY, 0, 0, NULL, 0,
&cbPhoto);
retcode = SQLBindParameter(hstmt, 2, SQL_PARAM_INPUT, SQL_C_BINARY,
SQL_LONGVARBIANRY, 0, 0, NULL, 0,
&cbStamp);
retcode = SQLBindParameter(hstmt, 3, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_LONGVARCHAR, 0, 0, NULL, 0,
&cbMemo);
retcode = SQLExecute(hstmt);
if (retcode == SQL_NEED_DATA) /* any large data ? */
{
Parm = 0;
while ((retcode = SQLParamData(hstmt, &pAddr)) == SQL_NEED_DATA)
{ /* need to put large data for this column */
Parm++; /* in loop to get/put data in blob */
InitUserData(Parm, DataFile);
while (GetUserData(Parm, DataFile, InputData, &DataLen))
retcode = SQLPutData(hstmt, InputData, DataLen);
}
if (retcode == SQL_SUCCESS || retcode == SQL_SUCCESS_WITH_INFO)
printf("insert a record of account table success !! \n");
}
}
|
Example
4-6: Inserting data with SQLParamData and SQLPutData
Cancelling the Execution of SQLPutData
If an error
occurs while entering data into the database or we decide not to continue
entering data, we can cancel the process by using the SQLCancel
function.
The prototype
for SQLCancel is:
RETCODE SQLCancel(HSTMT hstmt);
|
You can
call SQLCancel and the whole statement will be aborted. After
canceling the current execution of a statement, you can call SQLExecute
or SQLExecDirect again.
Placing Large Data in a File Object
A File
Object (FO) is a powerful large object data type supported by DBMaker.
A FO is similar to LONG VARCHAR or LONG VARBINARY data, but it is stored
as an external file on your host file system. A FO column is created
by defining the column with the FILE data type. In the following example,
the photograph column is a FO column.
create table student (name char(20), photograph file)
|
Since a
FO is treated as BLOB data, you can use all methods of BLOB insertion
to insert data into FO columns. By setting fSQLType to SQL_LONGVARCHAR
or SQL_LONGVARBINARY, DBMaker will create a new file for each FO column
and copy data from the input buffer (when fCType is SQL_C_CHAR)
or from a file on the client side (when fCType is SQL_C_FILE).
Instead
of creating another file, you may want to link a FO column to an existing
file on the server side, such as a file on a CD-ROM. When you want to
link a server side file, set fCType to SQL_C_CHAR and fSqlType
to SQL_FILE. When using this method, you need to copy the file name
into the buffer before calling SQLExecute.
|
fSqlType
|
FO
Column
|
fCType
|
Data
Source
|
|
SQL_LONGVARCHAR
or SQL_LONGVARBINARY
(same
as BLOB)
|
Create
a new file on the server side.
|
SQL_C_FILE
|
Copy
data from a file on the client side.
|
|
|
|
SQL_C_CHAR
SQL_C_BINARY
|
Copy
data from the input buffer.
|
|
SQL_FILE
|
Link
to an existing file on the server side.
|
SQL_CHAR
|
Get
file name from the input buffer.
|
In the
following example a new record is inserted for a customer named Mary,
along with her photo which is stored as a File Object. The file already
exists on the server side, so we link this file into the database.
UCHAR pPhotoFlName[80];
SDWORD retcode;
retcode = SQLPrepare(hstmt, "INSERT INTO student VALUES('Mary', ?)",
SQL_NTS);
retcode = SQLBindParameter(hstmt, 1, SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_FILE, 80, 0, pPhotoFlName, 0, SQL_NTS);
strcpy(pPhotoFlName, "/disk1/sys/fo/photo"); /* pass the file name */
retcode = SQLExecute(hstmt);
|
Example
4-7: Placing large data in a File Object
Since a
FO is stored as an external file, it has the advantage that applications
used to edit the file can still work on the file directly.
4.5 Get Options of Statement Handle
The current
setting of a statement option in a statement handle can be obtained
by using the SQLGetStmtOption function.
The prototype
for SQLGetStmtOption is:
RETCODE SQLGetStmtOption(
HSTMT hstmt,
UWORD fOption,
PTR pvParam
);
|
where hstmt
is a valid statement handle, fOption is the option to be retrieved
and pvParam is the value associated with fOption. Depending
on the value of fOption, a 32-bit integer value or a pointer
to a null-terminated character string will be returned in pvParam.
In DBMaker,
this function is used for some special purposes. There are several extended
statement options in DBMaker:
SQL_GET_INCREMENT_VALUE
- used to get the value of a SERIAL column
SQL_GET_CURRENT_OID
- used to get the OID of the most recently inserted/fetched tuple
SQL_GET_BACKUP_ID
- used to get the backup id
SQL_DUMP_PLAN
- used to get/set dump plan options
SQL_PLAN
- used to get the plan length
SQL_PLAN_LEN
- used to get the plan length
In the
following example, the table account contains a SERIAL data type
column. The value of a SERIAL column is incremented automatically, and
users don't need to explicitly specify it. However, after inserting
a record, users may want to know the value of the SERIAL column just
inserted. This value can be obtained by calling SQLGetStmtOption
with fOption SQL_GET_INCREMENT_VALUE.
For example:
/* insert a record into table account where the value of each field */
/* is it's default value */
SDWORD val;
SDWORD retcode;
retcode = SQLExecDirect(hstmt, "INSERT INTO account VALUES()", SQL_NTS);
/* get the serial number that was just inserted */
retcode = SQLGetStmtOption(hstmt, SQL_GET_INCREMENT_VALUE, &val);
|
Example
4-8: Getting the value of a SERIAL column using SQLGetStmtOption
In this
example, the variable val will be the value of the SERIAL column
which is just inserted into the table account in the previous INSERT
statement. For the definition and the use of the SERIAL data type, please
refer to the SQL Command and Function Reference.
Another
special use of SQLGetStmtOption is to get the OID of the most
recently inserted or fetched tuple. Continuing with the previous example,
you can submit SQLGetStmtOption with fOption SQL_GET_CURRENT_OID
to get the OID of the record just inserted into the database.
For example:
UCHAR oid[8];SDWORD retcode;
/* insert a recored into account table */
retcode = SQLExecDirect(hstmt, "INSERT INTO account VALUES()", SQL_NTS);
/* get the OID of the record just inserted */
retcode = SQLGetStmtOption(hstmt, SQL_GET_CURRENT_OID, &oid);
|
Example
4-9: Getting the current object's OID using SQLGetStmtOption
An OID
is an unique ID for an object in DBMaker. You can use the OID to uniquely
specify an object in database. For example, you can use OID in the WHERE
clause of a query:
SELECT * FROM account WHERE OID = ?
|
For more
detailed information on OID data type, please refer to the SQL Command
and Function Reference or the Database Administrator's Reference.
The extended
statement options SQL_DUMP_PLAN, SQL_PLAN_LEN and SQL_PLAN are used
to get the query plan generated by the DBMaker Query Optimizer for a
prepared SQL statement.
To get
the plan of a prepared SQL statement, use the following procedure:
Turn on the SQL_DUMP_PLAN option by calling SQLSetStmtOption.
Get the length of the plan string by calling SQLGetStmtOption
with fOption SQL_PLAN_LEN
Allocate a buffer according to the plan string and then call SQLGetStmtOption
with fOption SQL_PLAN to get the plan string.
For example:
SDWORD planlen;
UCHAR *planstr;
SDWORD retcode;
/* turn on the dump plan option */
retcode = SQLSetStmtOption(hstmt, SQL_DUMP_PLAN, SQL_DUMP_PLAN_ON);
/* prepare an SQL JOIN statement */
retcode = SQLPrepare(hstmt, "SELECT * FROM account, branch WHERE
account.branchId = branch.branchId", SQL_NTS);
/* get the plan string length */
retcode = SQLGetStmtOption(hstmt, SQL_PLAN_LEN, &planlen);
/* allocate a buffer for the plan string according to the plan */
/* string length */
planstr = malloc(planlen);
/* get the plan string */
retcode = SQLGetStmtOption(hstmt, SQL_PLAN, planstr);
|
Example
4-10: Getting an SQL statement's query plan using SQLGetStmtOption
   
|