 | Level: Introductory Rajesh Kartha (kartha@us.ibm.com), Software Engineer, IBM Cloudscape Group
23 Sep 2004 This article introduces you to ODBC programming with Apache Derby or IBM® Cloudscape™ as the backend data source. You'll learn how ODBC applications differ from embedded SQL applications, how to set up Apache Derby as an ODBC data source, and how an ODBC application is structured. In addition, the author covers error handling and includes an example.
Embedded SQL vs. ODBC
Open Database Connectivity (ODBC) is one of the most popular ways today to code a database application. Let's begin our examination of ODBC
programming for Apache Derby by comparing ODBC applications with embedded SQL applications.
Embedded SQL applications contain SQL statements embedded into the source code,
which is written using a high-level language, a common example being C.
ODBC applications, in contrast, use a standard set of APIs to communicate with the
database. The SQL statements are sent using these standard API methods to the
database manager for processing.
Some of the main differences between embedded SQL and ODBC applications
are:
-
Embedded SQL applications tend to lack interoperatiliby. Applications
might have to modified to work with other RDBMS.
-
Cursors are automatically generated in ODBC applications, if
needed, whenever the SQLExecute()/SQLExecDirect() functions are
executed. In contrast, embedded SQL requires a DECLARE CURSOR statement.
-
ODBC applications do not require the explicit declaration and use of
host variables.
-
ODBC functions manage environment, connection, and SQL information
using handles; hence there is no need for complex product-specific
data structures (such as the SQLCA and SQLDA, used for DB2 embedded SQL apps).
-
Typically in a manual transaction, ODBC/CLI applications rely on the
standard SQLEndTran() function to terminate active transactions.
Embedded SQL applications depend on the way SQL statements are
prepared dynamically, which may differ between database vendors.
Since ODBC applications pass all the SQL statements to the
data source for execution, applications portability is easier to achieve. This
could be an issue with embedded SQL applications.
DB2 Runtime Client
The ODBC support for Apache Derby or IBM Cloudscape is made possible
with the DB2 Runtime Client.
Distributed relation database architecture (DRDA) is the common protocol used for
communication between the client and the server, and is provided by the Runtime Client. Please refer to the
instructions available in the developerWorks article Cloudscape and ODBC for
more installation instructions and information on the platform availablity. Using Cloudscape
and ODBC is currently in beta status.
Set up an Apache Derby database as an ODBC data source
Follow these steps to create an ODBC data source for
an Apache Derby database:
-
Start the Apache Derby Network Server. Please refer to the Derby
documentation for infomation related to setting up the CLASSPATH
and the various options for starting the Derby Network Server.
-
For example:
java org.apache.derby.drda.NetworkServerControl start
The command above starts the Derby Network Server with the
default options: host is 'localhost', port is '1527'
-
Connect to the Derby Network Server using the 'ij' tool to create
the database
-
For example:
ij> connect 'jdbc:derby:net://localhost:1527/SAMPLE;create=true:user=app;password=app;';
The command above connects to the Derby Network Server and
creates a database called SAMPLE.
-
Catalog the Derby Network Server with the DB2 Runtime client
using the CLP
db2 catalog tcpip node DERBYNET remote localhost server 1527
-
Catalog the database with the DB2 Runtime client using the Command Line Processor (CLP).
db2 catalog db SAMPLE at node DERBYNET authentication server
-
Verify connection to the database using DB2 CLP:
db2 connect to SAMPLE user abc using abc
-
Create the ODBC data source:
-
On Windows® 2000, create the ODBC data source by clicking on Start -> Settings ->
Control Panel -> Administrative Tools -> Data Sources -> System DSN.
-
Another approach for Windows is to use DB2 CLP to perform the ODBC
data source creation.
db2 catalog system odbc data source SAMPLE
-
To list the current set of ODBC data sources:
db2 list system odbc data sources
-
For Linux, it will depend on the type of ODBC Driver Manager installed;
please refer its documentation on setting up the ODBC data source.
 |
