Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

C/C++

The Oracle Call Interface and C++


NOV95: The Oracle Call Interface and C++

The Oracle Call Interface and C++

High-level abstractions for database developers

Jeremy Woo-Sam and Tony Murphy

Jeremy is the project leader of SQL*C++, and Tony is the project leader for SQR at MITI. They can be reached at jeremyw@ miti.com and [email protected], respectively.


The Oracle Call Interface (OCI) allows C applications to access and manipulate data in an Oracle database. Unfortunately, using the C API functions requires extensive knowledge of your tables and their data types. You must also know how to access columns inside a table and efficiently retrieve database rows into an application. By creating abstractions for database access and manipulation, you can wrap the C API into C++ classes. This provides database access in an easy-to-use and easy-to-maintain form, promotes code reuse, and bridges RDBMS concepts to an object-oriented paradigm.

In this article, we will present DBObject, a C++ class library that provides database connectivity and query access to an Oracle RDBMS. In doing so, we will examine the interface classes that are used within a C++ application, and the implementation classes that support array fetching within the interface classes. We will use an example that supports some primary data types: variable character strings, fixed-length character strings, numerics, and date types. Our abstraction of a database connection allows both local and remote connectivity, while our abstraction of database columns provides the mechanism for accessing data in the query.

The object-oriented approach offers database developers several benefits. For instance, users not familiar with cursors, array fetching, and other OCI concepts often find retrieving data from Oracle a time-consuming task. Object orientation lets users focus on solving their domain problems rather than on retrieving data.

Oracle data types are substantially different from those supported in C. This causes problems when converting between the database and C data types. For example, the Oracle NUMBER data type can have a value of NULL or 0. In the Oracle world, these values are different. This means no primitive C data type can entirely represent an Oracle NUMBER, because float, double, and int cannot represent both NULL and 0. This problem is commonly referred to as the "impedance mismatch" between RDBMS and C data types.

The common solution is to map the Oracle NUMBER data type to a C struct. However, this creates a new problem because you cannot use the C arithmetic operators with user-defined structs. (Note that C++ classes do allow structs to have operators.)

Finally, using high-level database abstractions lets you extend a class library's functionality without impacting existing applications. For example, the first version of our class did not implement array fetching; this was added later to improve efficiency. Likewise, obsolete OCI calls can be replaced with their newer counterparts without changing any application code. And, since our abstractions are valid on most SQL databases, our class library can easily be ported to other relational databases.

The DBObject Classes

The DBObjects database class library has two layers: the interface layer and the implementation layer. Interface-layer classes are routinely used to retrieve information from Oracle; implementation-layer classes are used internally by the interface layer. Figure 1 shows the class hierarchy for DBObjects, while Figure 2 shows the implementation and interface layers.

The DBObject class is the base class. It handles local and database errors. The DBConnection class is an abstraction of a database connection; see Listing One. DBConnection encapsulates the OCI functions that let you log into an Oracle RDBMS; see Table 1. Multiple DBConnection objects can be created to access different local or remote data sources within a single application. Each time a DBConnection object is instantiated, it in turn creates and maintains an Oracle Login Cursor. The Oracle Login Cursor is deleted when the DBConnection object is destroyed.

Class DBSQLEngine encapsulates the database cursor and the OCI functions for parsing SQL statements. The class also describes column information in SELECT statements, defines the output buffers, and fetches rows from the database. The DBQuery class, which inherits from DBSQLEngine, takes a valid SELECT statement and retrieves rows for the SELECT statement. The member function MoveNext() provides the means of navigating through the rows of the query.

The abstract DBCol class provides the interface to the data in the DBQuery object. The derived type-specific classes format the data within DBQuery so that it can be manipulated or printed.

The DBColumnBuffer classes are implementation classes that support array fetching, which allows multiple rows to be fetched with a single database access. This technique greatly reduces network traffic and improves access to database rows. The DBColumnBuffer classes also contain information about the column, such as its position, name, width, data type, scale, and precision.

The Interface Layer

