Read this before hacking some ODBSequoia code.

A minimal knowledge of ODBC is required to understand this document.

Seeing the ODBSequoia driver as an ODBC layer on top of a JDBC driver is not far from the truth. This design is eased by the fact that JDBC has been more or less designed after ODBC.

Overview

Despite being designed for C and COBOL, the ODBC API is very object-oriented, which fits quite well a C++ implementation. The main objects defined by the ODBC standard, and implemented here are:

These 4 classes derive the abstract class ODBCItem defined in file abstract_item.hpp. This main purpose of this base class is to implement diagnostics (see below). The 4 derived classes are implemented in files env.hpp, connect.hpp, statement.hpp and descriptors.hpp respectively. The file explicit_type.cpp implements polymorphic ODBC functions, immediately redirecting the call to the class explicity specified by the ODBC application.

Below ODBSequoia, JDBC-like classes exported by Carob are:

There is one-to-one mapping between ODBCConnection and Connection as well as between ODBCStatement and Statement, the first holding a pointer to its underlying Carob implementation. Since in ODBC all accesses to result sets goes through an ODBCStatement handle, there is no need for an ODBCResultSet class on to of the Carob one. ODBStatement just calls carob_stmt->getResultSet() when needed. The fact that a Statement can have only one active ResultSet at a time makes this easy.

These two ODBCItem unfortunately need to hold a pointer to their corresponding Carob object (as opposed to a safer reference), because they need to support a dirty half-constructed state with a Carob NULL pointer inside. That's because ODBC objects are not atomically created: you can first create an empty ODBC object using SQLAllocHandle() and initialize it only later. Do not forget ODBC is just C, not real object-oriented C++.

Descriptors

Descriptors describe buffers for SQL Data: either rows of ResultSets or one row of parameters for (in the future) ParameterStatements. They hold all the variables related to memory management, type conversion etc. A descriptor is composed by a header and a vector of descriptor records, one record for each column/parameter. SQLBindCol() and SQLBindParam() are the usual way to configure records before fetching data/executing ParamaterStatements. There are new functions in ODBC 3.0 to control descriptors more finely; but those fine-tuning functions are not yet implemented by ODBSequoia.

Diagnostics

Errors and warnings are implemented in ODBC using diagnostics. According to the specification each ODBCItem holds one diagnostic composed of one header and a vector of diagnostics records. Each record represents one error/warning, so it is possible to attach an arbitrary number of errors/warnings to each ODBCItem (in a well defined order). Diagnostic records are implemented here by the class DiagRecord in file abstract_item.hpp.

The java.sql.SQLException chain (and its derived class SQLWarning) has been later designed in a way quite similar to ODBC diagnostics. CarobException is the Carob equivalent: CarobExceptions are used by Carob both to report SQLExceptions coming from the Controller and to issue its own errors. So there is a rather straight-forward mapping between these three cousins. Internally ODBSequoia and Carob makes an intensive use of exceptions, but obviously exceptions cannot be thrown by a C interface, so almost each ODBC function is implemented like this (simplified a bit):

SQLDoSomething(somehandle, arg1, arg2,...)
{
  try {
      somehandle->do_something(arg1, arg2,...)
  } catch (const CarobException& ce_chain) {
      somehandle->push_diagnostics(ce_chain);
      return SQL_ERROR;
  }
}

Instead of being thrown, warnings are just returned but they use the same classes.


$Date: 2006/03/24 14:04:36 $ $Revision: 1.1 $