You are on page 1of 13

Using ODBC Tracing

Introduction to ODBC Tracing

What does tracing do?


ODBC tracing provides a way to log ODBC calls between an application, the ODBC driver
manager and the ODBC driver. The ODBC calls are written to a text file which can then be
examined for details on the behavior of the ODBC application and driver. This is helpful when
debugging error messages and other types of problems to determine their cause.

How to enable and disable tracing on Windows, UNIX and Linux


On Windows, ODBC tracing is enabled through the ODBC Administrator. To enable tracing:
 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 the Tracing tab
 Enter a location and name for the output file in the “Log file Path” field, such as
C:\temp\my_trace.log
 Click the button reading “Start Tracing Now.” After you click it, it will then read
“Stop Tracing Now.”
 Click OK to close the ODBC Administrator
 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
 Now open the log (e.g. /home/my_dir/odbctrace.out) with a text viewer

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.

Why disable tracing?


Tracing is a very useful tool, so why disable it instead of leaving tracing turned on all the time? It
might seem like leaving tracing enabled would be a great idea- you’d have a running log of every
ODBC call made on your machine. However, there is a powerful downside that outweighs this
benefit. Because tracing writes every ODBC call to a text file, its very resource intensive. That
means the performance of your ODBC application is seriously impacted by tracing. Leaving
tracing enabled will noticeably slow your application performance and use CPU resources on
your machine. In addition, the size of the output file will rapidly grow to the point that it requires
many megabytes of storage space.

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.

When will an ODBC trace be helpful?


Tracing is a way to debug issues when you can’t determine the source of a problem or error. It
can be used to narrow down a problem to its origination point—the driver, the application, the
database, even the network. For example, tracing can contribute in the debugging of error
messages, data corruption, and performance problems.
With error messages, the format of the message points to its source. Error messages usually
come from:
 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 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

Process_Name XXX-YYY ENTER ODBC_Function


DataType value_of_argument_1
DataType value_of_argument_2
DataType value_of_argument_3
.
.
.

Process_Name XXX-YYY EXIT ODBC_Function with return code


N (RetCode)
DataType value_of_argument_1
DataType value_of_argument_2
DataType value_of_argument_3
.
.
.

Process_Name - The name of the process being traced


XXX - The current process id
YYY - The current thread id
ENTER : EXIT - Enter or exit the ODBC function
ODBC_Function - The ODBC API function being executed by the application
RetCode - A return code provided by the ODBC driver which determines the
status of the function executed
DataType - The data type of the ODBC function’s argument
Value_of_argument - The argument’s data which is passed or returned during the
ODBC function’s execution

Here is an example of executing the function SQLExecDirect using an ODBC application called
ODBC Test.

OdbcTE32 870-4e8 ENTER SQLExecDirect


HSTMT 00982470
UCHAR * 0x0015B370 [ -3] "select * from emp\ 0"
SDWORD -3

OdbcTE32 870-4e8 EXIT SQLExecDirect with return code 0


(SQL_SUCCESS)
HSTMT 00982470
UCHAR * 0x0015B370 [ -3] "select * from emp\ 0"
SDWORD -3

According to the ODBC Specification, the SQLExecDirect function takes 3 arguments:


RETCODE SQLExecDirect(hstmt, szSqlStr, cbSqlStr)

Type Argument Use Description


HSTMT hstmt Input Statement handle
UCHAR FAR * szSqlStr Input SQL statement to be executed
SDWORD cbSqlStr Input Length of szSqlStr

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

[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. 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

OdbcTE32 4c8-d3c EXIT SQLExecDirect with return code -1


(SQL_ERROR)
HSTMT 00981BE8
UCHAR * 0x001513B0 [-3] "select * from em\ 0"
SDWORD -3
DIAG [42S02] [DataDirect][ODBC Oracle Wire Protocol driver]
[Oracle]ORA-00942: table or view does not exist (942)

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

OdbcTE32 570-6c0 EXIT SQLAllocStmt with return code 0


(SQL_SUCCESS)
HDBC 01391690
HSTMT * 0x01495184 ( 0x01394a28)

OdbcTE32 570-6c0 ENTER SQLNumResultCols


HSTMT 01394A28
SWORD * 0x0012F958

OdbcTE32 570-6c0 EXIT SQLNumResultCols with return code -1


(SQL_ERROR)
HSTMT 01394A28
SWORD * 0x0012F958

DIAG [S1010] [Microsoft][ODBC Driver Manager] Function


sequence error (0)

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.

Reproducing problems based on a trace log


An ODBC trace log can be used as a guide for reproducing a problem in a simpler environment.
It helps isolate the problem to a few numbers of steps. One way of reproducing the problem
outside of your application is to use the program ODBC Test. ODBC Test is a tool developed by
Microsoft. Contact DataDirect SupportLink for ODBC Test, or download it from the SupportLink
Online Downloads/Tools page available from http://support.datadirect.com.

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.

msqry32 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 ENTER SQLExecute


HSTMT 008C1A90

msqry32 348-948 EXIT SQLExecute with return code 0


(SQL_SUCCESS)
HSTMT 008C1A90

msqry32 348-948 ENTER SQLNumResultCols


HSTMT 008C1A90
SWORD * 0x0012E57E

msqry32 348-948 EXIT SQLNumResultCols with return code 0


(SQL_SUCCESS)
HSTMT 008C1A90
SWORD * 0x0012E57E (3)

msqry32 348-948 ENTER SQLBindCol


HSTMT 008C1A90
UWORD 1
SWORD 1 <SQL_C_CHAR>
PTR 0x0018B530
SQLLEN 9
SQLLEN * 0x0012E37C
msqry32 348-948 EXIT SQLBindCol with return code 0
(SQL_SUCCESS)
HSTMT 008C1A90
UWORD 1
SWORD 1 <SQL_C_CHAR>
PTR 0x0018B530
SQLLEN 9
SQLLEN * 0x0012E37C (0)

msqry32 348-948 ENTER SQLBindCol


HSTMT 008C1A90
UWORD 2
SWORD 1 <SQL_C_CHAR>
PTR 0x0018B539
SQLLEN 11
SQLLEN * 0x0012E37C

msqry32 348-948 EXIT SQLBindCol with return code 0


(SQL_SUCCESS)
HSTMT 008C1A90
UWORD 2
SWORD 1 <SQL_C_CHAR>
PTR 0x0018B539
SQLLEN 11
SQLLEN * 0x0012E37C (0)

msqry32 348-948 ENTER SQLBindCol


HSTMT 008C1A90
UWORD 3
SWORD 1 <SQL_C_CHAR>
PTR 0x0018B544
SQLLEN 5
SQLLEN * 0x0012E37C

msqry32 348-948 EXIT SQLBindCol with return code 0


(SQL_SUCCESS)
HSTMT 008C1A90
UWORD 3
SWORD 1 <SQL_C_CHAR>
PTR 0x0018B544
SQLLEN 5
SQLLEN * 0x0012E37C (0)

msqry32 348-948 ENTER SQLFetch


HSTMT 008C1A90

msqry32 348-948 EXIT SQLFetch with return code 0 (SQL_SUCCESS)


HSTMT 008C1A90

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

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, I’ll 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);
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");

Here is the output of this application:


Allocating ODBC Environment...
Connecting to DSN: 500_Sybase12, UID: test01, PWD: test01

First Name Last Name Emp ID


---------- --------- ---------
Tyler Bennett E10297
.
.
.

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.

You might also like