The interface layer is comprised of DBConnection, DBQuery, DBCol, DBColNumber, DBColDate, and DBColString. Each class is a well-defined abstraction of some aspect of the Oracle interface.

DBConnection manages the database connection. You simply instantiate an instance of the class by passing an Oracle connect string to the constructor to establish a connection with the database. When the object is destroyed (either explicitly or when it goes out of scope), the destructor severs the connection with the database. Behind the scenes, the OCI functions ORLON and OLOGOF are used to log on to and off of the Oracle server.

DBQuery abstracts the concept of an SQL query. Its constructor accepts an SQL string as a parameter. To execute the query, invoke the Execute() member function. The rows of data returned by the query can be sequenced using the MoveNext() member function.

Internally, the DBQuery object uses an instance of DBSQLEngine to parse the SQL statement. It allocates DBColumnBuffers to hold the columns retrieved from the database by the SQL string. DBQuery uses the OCI function ODESCR to decide which type of column buffer to use: DBNumberBuff, DBDateBuff, or DBBufferInfo.

DBCol classes are used to interface and manipulate the actual data returned from the database. DBColNumber, DBColDate, and DBColString implement the C++ equivalents to the Oracle NUMBER, DATE, and CHAR data types, respectively.

DBCol is an abstract base class for the column classes that implements services required by DBColNumber, DBColDate, and DBColString. Such services include maintaining a pointer to the DBQuery object, identifying the corresponding DBColumnBuffer that contains the physical data, and implementing an IsNull() member function to tell if a column's value is set to NULL.

Interoperability of Column Objects

Implementing a cast to double in DBColNumber lets you use instances of this type with C arithmetic operators. For example, Total=A+B is legal in C++, where A and B are of type DBColNumber or double, and Total is of type double. During arithmetic operations, a NULL column is assumed to be of value 0. However, if you prefer a value other than 0, simply inherit from DBColNumber and redefine the cast.

DBColString implements a cast to char*, which allows its instances to interoperate with functions that require null-terminated strings. For example, strcmp(A,B) is acceptable in C++ if A and B are either of type DBColString or are pointers to null-terminated strings. During string function calls, a NULL value is converted to a null-terminated string of length 0. Users can implement their own representation of NULL by overloading the DBColString::GetString() member function.

The Implementation Layer

Implementation classes include DBSQLEngine, DBColumnBuffer, DBStringBuffer, DBNumberBuffer, and DBDateBuffer. They represent the internal structure and implementation of handling the rows retrieved from the database.

The DBSQLEngine class encapsulates the OCI functions that handle database cursors. These OCI functions handle fetching rows from the database, parsing the SQL statement, describing columns from a SELECT statement, and defining the memory area to hold rows returned from a database fetch. DBSQLEngine can be easily extended to support DML and DDL statements.

DBColumnBuffer is an abstract base class for DBStringBuffer, DBNumberBuffer, and DBDateBuffer. It represents the static and dynamic information of a database column and is responsible for gathering and maintaining information about it, including column type, external type, width, position, name, scale, and precision. DBColumnBuffer also contains information about the return length and NULL status for each row of the column. The pure virtual member functions GetData() and GetBufferAddress() are defined in the derived classes. This allows the derived classes to manage, maintain, and index their rows.

The Big Picture

The DBConnection, DBQuery, and DBCol classes work together to access data from the database. Separating these classes into complete, distinct abstractions allows greater flexibility than folding connectivity, query capabilities, and column handling into one class. Once a successful connection to the database is established, the DBConnection object can be used by multiple DBQuery objects. The DBQuery constructor takes the SELECT string and a reference to a DBConnection object as its arguments. When a DBQuery object has been successfully created, DBCol objects can be associated to the DBQuery internal columns. The DBCol objects take a reference to a DBQuery object and the column number that it wants to reference. Once "attached" to the DBQuery internal column, you can print and manipulate the DBCol object.

