Professional Documents
Culture Documents
On Unix and Linux, tracing is enabled through the odbc.ini file or through the Linux ODBC
Administrator tool that was added in the Connect for ODBC and Connect64 for ODBC 5.3
release.
To enable tracing with the Linux ODBC Administrator tool available in the 5.3 Connect and
Connect64 release:
Start the Linux ODBC Administrator in the install_dir/tools directory with the
command
odbcadmin
Click on the Trace tab.
Select the Enable Tracing checkbox.
Specify the path and the name of the trace log file in the Trace File field or click
Browse to select a file. If no location is specified, the trace log resides in the working
directory of the application you are running.
Click Apply.
Click OK when you are finished with the Linux ODBC Administrator and the
tracing tab changes are accepted.
Clear the Enable Tracing checkbox to disable tracing.
NOTE: The Linux ODBC Administrator was not available on the Linux Itanium platform at release
time; it is available on Linux x64 and Linux x86.
For these reasons, tracing should only be enabled when a specific problem or set of steps is
being traced. At all other times, tracing should be disabled.
The last component listed in the error string is typically the source of the error message. An error
message originating from a database would look like this:
An error originating from the ODBC driver manager would look like:
Knowing the origination of the error message may help you determine where to start the
debugging process, because the action you take in debugging an error is dependent upon the
error message. The DataDirect SupportLink Knowledgebase, available at
http://support.datadirect.com, is a great resource for researching error messages. Database
vendor web sites are another helpful resource.
With data corruption and performance issues, it is equally true that the problem could be in the
application, ODBC driver, or database. Performance slow-downs are also often caused by
network issues. The first step with these types of problems, as with all debugging, is to determine
the source of the problem.
See more information below on using ODBC trace to debug under How to read an ODBC trace
log.
Another source of useful information found in ODBC trace logs is SQLSTATEs. The Microsoft
ODBC API specifies that SQLSTATEs provide detailed information about the cause of a warning
or error. SQLSTATEs are guidelines and while drivers are not required to return them, many do
return them for the error messages and warnings they are capable of detecting. While
applications should not count on SQLSTATEs being returned, they are useful when available.
Most applications display a returning SQLSTATE along with the error message. A complete list of
SQLSTATEs and what functions return them is available in the ODBC Programmer’s Reference
on the MSDN site.
Finally, many databases return native error codes when possible. For example, the Oracle
database returns a series of error codes beginning with the letters ORA or PLS, among others.
The ODBC Programmer’s Reference does not document database-specific error codes. Rather,
the database vendor documentation or web site would be more likely to contain information on
these.
How to Read an ODBC Trace Log
An ODBC trace log will show the ODBC function calls an application makes to the ODBC Driver
and driver manager. Knowledge of the ODBC API Specification will help to decipher the trace log
generated. Having a copy of the ODBC Specification available will help as well.
Each ODBC function accepts a set of parameters. Here is an example of how an ODBC function
is seen in the trace log
Here is an example of executing the function SQLExecDirect using an ODBC application called
ODBC Test.
Compare the arguments above to the trace log to see what information is passed to the
SQLExecDirect function.
hstmt 00982470
szSqlStr “select * from emp\ 0” where “\ 0” is the null terminator
cbSqlStr -3 or SQL_NTS means the value is a null-terminated string
Error messages
Whenever an error message is returned by the ODBC driver, it will be displayed with the format
or
The [] immediately to the left of the error message informs the user where the error message was
generated. The error message will either be returned by one of the ODBC components, i.e. the
ODBC driver or driver manager, or by the data store, i.e. Oracle, Sybase, DB2, etc.
The [Vendor] is the distributor of the ODBC component referenced in the error message.
When trying to determine which ODBC function failed, search the trace log for the return code
SQL_ERROR or the error message returned by the application. If the error message or
SQL_ERROR cannot be found, search for the SQL statement or ODBC function being executed
at the time the problem exists in the application. Use that marker as a beginning point to start
traversing backward through the trace log. It’s possible that, if the error isn’t found in the trace
log, the results from executing a function caused the application to error. Application error
messages will not appear in the ODBC trace log.
Once the error message is found, verify the arguments passed for each ODBC function are
correct between where the statement was executed and where the error appeared. You may
need to look in the ODBC specification to confirm the correctness of the argument values.
Example 1
OdbcTE32 4c8-d3c ENTER SQLExecDirect
HSTMT 00981BE8
UCHAR * 0x001513B0 [-3] "select * from em\ 0"
SDWORD -3
Based on the error message returned, the table “em” referenced in the SQL statement executed
doesn’t exist in the database. Here, you would want to verify the table does in fact exist. The
table name may be incorrect, the table names maybe case sensitive, or it may exist under a
different catalog or schema than the default. If that is the case, you would need to qualify it in the
query.
Example 2
OdbcTE32 570-6c0 ENTER SQLAllocStmt
HDBC 01391690
HSTMT * 01495184
In this example the user is getting the error message “Function sequence error”. According to the
ODBC API Specification entry for SQLNumResultCols, “Function sequence error” is returned from
SQLNumResultCols when it is called before calling SQLPrepare or SQLExecDirect. The ODBC
trace log shows a statement handle was allocated followed by SQLNumResultCols. Since a
statement wasn’t prepared or executed, this error message is valid. To correct the problem, the
application needs to be modified such that SQLNumResultCols is executed in the proper order.
At times, the error message “Optional feature not implemented” is displayed in the log file. The
error message means the driver doesn’t support the functionality being requested. Unless you
require the functionality described by the ODBC function that is failing or this error message is
being returned back to your application, in most instances your problem is not related to this error
and the error can be ignored.
Data Corruption
An ODBC trace log can be helpful when diagnosing data corruption problems. The ODBC trace
file will display your data in clear text or its associated memory address, and you can see the
describe information for any of the columns or data types.
When debugging data corruption problems, its best if you know which statement is causing the
problem. Once the ODBC trace log is created, you can search the trace log for that SQL
statement. Either before or after the SQL statement, verify the values in the column describe
functions (SQLDescribeCol, SQLBindCol, etc) are correct based on how the data is stored and
how your application needs it returned. If executing a parameterized statement, verify
SQLBindParameter is defined correctly for each parameter marker.
It’s recommended that you get a Snoop trace as well as an ODBC trace when diagnosing data
corruption problems. The Snoop trace will show the hex values for the data and confirm the data
is being passed correctly between the ODBC driver and the data store. Contact DataDirect
SupportLink to obtain Snoop, or download it from the SupportLink Online Downloads/Tools page
available from http://support.datadirect.com.
Also, try executing the same statement through a database’s native tool. The native tools will
display how the data is stored in the database and whether the problem could or could not be the
ODBC driver. If corrupt data also occurs with the native tool, the ODBC driver is not related to the
problem.
Performance
When debugging performance problems, an ODBC trace log can prove helpful. However, to
determine if you have a performance problem, you need a good example demonstrating desired
and undesired performance. The example needs to be in similar environments. Comparing the
performance of 2 Oracle drivers running on the same operating system connecting to the same
database is a good example. Comparing the performance between Oracle and SQLServer is not
a good example. Oracle and SQLServer ODBC drivers and databases are coded completely
differently and they support different functionality. When comparing two different Oracle ODBC
drivers, as in the good example above, you have one variable: the ODBC drivers.
Generating an ODBC trace log of two different scenarios will allow you to compare the
application’s steps with the two drivers. Many times an application will execute one set of ODBC
functions with one driver and execute a different set of ODBC functions with another driver based
on the supported functionality of the driver. If that is the case, you need to determine why the
application is executing a different set of ODBC functions.
To determine the supported differences between the drivers, you can to look at the results of the
executed SQLGet functions, i.e. SQLGetInfo, SQLGetFunctions, SQLGetConnectAttr,
SQLGetStmtAttr, etc. These functions will return the supported functionality of the driver and/or
data store. You may need to look up the function attributes in the ODBC specification to
understand what functionality is being requested and their return values.
There is a document available on the DataDirect web site which explains how to write
performance-optimized applications.
Following handles
Handles are memory addresses used to identify a particular item, environment, connection,
statement or descriptor. The handles are used by ODBC functions for each particular item when
the function is called. The driver manager uses the handles to locate each item. You can use the
handles to follow the path an application takes when calling ODBC functions and executing
statements. An application can use multiple environments, connections, statements and
descriptor handles to process various functions.
How do you use handles to debug a problem? Let’s say your application is returning an error
message. You can search the ODBC trace log for the error message. You then want to find out
what steps occurred prior to the error.
In the trace log, you’ll see the data type of the handle being SQLHANDLE, HENV / SQLHENV,
HDBC / SQLHDBC, HSTMT / SQLHSTMT. This value is unique to the particular series of items
being called. You can search in the trace log for this handle number and each call made on the
handle will be shown. This allows you to pick out only the calls in the trace log that are relevant
to your error message. Keep in mind, some applications may create multiple handles. For
example, your application may execute three statements consecutively with each residing on a
different statement handle.
ODBC Test is a GUI application that allows a user to call any of the ODBC functions. Therefore,
any function found in an ODBC trace can be executed within this program.
In this example, I used MS Query to fetch data out of a Sybase database. I created an ODBC
Trace file so you can see what ODBC functions MS Query calls.
I’ll follow the same steps in ODBC Test. See the document ODBCTest.doc included in the ODBC
Test download for a tutorial on using ODBC Test. Here are results from the output box:
Full Connect(Default)
Env. Attr. SQL_ATTR_ODBC_VERSION set to SQL_OV_ODBC3
/*
** Allocate a HSTMT to communicate with ODBC DB Driver.
*/
rc = SQLAllocHandle (SQL_HANDLE_STMT, hdbc, &hstmt);
if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO)) {
printf ("Unable to Allocate a HSTMT:\n");
ODBC_error (SQL_HANDLE_DBC, hdbc);
EnvClose (henv, hdbc);
exit (255);
}
/*
** Build the SQL statement
*/
strcpy ((char*)table, "EMP");
strcpy ((char*)sql, "SELECT FIRST_NAME, LAST_NAME, EMP_ID ");
strcat ((char*)sql, "FROM EMP");
/*
** Prepare our SQL Statement for Executions.
*/
rc = SQLPrepare (hstmt, sql, (SDWORD)strlen((char*)sql));
if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO)) {
printf ("SQLPrepare has Failed. RC=%d\n", rc);
ODBC_error (SQL_HANDLE_STMT, hstmt);
EnvClose (henv, hdbc);
exit (255);
}
/*
** Execute Prepared SQL
*/
rc = SQLExecute (hstmt);
if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO)) {
printf ("......SQLExcute has Failed. RC=%d\n", rc);
ODBC_error (SQL_HANDLE_STMT, hstmt);
EnvClose (henv, hdbc);
exit (255);
}
/*
** Bind variables to SQL Columns
*/
rc = SQLBindCol (hstmt, 1, SQL_C_CHAR,
&first_name,
(SDWORD)sizeof(first_name),
&val_first_name);
rc = SQLBindCol (hstmt, 2, SQL_C_CHAR,
&last_name,
(SDWORD)sizeof(last_name),
&val_last_name);
rc = SQLBindCol (hstmt, 3, SQL_C_CHAR,
&emp_id,
(SDWORD)sizeof(emp_id),
&val_emp_id);
/*
** Fetch Data
*/
printf("\n");
printf("%-15s %-15s %-15s\n",
"First Name", "Last Name", "Emp ID");
printf("%-15s %-15s %-15s\n",
"----------", "---------", "---------");
for (;;) {
rc = SQLFetch (hstmt);
if (rc == SQL_NO_DATA_FOUND) {
printf ("SQLFetch returns: SQL_NO_DATA_FOUND\n");
break;
}
if ((rc != SQL_SUCCESS) && (rc != SQL_SUCCESS_WITH_INFO)) {
printf ("SQLFetch has Failed. RC=%d\n", rc);
ODBC_error (SQL_HANDLE_STMT, hstmt);
break;
}
printf("%-15s %-15s %-15s\n",
first_name, last_name, emp_id);
}
printf("\n");
The above examples show you 2 ways to use an ODBC trace to replicate an application’s steps
in a small, stand-alone environment. This is useful for debugging purposes and also useful when
reporting issues to DataDirect SupportLink as you’ll have a way to reproduce an issue outside a
full-scale application.