Saturday, August 15, 2009

Getting Started with OCCI (Linux Version)

This is a follow-on post to the "Getting Started with OCCI (Windows Version)" with the not too minor change of swapping out Windows for Linux as the development machine. Much of the content is intentionally the same, however. For this particular "walkthrough" (for lack of a better term) I illustrate creating a "private" installation of the Oracle software. That is, I extract the software under my user's home directory. This has the advantage that it is completely separate from any other user's software. Of course, it also has the disadvantage that if many users have the same software installed there will be duplication. As this machine has no other "normal" user accounts (i.e. non-system accounts such as "root") other than my account this is not an issue for me.

The steps should be general enough to easily mould them to your environment and/or needs.

The Oracle C++ Call Interface, also known as OCCI, is an application programming interface (API) built upon the Oracle Call Interface (OCI - another lower level API from Oracle). One of the goals of OCCI is to offer C++ programmers easy access to Oracle Database in a fashion similar to what Java Database Connectivity (JDBC) affords Java developers. If you would like to learn more about what OCCI is (and isn't), pay a visit to the OCCI documentation on Oracle Technology Network (OTN) here:

http://download.oracle.com/docs/cd/B28359_01/appdev.111/b28390/toc.htm

My goal with this "Getting Started" post is to give you one method of setting up an environment in which you can use OCCI to develop C++ applications under Linux that access Oracle Database. I am not in any way covering all possible scenarios or delving deep into OCCI itself. Please note that the database itself can be on any supported host.

The Environment

Your environment is likely to differ from mine; however, it is important to be familiar with the various components in the environment used here so that you can make adaptations as necessary for your specific environment.

  • Oracle Database Server/Host: oel01 (Oracle Enterprise Linux 32-bit server)
  • Oracle Database: SID value of OEL11GR1, Service Name value of OEL11GR1.SAND, version 11.1.0.7
  • Development Machine: Hostname of "gerrard", Oracle Enterprise Linux 32-bit (2.6.9 kernel)
  • Development IDE: VIM (any text editor or IDE you can configure should work)
  • Oracle Client: Oracle Instant Client with OCCI

Important Information

One of the most crucial attributes of working with OCCI is that you must ensure that all of the components of the development environment and the runtime environment are supported combinations and correct versions. I can not emphasize this enough. If you deviate from this, you will almost certainly find trouble! In order to find the correct combinations of products and versions, see the following links on OTN:

Download the Correct Packages

The Linux packages come in two varieties: a .zip file or a .rpm file. I have used the .zip files since I will be creating a private install under my user's home directory. The .rpm files require a user with the ability to install them (such as "root") and use directories such as "/usr/lib" and "/usr/bin". I want to keep this install as simple as possible and the .zip files work well for this.

From the download links above, you should download the following components to your development machine. I downloaded them to my home directory.

  • Instant Client Package – Basic Lite: instantclient-basiclite-linux32-11.1.0.7.zip
  • Instant Client Package - SDK: instantclient-sdk-linux32-11.1.0.7.zip
  • Instant Client Package - SQL*Plus: instantclient-sqlplus-linux32-11.1.0.7.zip  (optional, but I always install it)

NOTE: I am using the "Basic Lite" version of the main Instant Client packages. This is a smaller download and meets my needs; however, if you require character set or language support not provided by the "Basic Lite" version of Instant Client you should use the "basic" version instead. See the Instant Client home page (link above) for additional information.

Install the Instant Client Packages

Installing the Instant Client packages provided as .zip files is simply a matter of unzipping them – not much to go wrong here! I unzipped each of them (3 total) in my user's home directory on "gerrard". A new directory (instantclient_11_1) will be created and all the necessary files/directories will be under this single directory. This machine has no other Oracle software installed. Unzipping the files should result in the following:

[markwill@gerrard ~/instantclient_11_1]> find .
.
./libsqlplusic.so
./libclntsh.so.11.1
./ojdbc6.jar
./sqlplus
./adrci
./libnnz11.so
./SQLPLUS_README
./libociicus.so
./ojdbc5.jar
./sdk
./sdk/demo
./sdk/demo/occiobj.cpp
./sdk/demo/occidemo.sql
./sdk/demo/occiobj.typ
./sdk/demo/occidemod.sql
./sdk/demo/demo.mk
./sdk/demo/occidml.cpp
./sdk/demo/cdemo81.c
./sdk/SDK_README
./sdk/ott
./sdk/ottclasses.zip
./sdk/include
./sdk/include/orid.h
./sdk/include/occiAQ.h
./sdk/include/oratypes.h
./sdk/include/nzerror.h
./sdk/include/oci1.h
./sdk/include/ocikpr.h
./sdk/include/ocidef.h
./sdk/include/oci.h
./sdk/include/ort.h
./sdk/include/ocixmldb.h
./sdk/include/ocidfn.h
./sdk/include/odci.h
./sdk/include/ori.h
./sdk/include/occiCommon.h
./sdk/include/occi.h
./sdk/include/ociapr.h
./sdk/include/occiObjects.h
./sdk/include/occiControl.h
./sdk/include/xa.h
./sdk/include/ocidem.h
./sdk/include/oci8dp.h
./sdk/include/ociextp.h
./sdk/include/occiData.h
./sdk/include/ociap.h
./sdk/include/nzt.h
./sdk/include/orl.h
./sdk/include/oro.h
./libocci.so.11.1
./libocijdbc11.so
./genezi
./BASIC_LITE_README
./libsqlplus.so
./glogin.sql
[markwill@gerrard ~/instantclient_11_1]>


Create Links

To ensure proper operation of SQL*Plus and linking executables, two links should be created in the "instantclient_11_1" directory. The first is the Oracle Client Shared Library and the second is the OCCI Library:

ln -s ./libclntsh.so.11.1 ./libclntsh.so
ln -s ./libocci.so.11.1  ./libocci.so

After creating the links you should see the following when performing an "ls" on them:

[markwill@gerrard ~/instantclient_11_1]> ls -l libclntsh.so
lrwxrwxrwx  1 markwill markwill 19 Aug 15 19:17 libclntsh.so -> ./libclntsh.so.11.1

[markwill@gerrard ~/instantclient_11_1]> ls -l libocci.so
lrwxrwxrwx  1 markwill markwill 17 Aug 15 19:17 libocci.so -> ./libocci.so.11.1


Configure The Environment

To facilitate easily using the new installation I create a file I can source to set my environment correctly. I use the csh (well, tcsh really) as my primary shell and I created a file called "oic11.csh" in my home directory to setup the environment for me:

[markwill@gerrard ~]> cat oic11.csh
#
setenv ORACLE_BASE /home/markwill
setenv ORACLE_HOME ${ORACLE_BASE}/instantclient_11_1
setenv LD_LIBRARY_PATH ${ORACLE_HOME}
set path = (${ORACLE_HOME} /usr/local/bin /bin /usr/bin /usr/X11R6/bin ~/bin)

If you are using the bash shell, you may find something like the following helpful:

[markwill@gerrard ~]> cat oic11.env
export ORACLE_BASE=/home/markwill
export ORACLE_HOME=$ORACLE_BASE/instantclient_11_1
export LD_LIBRARY_PATH=$ORACLE_HOME
export PATH=$ORACLE_HOME:/usr/local/bin:/bin:/usr/bin:/usr/X11R6/bin:~/bin

I can then set my environment to use the new install as follows (in csh):

[markwill@gerrard ~]> source ./oic11.csh

This could be done as follows for bash:

[markwill@gerrard ~]> . ./oic11.env

Create a Simple Test Project

All the setup work is now complete and the environment is configured! If needed, you can use the following (very!) basic application as a simple test to verify things are working as expected. Again, this is a simple example only to verify things are setup correctly. It is not intended to be a complete template for "proper" code development, etc. Be sure to set the environment correctly!

I created a "Projects" directory under my home directory and then a directory called "Employees" under the "Projects" directory. I then used VIM to create the Employees.h and Employees.cpp files.

Here's the content of the Employees.h file on my system:

/*
* A simple OCCI test application
* This file contains the Employees class declaration
*/

#include <occi.h>
#include <iostream>
#include <iomanip>

using namespace oracle::occi;
using namespace std;

class Employees {
public:
  Employees();
  virtual ~Employees();

  void List();

private:
  Environment *env;
  Connection  *con;

  string user;
  string passwd;
  string db;
};

Here's the content of the Employees.cpp file on my system:

/*
* A simple OCCI test application
* This file contains the Employees class implementation
*/

#include "Employees.h"

using namespace std;
using namespace oracle::occi;

int main (void)
{
  /*
   * create an instance of the Employees class,
   * invoke the List member, delete the instance,
   * and prompt to continue...
   */

  Employees *pEmployees = new Employees();

  pEmployees->List();

  delete pEmployees;

  cout << "ENTER to continue...";

  cin.get();

  return 0;
}

Employees::Employees()
{
  /*
   * connect to the test database as the HR
   * sample user and use the EZCONNECT method
   * of specifying the connect string. Be sure
   * to adjust for your environment! The format
   * of the string is host:port/service_name

   */

  user = "hr";
  passwd = "hr";
  db = "oel01:1521/OEL11GR1.SAND";

  env = Environment::createEnvironment(Environment::DEFAULT);

  try
  {
    con = env->createConnection(user, passwd, db);
  }
  catch (SQLException& ex)
  {
    cout << ex.getMessage();

    exit(EXIT_FAILURE);
  }
}

Employees::~Employees()
{
  env->terminateConnection (con);

  Environment::terminateEnvironment (env);
}

void Employees::List()
{
  /*
   * simple test method to select data from
   * the employees table and display the results
   */

  Statement *stmt = NULL;
  ResultSet *rs = NULL;
  string sql = "select employee_id, first_name, last_name " \
               "from employees order by last_name, first_name";

  try
  {
    stmt = con->createStatement(sql);
  }
  catch (SQLException& ex)
  {
    cout << ex.getMessage();
  }

  if (stmt)
  {
    try
    {
      stmt->setPrefetchRowCount(32);

      rs = stmt->executeQuery();
    }
    catch (SQLException& ex)
    {
      cout << ex.getMessage();
    }

    if (rs)
    {
      cout << endl << setw(8) << left << "ID"
           << setw(22) << left << "FIRST NAME"
           << setw(27) << left << "LAST NAME"
           << endl;
      cout << setw(8) << left << "======"
           << setw(22) << left << "===================="
           << setw(27) << left << "========================="
           << endl;

      while (rs->next()) {
        cout << setw(8) << left << rs->getString(1)
             << setw(22) << left << (rs->isNull(2) ? "n/a" : rs->getString(2))
             << setw(27) << left << rs->getString(3)
             << endl;
      }

      cout << endl;

      stmt->closeResultSet(rs);
    }

    con->terminateStatement(stmt);
  }
}

To build the simple test I created a simplistic Makefile:

[markwill@gerrard Employees]> cat Makefile
Employees: Employees.cpp
  g++ -o Employees Employees.cpp \
  -I$(ORACLE_HOME)/sdk/include \
  -L$(ORACLE_HOME) -lclntsh -locci

debug: Employees.cpp
  g++ -ggdb3 -o Employees Employees.cpp \
  -I$(ORACLE_HOME)/sdk/include \
  -L$(ORACLE_HOME) -lclntsh -locci

clean:
  rm -f Employees

NOTE: The indented lines are tabs and not spaces in the Makefile

Whilst certainly not destined to win any awards for Makefile creativity it suffices for the purpose at hand.

I then built the application in debug mode by typing "make debug".

Executing the sample should result in output as follows:

[markwill@gerrard Employees]> ./Employees

ID      FIRST NAME            LAST NAME
======  ====================  =========================
174     Ellen                 Abel
166     Sundar                Ande
130     Mozhe                 Atkinson
105     David                 Austin
204     Hermann               Baer
116     Shelli                Baida
167     Amit                  Banda
172     Elizabeth             Bates

[ snip ]

120     Matthew               Weiss
200     Jennifer              Whalen
149     Eleni                 Zlotkey

ENTER to continue...

If you are new to using OCCI on Linux, perhaps the above will be helpful in getting started!