The DBQuery object constructs and maintains the DBColumnBuffer objects. It parses the SELECT statement and creates a linked list of DBBufferInfo objects for each column in the SELECT statement by calling the DBSQLEngine::Describe() function. A linked list is preferable to an array of DBColumnBuffer objects because Oracle does not provide any functions to determine the number of columns in a SELECT statement. To determine the number of columns in the query, the ODESCR function is called until it returns the end-of-select error status. While calling DBSQLEngine::Describe(), we gather information about the column to populate the DBQuery object's DBColumnBuffer array.

The DBQuery object now allocates an array of DBColumnBuffer pointers that reference a data-type-specific DBColumnBuffer object such as a DBStringBuffer or a DBDateBuffer. The DBQuery object walks the linked list, allocates a data-type-specific DBColumnBuffer object, copies the information from the linked list, and sets the proper width and external type.

After the array has been populated, the DBQuery object passes each DBColumnBuffer object in the array to the DBSQLEngine member function Define(). This is done to determine the destination address for the rows retrieved.

Example 1 is a typical C++ application that establishes a connection and performs a SELECT on a table called "EMP" (see Example 2). When Example 1 is executed, the output shown in Example 3 is generated. The source code for DBConnection, DBQuery, and other classes is available electronically; see "Availability," page 3.

Possible Extensions

To extend the DBObject class library, you could, for example, add enhanced data types. The DBObjects database class library demonstrates how to abstract a limited number of Oracle data types in C++. The same techniques can be applied to simulate other Oracle data types such as LONG and LONG RAW.

The current implementation utilizes double-precision floating-point numbers to represent the numeric values of NUMBER columns. This is sufficient for applications that don't require more than 15 digits of precision. Oracle itself supports a maximum precision of 38 digits. For more than 15 digits of precision, a high-precision column class could be developed. Client-application writers could then manage the efficiency-versus-precision trade-offs using the column class that most closely corresponds with their needs.

Additional member functions could be added to DBColString, DBColNumber, and DBColDate, resulting in more complete data types. This might include more operators and perhaps more advanced date manipulation.

Since most member functions are declared virtual, new column classes can be inherited from existing implementations. This technique can be used to add or modify behavior. For example, to change the way a column type prints its output, override the PrintValue() member function.

The DBObject class currently contains member functions to let derived classes set and get errors. A possible extension would be to throw an exception after the error message is copied into the buffer. Another extension could have the SetError() function terminate the application if the error were severe. Because all errors are processed in the DBObject class, your error-handling strategy can be implemented in this class.

Conclusion

Class libraries like DBObjects will play a substantial role in reducing complexity and enhancing interoperability between the C++ language and relational-database technology. The benefits don't just end here, however. By-products of our approach are portability, efficient database access at low cost, and reduced maintenance. Who knows? This type of technology may even aid a migration from RDBMS to the great nirvana of OODBMS.

Figure 1: DBObjects library architecture.

Figure 2: The DBObjects class library.

Table 1: OCI functions.

Function  Description
<I>orlon</I>    Establishes concurrent communication between an
                          OCI program and an Oracle database.
<I>ologof</I>   Disconnects a login data area from the Oracle program
                          global area and frees all Oracle resources owned by the Oracle
                          user process.
<I>ocom</I>     Commits the current transaction.
<I>ocon</I>     Enables autocommit.
<I>ocof</I>     Disables autocommit.
<I>orol</I>     Rolls back current transaction.
<I>oopen</I>    Opens specified cursor.
<I>oparse</I>   Parses an SQL statement or PL/SQL block and associates it
                          with a cursor.
<I>ocan</I>     Cancels a query after the desired number of rows
                          have been fetched.
<I>oclose</I>   Disconnects a cursor from the data area in the Oracle
                          server with which it is associated.
<I>ofen</I>     Fetches on multiple rows into arrays of variables, taking
                          advantage of the Oracle array interface.
<I>oexec</I>    Executes the SQL statement associated with a cursor.
<I>odescr</I>   Describes select-list items for SQL queries.
<I>odefin</I>   Defines an output variable for a specified select-list
                          item of an SQL query.