Structure of an ODBC
application
ODBC and the DB2 Call Level Interface (CLI) have many of the method
calls in common; however some extra driver manager related method
calls are available in ODBC. For more information on ODBC, please
refer to
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/odbc/htm/odbcabout_this_manual.asp.
The main difference between an ODBC application and a CLI application is in the
way the database driver is loaded. In an ODBC environment, the
Driver Manager provides the interface to the application. It also
dynamically loads the necessary driver for the database server
that the application connects to. Each ODBC function call the
application makes to the ODBC DriverManager is forwarded to the
appropriate data source driver for processing. In a CLI environment,
the application does the loading by itself and hence is bound to a
single data source driver. Figure 1
illustrates this difference.
Figure 1. Difference between the CLI and ODBC environments

For more information on the DB2 CLI, please refer to the DB2 CLI Guide and
Reference, Volume 1 and Volume 2.
An ODBC application mainly consists of the following three parts:
-
Initialization
-
Transaction processing
-
Termination
1) Initialization
All the resources needed to create connections, execute SQL and
perform transactions are allocated in this step, shown in Figure 2.
Figure 2. Initialization in an ODBC environment

In ODBC, all these resources are simple pointers (data storage areas)
to data objects controlled by the ODBC Driver Manager or the DB2
CLI, and are called 'handles'. The are four main types of handles:
Table 1. Handle types in ODBC | Handle type | Purpose | |
Environment
|
Global storage that has ODBC-specific information
| |
Connection
|
Storage for data source connection
| |
Statement
|
Storage for a single SQL statement
| |
Descriptor
|
Storage for metadata, which has information about
application variables used in binding to parameter
markers or columns of a result set
|
Environment handle
Allocating an environment handle is usually the first step in an
ODBC application. One environment handle must be present to allocate
other types of handles in an application. An environment handle is
allocated by calling the SQLAllocHandle() function with the
SQL_HANDLE_ENV option specified.
SQLHANDLE EnvHandle = 0;
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE, &EnvHandle);
|
Connection handle
To make a connection to a data source for an ODBC application, you first create
first a connection handle. A connection handle is
allocated by calling the SQLAllocHandle() function with the
SQL_HANDLE_DBC option specified.
SQLHANDLE ConnHandle = 0;
SQLAllocHandle(SQL_HANDLE_DBC, EnvHandle, &ConnHandle);
|
To establish a connection to a data source, use one of the following methods:
Table 2. Methods to connect to a data source | Function call | Usage | |
SQLConnect()
|
This is the simplest of the connect functions and only needs
a data source name, user, and password. All the other required information
needs to be in the db2cli.ini or the ODBC.INI file.
| |
SQLDriverConnect()
|
Using this function, an application can send connection information
to a data source driver using a connection string with key-value
pairs separted by semicolons. No information needs to be stored
in the db2cli.ini or the ODBC.INI file.
For example: DSN=SAMPLE;UID=app;PWD=app;
| |
SQLBrowseConnect()
|
This function is similar to the SQLDriverConnect() function, and
uses a connection string to send connection information to a driver.
But with the SQLBrowseConnect()function one can be used to construct
a complete connection string at application run time, using the
connection information given by the user, through the application's
dialog box, for example.
|
Statement handle
The statement handle is the most important, as it is used to process
the SQL statements in the application. For executing each SQL statement
there needs to be a statement handle associated with it, hence an
application can have multiple statement handles. A statement handle
is allocated by calling the SQLAllocHandle() function with the
SQL_HANDLE_STMT option specified.
SQLHANDLE StmtHandle = 0;
SQLAllocHandle(SQL_HANDLE_STMT, ConHandle, &StmtHandle);
|
A statement handle can be used to:
-
Bind application variables to parameter markers used in a prepared statement
-
Prepare and submit a statement for execution
-
Obtain metadata about any result data set(s) produced
-
Bind application variables to columns found in any result data set(s) produced
-
Retrieve (fetch) data from any result data set(s) produced
-
Obtain diagnostic information when a statement fails.
Descriptor handle
For each statement handle allocated, four descriptor handles are
automatically allocated and associated with the handle as part of
the allocation process. These descriptor handles remain associated
with the corresponding statement handle until it is destroyed.
The ODBC application can perform most of the operations using these
implicitly defined descriptor handles.
One or more descriptor handles can be explicitly allocated by calling
the SQLAllocHandle() function with the SQL_HANDLE_DESC option and a
valid connection and statement handle.
Attributes
ODBC applications can connect to various data sources and can use
the SQLGetInfo(), SQLGetFunctions(), and SQLGetTypeInfo() functions
to get more information about the underlying data source driver in use.
In addition some of the 'driver attributes' can be altered to change
the behaviour of the driver. The following are the types of driver
attributes that are available:
-
Environment attributes
-
Connection attributes
-
SQL statement attributes
Please note the attributes mentioned below under each type of driver attributes
are the most common ones found in ODBC applications. For a complete list and description
of the attributes for each type, please refer to to the DB2 CLI Guide and Reference, Volume 2.
At the time of writing this paper, the list of ODBC attributes supported by Apache Derby have not been finalized. Once they are finalized a complete list will be made available on developerWorks.
Environment attributes
These attributes affect the way ODBC functions behave under a
specific environment. The environment attributes can only be changed
as long as no connection handles have been allocated using the
environment handle, once the connection handles are allocated, these
attributes can only retrieved and not changed. The functions used to
access these attributes are SQLSetEnvAttr()/SQLGetEnvAttr().
Table 3. Environment attributes |
SQL_ATTR_ODBC_VERSION
|
Set the ODBC version to be used
| |
SQL_ATTR_OUTPUT_NTS
|
Whether or not the driver needs to append the null terminator to string data
|
Connection attributes
These attributes affect the behaviour of the connections to data sources. Depending
on each of these connection attributes, they can be set a different times: before/after
allocating the connection handle, or before/after establishing a connection to the data source.
The functions used to access these attributes are SQLSetConnectAttr()/SQLGetConnectAttr().
Table 4. Connection attributes |
SQL_ATTR_AUTOCOMMIT
|
Automatic commit on/off
| |
SQL_ATTR_MAXCONN
|
Max concurrent connections that can be open for the application
| |
SQL_ATTR_TXN_ISOLATION
|
Set isolation level for the current connection
SQL_TXN_SERIALIZABLE for repeatable read,
SQL_TXN_REPEATABLE_READ for read stability,
SQL_TXN_READ_COMMITTED for cursor stability, and
SQL_TXN_READ_UNCOMMITTED for uncommitted read
|
Statement attributes
These attributes affect the behavior of the statement level functions.
Again depending on each of these statement attributes, they can be
set at different times: before/after executing the SQL statement or
at any time. The functions used to access these attributes are
SQLSetStmtAttr()/SQLGetStmtAttr().
Table 5. Statement attributes |
SQL_ATTR_CURSOR_SENSITIVITY
|
Make changes made by other cursors visible or not
| |
SQL_ATTR_CURSOR_TYPE
|
Type of cursor (forward-only, static, keyset-driven, or dynamic).
| |
SQL_ATTR_CONCURRENCY
|
Specifies the cursor concurrency level to use (read-only, low-level
locking, or value-comparison locking).
| |
SQL_ATTR_RETRIEVE_DATA
|
Specifies whether or not the SQLFetch() and SQLFetchScroll() functions
are to automatically retrieve data after they position the cursor.
|
2) Transaction processing
After the initialization of all the required handles, this section
in the ODBC application is where all transaction processing occurs,
where the SQL statements are sent to the data sources, and the results
are processed. As you can see in Figure 3, the main steps involved here are:
-
Allocate one or more statement handles (covered in the section above on the structure of an ODBC application).
-
Execute SQL statements
-
Process results
-
Manage transactions
Figure 3. Transaction processing in an ODBC application

