How To Read An ODBC Trace File
How To Read An ODBC Trace File
Tracing is now enabled. Perform the steps you would like to trace (i.e. Open your
application, repeat the steps until the error or problem occurs)
Once you have reproduced the behavior you were tracing, ODBC tracing should be
turned off
Re-open the ODBC Administrator (in the Control Panel, look for the Data Sources
(ODBC) icon)
Click the tab where your data source is listed (either System DSN or User DSN) and then
click Tracing
Click the Stop Tracing Now button, and click OK to close the ODBC Administrator
Now open the log (e.g. C:\temp\my_trace.log) with a text viewer such as Notepad
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 in the odbc.ini file:
Using a text editor, open the odbc.ini text file where your data source is configured
(e.g. /opt/odbc/odbc.ini)
The bottom of the odbc.ini file contains a section headed with [ODBC] such as:
[ODBC]
IANAAppCodePage=4
InstallDir=ODBCHOME
Trace=0
TraceDll=ODBCHOME/lib/odbctrac.so
TraceFile=odbctrace.out
UseCursorLib=0
Enter a location and name for the output file where the ODBC trace file will be created
TraceFile=/home/my_dir/odbctrace.out
To turn tracing on, set
Trace=1
Save and close the file. Tracing is now enabled. Perform the steps you would like to trace
(i.e. Open your application, repeat the steps until the error or problem occurs)
Once you have reproduced the behavior you were tracing, ODBC tracing should be
turned off. To do so, re-open the odbc.ini file and set
Trace=0
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.
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.
An ODBC driver
The database system
The ODBC driver manager
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:
[vendor] [ODBC_driver] [database] message
For example,
[DataDirect] [ODBC Oracle driver] [Oracle] ORA-0919: specified length
too long for CHAR column
An error originating from the ODBC driver would look like:
[vendor] [ODBC_component] message
For example,
[DataDirect] [ODBC Oracle driver] Invalid precision specified.
An error originating from the ODBC driver manager would look like:
[vendor] [ODBC XXX] message
For example,
[Microsoft] [ODBC Driver Manager] Driver does not support this function
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 Programmers 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 Programmers 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.
XXX-YYY
ENTER ODBC_Function
DataType
value_of_argument_1
DataType
value_of_argument_2
DataType
value_of_argument_3
.
.
.
Process_Name
N (RetCode)
XXX-YYY
DataType
DataType
DataType
.
.
.
EXIT
value_of_argument_1
value_of_argument_2
value_of_argument_3
Here is an example of executing the function SQLExecDirect using an ODBC application called
ODBC Test.
OdbcTE32
870-4e8
HSTMT
UCHAR *
SDWORD
OdbcTE32
870-4e8
(SQL_SUCCESS)
HSTMT
UCHAR *
SDWORD
ENTER SQLExecDirect
00982470
0x0015B370 [
-3] "select * from emp\ 0"
-3
EXIT
SQLExecDirect
00982470
0x0015B370 [
-3
Type
HSTMT
UCHAR FAR *
SDWORD
Argument
hstmt
szSqlStr
cbSqlStr
Use
Input
Input
Input
Description
Statement handle
SQL statement to be executed
Length of szSqlStr
Compare the arguments above to the trace log to see what information is passed to the
SQLExecDirect function.
hstmt
szSqlStr
cbSqlStr
00982470
select * from emp\ 0 where \ 0 is the null terminator
-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
[Vendor][ODBC Component]Error message
or
[Vendor][ODBC Component][Data source]Error message
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. Its possible that, if the error isnt 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
HSTMT
UCHAR *
SDWORD
OdbcTE32
4c8-d3c
(SQL_ERROR)
HSTMT
UCHAR *
SDWORD
ENTER SQLExecDirect
00981BE8
0x001513B0 [-3] "select * from em\ 0"
-3
EXIT
SQLExecDirect
00981BE8
0x001513B0 [-3] "select * from em\ 0"
-3
Based on the error message returned, the table em referenced in the SQL statement executed
doesnt 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
HDBC
HSTMT *
ENTER SQLAllocStmt
01391690
01495184
OdbcTE32
570-6c0
(SQL_SUCCESS)
HDBC
HSTMT *
EXIT
OdbcTE32
ENTER SQLNumResultCols
01394A28
0x0012F958
570-6c0
HSTMT
SWORD *
OdbcTE32
570-6c0
(SQL_ERROR)
HSTMT
SWORD *
SQLAllocStmt
01391690
0x01495184 ( 0x01394a28)
EXIT
SQLNumResultCols
01394A28
0x0012F958
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.
Its 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 databases 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
applications 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? Lets 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, youll 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.
348-948
ENTER SQLPrepare
HSTMT
008C1A90
UCHAR *
0x0018A8E0 [
69] "SELECT EMP.FIRST_NAME,
EMP.LAST_NAME, EMP.DEPT\ d\ aFROM test.dbo.EMP EMP"
SDWORD
69
msqry32
348-948
EXIT SQLPrepare with return code 0
(SQL_SUCCESS)
HSTMT
008C1A90
UCHAR *
0x0018A8E0 [
69] "SELECT EMP.FIRST_NAME,
EMP.LAST_NAME, EMP.DEPT\ d\ aFROM test.dbo.EMP EMP"
SDWORD
69
msqry32
348-948
HSTMT
ENTER SQLExecute
008C1A90
msqry32
348-948
(SQL_SUCCESS)
HSTMT
EXIT
msqry32
ENTER SQLNumResultCols
008C1A90
0x0012E57E
348-948
HSTMT
SWORD *
SQLExecute
008C1A90
msqry32
348-948
(SQL_SUCCESS)
HSTMT
SWORD *
EXIT
msqry32
ENTER SQLBindCol
008C1A90
1
1 <SQL_C_CHAR>
0x0018B530
9
0x0012E37C
348-948
HSTMT
UWORD
SWORD
PTR
SQLLEN
SQLLEN *
msqry32
348-948
(SQL_SUCCESS)
SQLNumResultCols
008C1A90
0x0012E57E (3)
EXIT
SQLBindCol
msqry32
HSTMT
UWORD
SWORD
PTR
SQLLEN
SQLLEN *
008C1A90
1
1 <SQL_C_CHAR>
0x0018B530
9
0x0012E37C (0)
348-948
HSTMT
UWORD
SWORD
PTR
SQLLEN
SQLLEN *
ENTER SQLBindCol
008C1A90
2
1 <SQL_C_CHAR>
0x0018B539
11
0x0012E37C
msqry32
348-948
(SQL_SUCCESS)
HSTMT
UWORD
SWORD
PTR
SQLLEN
SQLLEN *
EXIT
msqry32
ENTER SQLBindCol
008C1A90
3
1 <SQL_C_CHAR>
0x0018B544
5
0x0012E37C
348-948
HSTMT
UWORD
SWORD
PTR
SQLLEN
SQLLEN *
SQLBindCol
008C1A90
2
1 <SQL_C_CHAR>
0x0018B539
11
0x0012E37C (0)
msqry32
348-948
(SQL_SUCCESS)
HSTMT
UWORD
SWORD
PTR
SQLLEN
SQLLEN *
EXIT
SQLBindCol
msqry32
348-948
HSTMT
ENTER SQLFetch
008C1A90
msqry32
348-948
HSTMT
EXIT SQLFetch
008C1A90
008C1A90
3
1 <SQL_C_CHAR>
0x0018B544
5
0x0012E37C (0)
Ill 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
Successfully connected to DSN '500_Sybase12'.
SQLPrepare:
In:
StatementHandle = 0x00981930,
StatementText = "SELECT EMP.FIRST_NAME, EMP.LAST_NAME,
EMP.DEPT FR...", TextLength = 68
Return:
SQL_SUCCESS=0
SQLExecute:
In:
StatementHandle = 0x00981930
Return:
SQL_SUCCESS=0
SQLNumResultCols:
In:
StatementHandle = 0x00981930, ColumnCountPtr = 0x0015AD98
Return:
SQL_SUCCESS=0
Out: *ColumnCountPtr = 3
SQLBindCol:
In:
StatementHandle = 0x00981930, ColumnNumber = 1,
TargetType = SQL_C_CHAR=1, TargetValuePtr = VALID,
BufferLength = 9, StrLen_or_IndPtr = VALID
Return:
SQL_SUCCESS=0
SQLBindCol:
In:
StatementHandle = 0x00981930, ColumnNumber = 2,
TargetType = SQL_C_CHAR=1, TargetValuePtr = VALID,
BufferLength = 11, StrLen_or_IndPtr = VALID
Return:
SQL_SUCCESS=0
SQLBindCol:
In:
StatementHandle = 0x00981930, ColumnNumber = 3,
TargetType = SQL_C_CHAR=1, TargetValuePtr = VALID,
BufferLength = 5, StrLen_or_IndPtr = VALID
Return:
SQL_SUCCESS=0
SQLFetch:
In:
StatementHandle = 0x00981930
Return:
SQL_SUCCESS=0
Using the example above, I can program a C application to follow the same steps. You can use
which ever programming language you like as long as it will all you to import the ODBC header
files. In this example, Ill be using C.
/*
** 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);
}
/*
** 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");
Last Name
--------Bennett
Emp ID
--------E10297
The above examples show you 2 ways to use an ODBC trace to replicate an applications steps
in a small, stand-alone environment. This is useful for debugging purposes and also useful when
reporting issues to DataDirect SupportLink as youll have a way to reproduce an issue outside a
full-scale application.