<I>oerhms</I>   Returns the text of an Oracle error message, given
                          the error code.
Example 1: Typical C++ app.
#include <iostream.h>
#include <dbobject.h>
#include <dbcol.h>
int main()
{
    DBConnection TestConn("scott/tiger@t:cire-ss2:oracle7");
    DBQuery TestQuery("SELECT ename,empno,job,hiredate from EMP",&TestConn);
    TestQuery.Execute();
    DBColString colEname(&TestQuery, 1);
    DBColNumber colEmpno(&TestQuery, 2);
    DBColString colJob(&TestQuery, 3);
    DBColDate colHiredate(&TestQuery, 4);
    while(TestQuery.MoveNext())
    {
    cout    << "EName:  " << colEname << endl
            << "Empno:  " << colEmpno  << endl
        << "Job:    " << colJob    << endl
        << "Hired   " << colHiredate << endl << endl;
    }
}
Example 2: Contents of EMP table.
ENAME  EMPNO JOB       HIREDATE
------ ----- ----      ---------
SMITH  7369  CLERK     17-DEC-80
ALLEN  7499  SALESMAN  20-FEB-81
WARD   7521  SALESMAN  22-FEB-81
JONES  7566  MANAGER   02-APR-81
MARTIN 7654  SALESMAN  28-SEP-81
BLAKE  7698  MANAGER   01-MAY-81
CLARK  7782  MANAGER   09-JUN-81
SCOTT  7788  ANALYST   09-DEC-82
KING   7839  PRESIDENT 17-NOV-81
TURNER 7844  SALESMAN  08-SEP-81
ADAMS  7876  CLERK     12-JAN-83
JAMES  7900  CLERK     03-DEC-81
Example 3: Output generated by Example 1.
EName:  SMITH
Empno:  7369
Job:    CLERK
Hired   17-DEC-1980 00:00:00
EName:  ALLEN
Empno:  7499
Job:    SALESMAN
Hired   20-FEB-1981 00:00:00
EName:  WARD
Empno:  7521
Job:    SALESMAN
Hired   22-FEB-1981 00:00:00
EName:  JONES
Empno:  7566
Job:    MANAGER
Hired   02-APR-1981 00:00:00
EName:  MARTIN
Empno:  7654
Job:    SALESMAN
Hired   28-SEP-1981 00:00:00
EName:  BLAKE
Empno:  7698
Job:    MANAGER
Hired   01-MAY-1981 00:00:00
EName:  CLARK
Empno:  7782
Job:    MANAGER
Hired   09-JUN-1981 00:00:00
EName:  SCOTT
Empno:  7788
Job:    ANALYST
Hired   09-DEC-1982 00:00:00
EName:  KING
Empno:  7839
Job:    PRESIDENT
Hired   17-NOV-1981 00:00:00
EName:  TURNER
Empno:  7844
Job:    SALESMAN
Hired   08-SEP-1981 00:00:00
EName:  ADAMS
Empno:  7876
Job:    CLERK
Hired   12-JAN-1983 00:00:00
EName:  JAMES
Empno:  7900
Job:    CLERK
Hired   03-DEC-1981 00:00:00

Listing One