Executing SQL statements
There are two ways to execute SQL Statements: Prepare and Execute, and
Execute Immediate.
Prepare and Execute
This is used when statements have to be executed repeatedly. The
statement can have multiple parameter markers ('?'). The ODBC
functions SQLPrepare() and SQLExecute() are used to process the
SQL statements.
SQLPrepare() function prepares the statement:
strcpy((char *) SQLStmt, "INSERT INTO EMPLOYEE VALUES (?,?,?,?, CURRENT DATE)" );
RetCode = SQLPrepare(StmtHandle, SQLStmt, SQL_NTS);
|
The '?' indicates the position where values of application variables
can be substituted. Application variables are bound and associated with
the parameter markers using the SQLBindParameter() function. Once an
application variable is bound to a parameter marker, the association
remains until it is overridden or until the corresponding statement
handle is freed. Data from the bound variable is retrieved only
during the execution of the SQL statement.
SQLExecute() function executes the statement:
RetCode = SQLBindParameter(StmtHandle, 1,SQL_PARAM_INPUT, SQL_C_LONG, \
SQL_INTEGER,sizeof(EmpId), 0, &EmpId ,sizeof(EmpId), NULL);
RetCode = SQLExecute(StmtHandle);
|
Execute Immediate
This is typically used when statements have to be executed only once.
The ODBC function SQLExecDirect() is used to process the SQL statements.
SQLExecDirect() method performs the execution:
strcpy((char *) SQLStmt, "Create table Employee(empno int, firstname char(15), \
lastname char(25), jobtype varchar(10), date date) " );
RetCode = SQLExecDirect(StmtHandle, SQLStmt, SQL_NTS);
|
Process results
The results generated from the SQL statements have to be retrieved
and processed. They are stored in storage areas that you can reference
using the connection and statement handles used during execution. For
statements that do not generate any results, you can use the function return code
to verify a successful execution of the statement. To
retrieve rows from the result set generated by the SQL statements, the
following steps are involved:
-
For executed SQL statements, obtain the structure of the result set in terms
of the columns types, data lengths number of columns, and so on, by
using the SQLNumResultCols(), SQLDescribeCol() and
SQLColAttributes() functions.
-
Bind application variables to the columns from the result set
using the SQLBindCol() function. These variables are used as output
arguments and the values are retrieved and assigned to them during
the fetch operation.
SQLBindCol(StmtHandle, 1, SQL_C_CHAR, (SQLPOINTER)EmpNo, sizeof(EmpNo), NULL);
|
-
Retrieve the data stored in a result data set using any
combination of the SQLFetch() and SQLGetData() methods. You can fetch values from
the result set into the bound application variables
by calling the SQLFetch() repeatedly until there is no data available.
If no variables were bound, the values can be obtained using the
SQLGetData() function.
Transaction management
A transaction is a unit of work and consists of a set of SQL queries
which are either committed or rolled back on completion. There are
two commit modes for transaction processing:
By default, for ODBC applications the commit mode is 'auto
commit', where each individual SQL statement is treated as a complete
transaction, and is automatically committed after its successful
execution. For 'manual commit', the auto commit is turned off using
the following statement:
SQLSetConnectAttr(ConHandle, SQL_ATTR_AUTOCOMMIT,SQL_AUTOCOMMIT_OFF, SQL_IS_UINTEGER);
|
With manual commit mode, the transactions starts when the first
statement is executed, and are explicitly ended when the SQLEndTran()
function is called. To commit a transaction, the SQLEndTran() function
with the SQL_COMMIT option is called as follows:
SQLEndTran(SQL_HANDLE_DBC, ConHandle,SQL_COMMIT);
|
To roll back a transaction, the SQLEndTran() function with the
SQL_ROLLBACK option is called:
SQLEndTran(SQL_HANDLE_DBC, ConHandle,SQL_ROLLBACK);
|
3) Termination
Once all SQL statements in the application are executed and there
are none left, the storage area/memory allocated during the
transaction has to be freed up. You do this using the SQLFreeHandle()
function, during which all the parameter and column bound application
variable are unbound, all open cursors are closed, and the memory
associated with the statement and description handles is released.
SQLFreeHandle(SQL_HANDLE_STMT, StmtHandle);
|
Similarly, if the application is about to terminate, all the connections
needs to be terminated and all the resources allocated for the
connection and environment handles have to be released. To disconnect
an established connection, the SQLDisconnect() function is used as
follows:
SQLDisconnect(ConHandle);
|
All the storage area allocated for the connection and environment
handles is freed using the SQLFreeHandle() function with the
SQL_HANDLE_DBC and the SQL_HANDLE_ENV options respectively.
SQLFreeHandle(SQL_HANDLE_DBC, ConHandle); //free the connection handle
SQLFreeHandle( SQL_HANDLE_ENV, EnvHandle); //free the environment handle
|
Error handling and diagnostics
Return codes and SQLSTATE messages aid in diagnosing error conditions during
the execution of an ODBC application.
Return codes
For each ODBC function invoked, a special value known as a return code
is returned to the calling application. It indicates whether the
function executed as expected. Table 6 lists the possible return
codes that can be returned by any ODBC function:
Table 6. ODBC Return Codes |
SQL_SUCCESS_WITH_INFO
|
Function completed successfully with warning/non-fatal error
| |
SQL_NO_DATA or SQL_NO_DATA_FOUND
|
Function completed successfully without any data
| |
SQL_NEED_DATA
|
Function failed, missing data (connection information or parameter data)
| |
SQL_INVALID_HANDLE
|
Function failed with invalid handle
| |
SQL_STILL_EXECUTING
|
An asynchronous function still in progress
| |
SQL_ERROR
|
Function failed
| |
SQL_SUCCESS
|
Function completed successfully
|
SQL states
SQLSTATE messages give specific information about what caused an error or
warning condition, and are very useful for resolving errors. Almost
all of the database products use SQLSTATEs to provide such information.
SQLSTATEs are alphanumeric strings of five characters (bytes) in
length and have the format 'ccSSS', where 'cc' indicates the error
message class, as follows:
|
01
|
Warning
| |
HY
|
DB2 Call Level Interface error
| |
IM
|
ODBC Driver Manager error
|
'SSS' indicates the error message subclass.
Please note that the Apache Derby SQLSTATEs may be different from the
DB2 SQLSTATEs.
Getting diagnostic information
You can obtain the SQLSTATE, diagnostic messages, and native error codes information
by calling the SQLGetDiagRec() function, the
SQLGetDiagField() function, or both.
These functions take environment, connection, statement, or descriptor
handles as input and return the diagnostic information related to
the last function call made using that handle. For multiple diagnostic
records that get generated, the SQLGetDiagField() function call with
record number 0 and the SQL_DIAG_NUMBER option return the total
number of records. For each diagnostic record, a SQLGetDiagRec()
function call retrieves SQLSTATE values, diagnostic messages, and
native error codes.
An example
The sections above described the various modules within an ODBC
application. Listing 1 shows a simple ODBC application, written in the
C programming language for Windows, that makes a connection to an
ODBC data source called 'SAMPLE', inserts a row into the EMPLOYEE
table, and retrieves the employee id and last names for all employees
that have the job title as 'ENGINEER'.
The method print_error() aids in diagnosing error conditions during
the execution and shows the usage of the SQLGetDiagRec() and the
SQLGetDiagField() functions.
The sample ODBC program must be compiled by an appropriate C
programming language compiler, such as Visual C++.
Listing 1. Example ODBC Application
#include <stdio.h>
#include <windows.h>
#include <sql.h>
#include <sqlext.h>
#include <sqltypes.h>
void print_error( SQLSMALLINT htype, /* A handle type identifier */
SQLHANDLE hndl, /* A handle */
SQLRETURN frc, /* Return code to be included with error msg */
int line, /* Used for output message, indcate where */
char * file /* the error was reported from */
) {
SQLCHAR buffer[SQL_MAX_MESSAGE_LENGTH + 1] ;
SQLCHAR sqlstate[SQL_SQLSTATE_SIZE + 1] ;
SQLINTEGER sqlcode ;
SQLSMALLINT length, i ;
SQLINTEGER NumRecords;
printf( ">--- ERROR -- RC = %d Reported from %s, line %d ------------\n",
frc,
file,
line
) ;
SQLGetDiagField(htype, hndl, 0,SQL_DIAG_NUMBER, &NumRecords, SQL_IS_INTEGER,NULL);
printf("Total Number of diagnostic records: %d\n",NumRecords);
i = 1 ;
while ( SQLGetDiagRec( htype,
hndl,
i,
sqlstate,
&sqlcode,
buffer,
SQL_MAX_MESSAGE_LENGTH + 1,
&length
) == SQL_SUCCESS ) {
printf( " SQLSTATE: %s\n", sqlstate ) ;
printf( "Native Error Code: %ld\n", sqlcode ) ;
printf( "%s \n", buffer ) ;
i++ ;
}
printf( ">--------------------------------------------------\n" ) ;
}
int main()
{
// Declare The Local Memory Variables
SQLHANDLE EnvHandle = 0;
SQLHANDLE ConHandle = 0;
SQLHANDLE StmtHandle = 0;
SQLRETURN RetCode = SQL_SUCCESS;
SQLCHAR SQLStmt[255];
SQLCHAR JobType[10];
SQLCHAR EmpNo[10];
SQLCHAR LastName[25];
SQLCHAR FirstName[15];
int EmpId=1002;
/**
* INITIALIZATION
**/
// Allocate An Environment Handle
SQLAllocHandle(SQL_HANDLE_ENV, SQL_NULL_HANDLE,&EnvHandle);
// Set The ODBC Application Version To 3.x
printf("Setting the ODBC version... \n");
if (EnvHandle != 0)
SQLSetEnvAttr(EnvHandle, SQL_ATTR_ODBC_VERSION,(SQLPOINTER) SQL_OV_ODBC3,
SQL_IS_UINTEGER);
// Allocate A Connection Handle
printf("Creating Connection handle... \n");
if (EnvHandle != 0)
SQLAllocHandle(SQL_HANDLE_DBC, EnvHandle,&ConHandle);
// Connect To The Appropriate Data Source
if (ConHandle != 0)
{
RetCode = SQLConnect(ConHandle, (SQLCHAR *) "SAMPLE",SQL_NTS,
(SQLCHAR *) "app",SQL_NTS,
(SQLCHAR *) "app", SQL_NTS);
printf("Got the connection... \n");
}
/*
* TRANSACTION PROCESSING
**/
// Allocate An SQL Statement Handle
if (ConHandle != 0 && RetCode == SQL_SUCCESS)
SQLAllocHandle(SQL_HANDLE_STMT, ConHandle,&StmtHandle);
else{
printf("Error getting connection:\n");
print_error((SQLSMALLINT)SQL_HANDLE_DBC,ConHandle,
RetCode,__LINE__,__FILE__ );
return(0);
}
// Define A SELECT SQL Statement That Uses A Parameter
strcpy((char *) SQLStmt, "INSERT INTO EMPLOYEE VALUES (?,?,?,?, \
CURRENT DATE)");
RetCode = SQLPrepare(StmtHandle, SQLStmt, SQL_NTS);
if(RetCode!=SQL_SUCCESS)
{
printf("Error preparing the insert statement:\n");
print_error((SQLSMALLINT)SQL_HANDLE_STMT,StmtHandle,
RetCode,__LINE__,__FILE__ );
}
RetCode = SQLBindParameter(StmtHandle, 1,SQL_PARAM_INPUT, SQL_C_LONG,
SQL_INTEGER,sizeof(EmpId),
0, &EmpId ,sizeof(EmpId), NULL);
if(RetCode!=SQL_SUCCESS)
{
printf("Error binding the first param in insert:\n");
print_error((SQLSMALLINT)SQL_HANDLE_STMT,StmtHandle,
RetCode,__LINE__,__FILE__ );
}
RetCode = SQLBindParameter(StmtHandle, 2,SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR,sizeof(FirstName),
0, FirstName,sizeof(FirstName), NULL);
if(RetCode!=SQL_SUCCESS)
{
printf("Error binding the second param in insert:\n");
print_error((SQLSMALLINT)SQL_HANDLE_STMT,StmtHandle,
RetCode,__LINE__,__FILE__ );
}
RetCode = SQLBindParameter(StmtHandle, 3,SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR,sizeof(LastName),
0, LastName,sizeof(LastName), NULL);
if(RetCode!=SQL_SUCCESS)
{
printf("Error binding the third param in insert:\n");
print_error((SQLSMALLINT)SQL_HANDLE_STMT,StmtHandle,
RetCode,__LINE__,__FILE__ );
}
RetCode = SQLBindParameter(StmtHandle, 4,SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR,sizeof(JobType),
0, JobType,sizeof(JobType), NULL);
if(RetCode!=SQL_SUCCESS)
{
printf("Error binding the fourth param in insert:\n");
print_error((SQLSMALLINT)SQL_HANDLE_STMT,StmtHandle,
RetCode,__LINE__,__FILE__ );
}
strcpy((char *) FirstName, "Robert");
strcpy((char *) LastName, "Evans");
strcpy((char *) JobType, "ENGINEER");
RetCode = SQLExecute(StmtHandle);
if (RetCode == SQL_SUCCESS)
{
printf("Successfully executed the insert statement...\n");
}
else
{
printf("Error executing insert statement:\n");
print_error((SQLSMALLINT)SQL_HANDLE_STMT,StmtHandle,
RetCode,__LINE__,__FILE__ );
}
// A SELECT Statement
strcpy((char *) SQLStmt, "SELECT EMPNO, LASTNAME FROM ");
strcat((char *) SQLStmt, "EMPLOYEE WHERE JOBTYPE = ?");
// Prepare The SQL Statement
RetCode = SQLPrepare(StmtHandle, SQLStmt, SQL_NTS);
if(RetCode!=SQL_SUCCESS)
{
printf("Error preparing:\n");
print_error((SQLSMALLINT)SQL_HANDLE_STMT,StmtHandle,
RetCode,__LINE__,__FILE__ );
}
// Bind The Parameter Marker Used In The SQL Statement To
// An Application Variable
RetCode = SQLBindParameter(StmtHandle, 1,SQL_PARAM_INPUT, SQL_C_CHAR,
SQL_CHAR,sizeof(JobType),
0, JobType,sizeof(JobType), NULL);
if(RetCode!=SQL_SUCCESS)
{
printf("Error binding:\n");
print_error((SQLSMALLINT)SQL_HANDLE_STMT,StmtHandle,
RetCode,__LINE__,__FILE__ );
}
// Populate The "Bound" Application Variable
strcpy((char *) JobType, "ENGINEER");
// Execute The SQL Statement
RetCode = SQLExecute(StmtHandle);
// If The SQL Statement Executed Successfully, Retrieve
// The Results
if (RetCode == SQL_SUCCESS)
{
printf("Successfully executed the select statement...\n");
// Bind The Columns In The Result Data Set Returned
// To Application Variables
SQLBindCol(StmtHandle, 1, SQL_C_CHAR, (SQLPOINTER)EmpNo,
sizeof(EmpNo), NULL);
SQLBindCol(StmtHandle, 2, SQL_C_CHAR, (SQLPOINTER)
LastName, sizeof(LastName), NULL);
// While There Are Records In The Result Data Set
// Produced, Retrieve And Display Them
while (RetCode != SQL_NO_DATA)
{
RetCode = SQLFetch(StmtHandle);
if (RetCode != SQL_NO_DATA)
printf("%-8s %s\n", EmpNo, LastName);
}
}else
{
printf("Error executing select statement:\n");
print_error((SQLSMALLINT)SQL_HANDLE_STMT,StmtHandle,
RetCode,__LINE__,__FILE__ );
}
// Commit The Transaction
RetCode = SQLEndTran(SQL_HANDLE_DBC, ConHandle,SQL_COMMIT);
/**
* TERMINATION
**/
// Free The SQL Statement Handle
if (StmtHandle != 0)
SQLFreeHandle(SQL_HANDLE_STMT, StmtHandle);
// Terminate The Data Source Connection
if (ConHandle != 0)
RetCode = SQLDisconnect(ConHandle);
// Free The Connection Handle
if (ConHandle != 0)
SQLFreeHandle(SQL_HANDLE_DBC, ConHandle);
// Free The Environment Handle
if (EnvHandle != 0)
SQLFreeHandle(SQL_HANDLE_ENV, EnvHandle);
// Return Control To The OS
return(0);
}
|
Resources
About the author  | |  | Rajesh Kartha works as a software engineer in the Cloudscape group of IBM Data Management. |
Rate this page
|  |