//////////////////////////////////////////////////////////////////////
//  DBConnection Implementation
//////////////////////////////////////////////////////////////////////
#include <dbobject.h>
// Default Constructor
DBConnection::DBConnection()
{  
   m_pLoginCursor = 0;
   Reset();
}
// Constructor with access string
DBConnection::DBConnection(const char *AccessString)
{
   m_pLoginCursor = 0;
   Connect(AccessString);
}  
DBConnection::~DBConnection()
{
  if (IsConnected()) {
     Disconnect();
  }
  delete m_pLoginCursor;
}
short DBConnection::Disconnect()
{
  if (IsConnected()) {
      if (ologof(m_pLoginCursor)) {
         SetDBError(m_pLoginCursor->csrarc,DBErrorMsg(m_pLoginCursor->csrrc));
         SetError(-210, m_pUserName); 
     return 0;
      }
      else {
         Reset();
         m_nIsConnected = 0;
         return 1;
      }
  }
  else {
     SetError(-203, NOLOGIN_CURSOR );
     return 0;
  }
}
short DBConnection::Connect(const char *pAccess)
{
    if (! m_pLoginCursor) { 
       m_pLoginCursor = new DBOBJLOGIN;
       if (!m_pLoginCursor) {
          SetError(-100, ALLOCATION_ERROR); 
          return 0;
       }
    }
    if (!*pAccess) {
       SetError(-200, NULLACCESSS_STRING);
       return 0;
    }
    char *pPassWrd = strchr(pAccess, '/');
    if (!pPassWrd) {
       SetError(-201, NOPASSWORD);
       return 0;
    }
    pPassWrd++; // past the '/'
    int  UserLen = strlen(pAccess) - strlen(pPassWrd);
    char User[30];
    if (UserLen > 30) UserLen = 30; // truncate
    strncpy(User, pAccess, UserLen-1 );
    User[UserLen-1] = 0;     
     
    // Copy to member 
    strcpy(m_pUserName, User);
    if (orlon(m_pLoginCursor, hda, (char*)pAccess)) {
        SetDBError(m_pLoginCursor->csrarc, DBErrorMsg(m_pLoginCursor->csrrc) );
        SetError(-205, m_pUserName);
        return 0;
    }
    Reset();
    m_nIsConnected = 1;
    return 1;
}
short DBConnection::Commit()
{
    if (m_pLoginCursor) {
        if (ocom(m_pLoginCursor)) {
      SetDBError(m_pLoginCursor->csrarc,DBErrorMsg(m_pLoginCursor->csrrc));
          SetError(-206, m_pUserName); 
          return 0;
        }
        Reset();
        return 1;       
     }
     else {
      SetDBError(m_pLoginCursor->csrarc,DBErrorMsg(m_pLoginCursor->csrrc));
          SetError(-202, m_pUserName);
     }    
     Reset();
     return 1;
}
short DBConnection::SetAutoCommit(short OnOrOff)
{
   if (m_pLoginCursor) {
      if (OnOrOff == 1) {
         if (ocon(m_pLoginCursor)) {
      SetDBError(m_pLoginCursor->csrarc,DBErrorMsg(m_pLoginCursor->csrrc));
          SetError(-206, m_pUserName); 
          return 0;
         }
         else {
           Reset();
           return 1;
         } 
      }
      else {
         if (ocof(m_pLoginCursor)) {
      SetDBError(m_pLoginCursor->csrarc,DBErrorMsg(m_pLoginCursor->csrrc));
          SetError(-206, m_pUserName); 
          return 0;
         }
         else {
           Reset();
           return 1;
         }
      }
   }
   else {
      SetDBError(m_pLoginCursor->csrarc, DBErrorMsg(m_pLoginCursor->csrrc) );
      SetError(201, "No Oracle Login Object Defined");
      return 0;
    }
}
short DBConnection::Rollback()
{
   if (m_pLoginCursor) {
      if (orol(m_pLoginCursor)) {
      SetDBError(m_pLoginCursor->csrarc,DBErrorMsg(m_pLoginCursor->csrrc));
          SetError(-206, m_pUserName); 
          return 0;
      }
      else {
          Reset();
          return 1;
      }
   }
   else {
      SetDBError(m_pLoginCursor->csrarc, DBErrorMsg(m_pLoginCursor->csrrc));
      SetError(201, NOLOGIN_CURSOR);
      return 0;
   }
}
const char* DBConnection::DBErrorMsg(int ErrorNo)
{
    if (m_pLoginCursor) {
       static char ExceptionBuffer[MAX_DBMSG];
       memset(ExceptionBuffer,0, MAX_DBMSG);
       oerhms(m_pLoginCursor, ErrorNo, ExceptionBuffer, MAX_DBMSG-1);
       return ExceptionBuffer;
    }
    else {
       SetError(-203, NOLOGIN_CURSOR);
       return "";
    }        
}


Copyright © 1995, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.