Oracle Call Interface Programmer's Guide
Release 8.1.5

A67846-01

Library

Product

Contents

Index

Prev Next

9
OCI Programming Advanced Topics

This chapter introduces advanced programming topics, including the following:

Overview

Chapter 2, "OCI Programming Basics" introduced the basic concepts of OCI programming. This chapter is designed to introduce more advanced concepts, including the following:

The OCIThread Package

This section describes the OCIThread package which provides a number of commonly used threading primitives for use by Oracle customers and offers a portable interface to threading capabilities native to various platforms.

User-defined Callback Functions

This section describes the OCI user callback feature.

Application Failover Callbacks

This section discusses how to write and use application failover callback functions.

OCI and Advanced Queuing

This section covers the OCI functions related to Oracle's Advanced Queuing feature.

Publish-Subscribe Notification

This section discusses how to register and receive notifications for events.

Direct Path Loading

This section discusses how to access the direct block formatter of the Oracle database server to load data from external files into an Oracle table or a partition of a partitioned table.

The OCIThread Package

The OCIThread package provides a number of commonly used threading primitives for use by Oracle customers. It offers a portable interface to threading capabilities native to various platforms. It does not implement threading on platforms which do not have native threading capability.

OCIThread does not provide a portable implementation of multi-threaded facilities. It only serves as a set of portable covers for native multi-threaded facilities. Therefore, platforms that do not have native support for multi-threading will only be able to support a limited implementation of OCIThread. As a result, products that rely on all of OCIThread's functionality will not port to all platforms. Products that must port to all platforms must use only a subset of OCIThread's functionality. This issue is discussed further in later sections of this document.

The OCIThread API is split into three main parts. Each part is described briefly here. The following subsections describe each in greater detail. See "Using the OCIThread Package" for important additional information.

Note: Detailed descriptions of OCIThread functions, including syntax, parameters lists, and other comments can be found in Chapter 15, "OCI Relational Functions".

Initialization and Termination

The types and functions described in this section are associated with the initialization and termination of the OCIThread package. OCIThread must be properly initialized before any of its functionality can be used. OCIThread's process initialization function, OCIThreadProcessInit(), must be called with care, as described below.

The observed behavior of the initialization and termination functions is the same regardless of whether OCIThread is in single-threaded or multi-threaded environment. You can call the initialization functions from both generic and operating system specific (OSD) code.

OCIThread Context

Most calls to OCIThread functions take the OCI environment or user session handle as a parameter. The OCIThread context is part of the OCI environment or user session handle and it must be initialized by calling OCIThreadInit(). Termination of the OCIThread context occurs by calling OCIThreadTerm().

Note: The OCIThread context is an opaque data structure. Do not attempt to examine the contents of the context.

The following functions are used to implement thread initialization and termination. Detailed descriptions of each function can be found in Chapter 15, "OCI Relational Functions".

Function   Purpose  

OCIThreadProcessInit()  

Performs OCIThread process initialization.  

OCIThreadInit()  

Initializes OCIThread context.  

OCIThreadTerm()  

Terminates the OCIThread layer and frees context memory.  

OCIThreadIsMulti()  

Tells the caller whether the application is running in a multi-threaded environment or a single-threaded environment.  

Passive Threading Primitives

The passive threading primitives deal with the manipulation of mutex, thread ID's, and thread-specific data. Since the specifications of these primitives do not require the existence of multiple threads, they can be used both on multi-threaded and single-threaded platforms.

OCIThreadMutex

The type OCIThreadMutex is used to represent a mutual exclusion lock (mutex). A mutex is typically used for one of two purposes:

Mutex pointers can be declared as parts of client structures or as stand-alone variables. Before they can be used, they must be initialized using OCIThreadMutexInit(). Once they are no longer needed, they must be destroyed using OCIThreadMutexDestroy(). A mutex pointer must not be used after it is destroyed.

A thread can acquire a mutex by using OCIThreadMutexAcquire(). This ensures that only one thread at a time is allowed to hold a given mutex. A thread that holds a mutex can release it by calling OCIThreadMutexRelease().

OCIThreadKey

The type OCIThreadKey can be thought of as a process-wide variable that has a thread-specific value. What this means is that all the threads in a process can use any given key. However, each thread can examine or modify that key independently of the other threads. The value that a thread sees when it examines the key will always be the same as the value that it last set for the key. It will not see any values set for the key by the other threads.

The type of the value held by a key is a dvoid * generic pointer.

Keys can be created using OCIThreadKeyInit(). When a key is created, its value is initialized to NULL for all threads.

A thread can set a key's value using OCIThreadKeySet(). A thread can get a key's value using OCIThreadKeyGet().

The OCIThread key functions will save and retrieve data specific to the thread. When clients maintain a pool of threads and assign the threads to different tasks, it may not be appropriate for a task to use OCIThread key functions to save data associated with it. Here is a scenario of how things can fail: A thread is assigned to execute the initialization of a task. During the initialization, the task stored some data related to it in the thread using OCIThread key functions. After the initialization, the thread is returned back to the threads pool. Later, the threads pool manager assigned another thread to perform some operations on the task, and the task needs to retrieve the data it stored earlier in initialization. Since the task is running in another thread, it will not be able to retrieve the same data. Applications that use thread pools should be aware of this and be cautious when using OCIThread key functions.

OCIThreadKeyDestFunc

OCIThreadKeyDestFunc is the type of a pointer to a key's destructor routine. Keys can be associated with a destructor routine when they are created (see OCIThreadKeyInit()).

A key's destructor routine will be called whenever a thread that has a non-NULL value for the key terminates.

The destructor routine returns nothing and takes one parameter. The parameter will be the value that was set for key when the thread terminated.

The destructor routine is guaranteed to be called on a thread's value in the key after the termination of the thread and before process termination. No more precise guarantee can be made about the timing of the destructor routine call; thus no code in the process may assume any post-condition of the destructor routine. In particular, the destructor is not guaranteed to execute before a join call on the terminated thread returns.

OCIThreadId

OCIThreadId is the type that will be used to identify a thread. At any given time, no two threads will ever have the same OCIThreadId. However, OCIThreadId values can be recycled; i.e., once a thread dies, a new thread may be created that has the same OCIThreadId as the one that died. In particular, the thread ID must uniquely identify a thread T within a process, and it must be consistent and valid in all threads U of the process for which it can be guaranteed that T is running concurrently with U. The thread ID for a thread T must be retrievable within thread T. This will be done via OCIThreadIdGet().

The OCIThreadId type supports the concept of a NULL thread ID: the NULL thread ID will never be the same as the ID of an actual thread.

Passive Threading Functions

The following functions are used to manipulate mutexes, thread keys and thread IDs. Complete descriptions of each function can be found in Chapter 15, "OCI Relational Functions".

Function   Purpose  

OCIThreadMutexInit()  

Allocates and initializes a mutex.  

OCIThreadMutexDestroy()  

Destroys and deallocates a mutex.  

OCIThreadMutexAcquire()  

Acquires a mutex for the thread in which it is called.  

OCIThreadMutexRelease()  

Releases a mutex.  

OCIThreadKeyInit()  

Allocates and initializes a key.  

OCIThreadKeyDestroy()  

Destroys and deallocates a key.  

OCIThreadKeyGet()  

Gets the calling thread's current value for a key.  

OCIThreadKeySet()  

Sets the calling thread's value for a key.  

OCIThreadIdInit()  

Allocates and initializes a thread ID.  

OCIThreadIdDestroy()  

Destroys and deallocates a thread ID.  

OCIThreadIdSet()  

Sets on thread ID to another.  

OCIThreadIdSetNull()  

Nulls a thread ID.  

OCIThreadIdGet()  

Retrieves a thread ID for the thread in which it is called.  

OCIThreadIdSame()  

Determines if two thread IDs represent the same thread.  

OCIThreadIdNull()  

Determines if a thread ID is NULL.  

Active Threading Primitives

The active threading primitives deal with the manipulation of actual threads. Because the specifications of most of these primitives require that it be possible to have multiple threads, they work correctly only in the enabled OCIThread; In the disabled OCIThread, they always return failure. The exception is OCIThreadHandleGet(); it may be called in a single-threaded environment, in which case it has no effect.

Active primitives should only be called by code running in a multi-threaded environment. You can call OCIThreadIsMulti() to determine whether the environment is multi-threaded or single-threaded.

OCIThreadHandle

Type OCIThreadHandle is used to manipulate a thread in the active primitives: OCIThreadJoin() and OCIThreadClose(). A thread handle opened by OCIThreadCreate() must be closed in a matching call to OCIThreadClose(). A thread handle is invalid after the call to OCIThreadClose().

The distinction between a thread ID and a thread handle in OCIThread usage follows the distinction between the thread ID and the thread handle on Windows NT. On many platforms, the underlying native types are the same.

Active Threading Functions

The following functions are used to implement active threading. Complete descriptions of the functions are available in Chapter 15, "OCI Relational Functions".

Function   Purpose  

OCIThreadHndInit()  

Allocates and initializes a thread handle.  

OCIThreadHndDestroy()  

Destroys and deallocates a thread handle.  

OCIThreadCreate()  

Creates a new thread.  

OCIThreadJoin()  

Allows the calling thread to join with another.  

OCIThreadClose()  

Closes a thread handle.  

OCIThreadHandleGet()  

Retrieves a thread handle.  

Using the OCIThread Package

This section summarizes some of the more important details relating to the use of OCIThread.

Process initialization

OCIThread only requires that the process initialization function (OCIThreadProcessInit()) be called when OCIThread is being used in a multi-threaded application. Failing to call OCIThreadProcessInit() in a single-threaded application is not an error.

OCIThread initialization

Separate calls to OCIThreadInit() will all return the same OCIThread context.

Also, remember that each call to OCIThreadInit() must eventually be matched by a call to OCIThreadTerm().

Active versus Passive Threading primitives

OCIThread client code written without using any active primitives can be compiled and used without modifications on both single-threaded and multi-threaded platforms.

OCIThread client code written using active primitives will only work correctly on multi-threaded platforms. In order to write a version of the same application to run on single-threaded platform, it is necessary to branch the your code, whether by branching versions of the source file or by branching at runtime with the OCIThreadIsMulti() call.

Example using OCIThread

The following code sample illustrates the use of OCIThread. For a listing of the complete demonstration programs, see Appendix B, "OCI Demonstration Programs".

static OCIEnv *envhp; 
static OCIError *errhp; 
void parent(argc, argv) 
sb4 argc; 
text **argv;
{
  OCIThreadId *tidArr[5];  
  OCIThreadHandle *tHndArr[5]; 
  ub4 i;  
  OCIThreadKey *key;  
  (void) OCIInitialize((ub4) OCI_DEFAULT, (dvoid *)0,  
                       (dvoid * (*)(dvoid *, size_t)) 0,  
                       (dvoid * (*)(dvoid *, dvoid *, size_t))0,
                       (void (*)(dvoid *, dvoid *)) 0 ); 
  (void) OCIEnvInit( (OCIEnv **) &envhp, OCI_DEFAULT, (size_t) 0,
                     (dvoid **) 0 ); 
   (void) OCIHandleAlloc( (dvoid *) envhp, (dvoid **) &errhp,  
                         OCI_HTYPE_ERROR, (size_t) 0, (dvoid **) 0); 
   OCIThreadProcessInit(); 
  OCIThreadInit(envhp, errhp); 
  OCIThreadKeyInit(envhp, errhp, &key, (OCIThreadKeyDestFunc) NULL); 
  for (i=0; i<5; i++)   
    {
      OCIThreadIdInit(envhp, errhp, &(tidArr[i])); 
      OCIThreadHndInit(envhp, errhp, &(tHndArr[i])); 
    } 
   for (i=0; i<5; i++) 
    OCIThreadCreate(envhp, errhp, child, (dvoid *)key,  
                            tidArr[i], tHndArr[i]); 
  for (i=0; i<5; i++) 
    { 
      OCIThreadJoin(envhp, errhp, tHndArr[i]);  
      OCIThreadClose(envhp, errhp, tHndArr[i]); 
    } 
  for (i=0; i<5; i++)   
    { 
       OCIThreadIdDestroy(envhp, errhp, &(tidArr[i])); 
       OCIThreadHndDestroy(envhp, errhp, &(tHndArr[i])); 
    } 
   OCIThreadKeyDestroy(envhp, errhp, &key); 
  OCIThreadTerm(envhp, errhp);  
} 
void child(arg) 
dvoid *arg; 
{ 
  OCIThreadKey *key = (OCIThreadKey *)arg; 
  OCIThreadId *tid; 
  dvoid *keyval; 
  OCIThreadIdInit(envhp, errhp, &tid); 
  OCIThreadIdGet(envhp, errhp, tid); 
   if (OCIThreadKeySet(envhp, errhp, key, (dvoid *)tid) != OCI_SUCCESS) 
     printf("Could not set value for key\n"); 
   if (OCIThreadKeyGet(envhp, errhp, key, &keyval) !=OCI_SUCCESS) 
     printf("Could not retrieve value for key\n"); 
   if (keyval != (dvoid *)tid) 
    printf("Incorrect value from key after setting it\n"); 
  /* we must destroy thread id */ 
  OCIThreadIdDestroy(envhp, errhp, &tid); 
}

User-defined Callback Functions

The Oracle Call Interface has the capability to execute user-specific code in addition to OCI calls. This functionality can be used for:

The OCI callback feature has been added by providing support for calling user code before or after executing the OCI calls. Functionality has also been provided to allow the user-defined code to be executed instead of executing the OCI code.

The user callback code can also be registered dynamically without modifying the source code of the application. The dynamic registration is implemented by loading a user-created dynamically linked library, such as a DLL or shared library, after the initialization of the environment handle during the OCIEnvInit() or OCIEnvCreate() calls. The user-created DLL registers the user callbacks for the selected OCI calls transparently to the application.

Sample Application

For a listing of the complete demonstration programs that illustrate the OCI user callback feature, see Appendix B, "OCI Demonstration Programs".

Registering User Callbacks

An application can register a user callback with the OCIUserCallbackRegister() function. Callbacks are registered in the context of the environment handle. An application can retrieve information about callbacks registered with a handle with the OCIUserCallbackGet() function. For detailed descriptions of these functions and their parameters, refer to the descriptions of OCIUserCallbackGet() and OCIUserCallbackRegister() in Chapter 15, "OCI Relational Functions".

A user-defined callback is a subroutine that is registered against an OCI call and an environment handle. It can be specified to be either an entry callback or an exit callback.

If the entry callback returns OCI_CONTINUE, then the program transfers control to the normal OCI code for that call. However, if the entry callback returns anything other than OCI_CONTINUE, such as OCI_SUCCESS or OCI_ERROR, then the OCI code is bypassed and control is passed to the exit callback if one is registered. This effectively means that the entry callback has replaced the OCI code for that call.

A user callback can return OCI_INVALID_HANDLE when either an invalid handle or an invalid context is passed to it.

Note: If no exit callback is registered and the entry callback returns something other than OCI_CONTINUE, then the return code from the entry callback is returned from the associated OCI call. Similarly, if the exit callback returns anything other than OCI_CONTINUE, then that return code is returned by the OCI call.

OCIUserCallbackRegister

As user callback is registered using the OCIUserCallbackRegister() call. See OCIUserCallbackRegister() for the syntax of this call. Currently, OCIUserCallbackRegister() is only registered on the environment handle. The user's callback function of typedef OCIUserCallback is registered along with its context for the OCI call identified by the OCI function code, fcode. The type of the callback, whether entry or exit, is specified by the when parameter.

For example, the stmtprep_entry_dyncbk_fn entry callback function and its context dynamic_context, are registered against the environment handle hndlp for the OCIStmtPrepare() call by calling the OCIUserCallbackRegister() function with the following parameters.

OCIUserCallbackRegister(hndlp, OCI_HTYPE_ENV, err, 
                        stmtprep_entry_dyncbk_fn, 
                        dynamic_context, 
                        OCI_FNCODE_STMTPREPARE,
                        OCI_UCBTYPE_ENTRY);

User Callback Function

The user callback function has to follow the following syntax:

typedef sword (*OCIUserCallback)
(dvoid *ctxp,      /* context for the user callback*/
 dvoid *hndlp,     /* handle for the callback, env handle for now */
 ub4 type,         /* type of handlp, OCI_HTYPE_ENV for this release */
 ub4 fcode,        /* function code of the OCI call */
 ub1 when,         /* type of the callback, entry or exit */
 sword returnCode, /* OCI return code */
 ub4 *errnop,      /* Oracle error number */
 va_list arglist); /* parameters of the oci call */

In addition to the parameters described in the OCIUserCallbackRegister() call, the callback is called with the return code, errnop, and all the parameters of the original OCI as declared by the prototype definition.

The return code is always passed in as OCI_SUCCESS and *errnop is always passed in as 0 for the entry callback. Note that *errnop refers to the content of errnop because errnop is an IN/OUT parameter.

If the callback is not a replacement for the OCI code, then it must return OCI_CONTINUE, and the value returned in *errnop is ignored. If on the other hand, the callback returns any other return code than OCI_CONTINUE, then the OCI code is bypassed, and the returned return code becomes the return code for the call. At the this point, the value of *errnop returned is set in the error handle, or in the environment handle if the error information is returned in the environment handle because of the absence of the error handle for certain OCI calls such as OCIHandleAlloc().

For the exit callback, the returnCode is the return code that the OCI call was going to return had the exit callback not been called and *errnop is the value of the error number being returned in the error handle. This allows the exit callback to change the return code or error information if needed.

All the original parameters of the OCI call are passed to the callback as variable parameters and the callback must retrieve them using the va_arg macros. The callback demonstration programs provide examples. See Appendix B, "OCI Demonstration Programs" for a list of available demos.

A null value can be registered to de-register a callback. That is, if the value of the callback (OCIUserCallback) is NULL in the OCIUserCallbackRegister() call, then the user callback is de-registered.

When using the thread-safe mode, the OCI program acquires all mutexes before calling the user callbacks.

UserCallback Control Flow

The pseudo code below describes the overall processing of a typical OCI call.

OCIXyzCall()
{
 Acquire mutexes on handles;
 if (ENTRY callback registered)
  {
     errno = 0;
     retCode = (*entryCallback)(..., OCI_SUCCESS, &errno, ...);
     if (retCode != OCI_CONTINUE)
      {
         set errno in error handle or env handle;
         goto executeExitCallback;
       }
   }
   retCode = return code for XyzCall; /* normal processing of OCI call */

 executeExitCallback:
   if (EXIT callback registered)
   {
       errno = error number from error handle or env handle;
       exitRetCode = (*exitCallback)(..., retCode, &errno,...);
       if (exitRetCode != OCI_CONTINUE)
       {
           set errno in error handle or environment handle;
           retCode = exitRetCode;
        }
    }
    release mutexes;
    return retCode;
  }

UserCallback for OCIErrorGet

If the callbacks are a total replacement of the OCI code, then they would usually maintain their own error information in the call context and would use that to return error information in bufp and errnop parameters of the entry callback of the OCIErrorGet() call.

If on the other hand, the callbacks are either partially overriding OCI code, or just doing some other post processing, then they can use the exit callback to modify the error text and errnop parameters of the OCIErrorGet() by their own error message and error number. Note that the *errnop passed into the exit callback is the error number in the error or the environment handle.

Dynamic Callback Registrations

Because user callbacks are expected to be used for monitoring OCI behavior or to access other data sources, it is desirable that the registration of the callbacks be done transparently and non-intrusively. This is accomplished by loading a user-created dynamically linked library (DLL) at OCI initialization time. The user-created DLL registers the user callbacks for the selected OCI calls. These callbacks can further register or de-register user callbacks as needed when receiving control at runtime.

A makefile (ociucb.mk) is provided with the OCI demonstration programs to create the dynamic-linked library (DLL). The exact naming and location of this dynamically linked library is operating system dependent. The source code for the DLL must provide code for a special callback called the OCIEnvCallback().

The loading of the DLL is controlled by setting an operating system environment variable ORA_OCI_UCBPKG. This variable names the DLL in a generic way. The DLL must be located in the $ORACLE_HOME/lib directory. For example, if ociucb.mk creates ociucb.so.1.0 on a Solaris system or ociudb.dll on an NT system, then ORA_OCI_UCBPKG must be set to ociucb.

The prototype of the OCIEnvCallback() is as follows:

sword OCIEnvCallback(
 OCIEnv *env,         /* environment handle being created */
 ub4 mode,            /* mode passed to the OCIEnvCreate call */
 size_t xtramemsz,    /* extra memory size in the OCIEnvInit call*/
 dvoid *usrmemp );    /* extra memory allocated in OCIEnvInit */

OCIUserCallbackRegister() and OCIUserCallbackGet() can be called in the body of OCIEnvCallback() function to register all the necessary user callbacks. No other functions can be called in the OCIEnvCallback function and OCIEnvCallback must return OCI_CONTINUE. Note that because an error handle is not available within OCIEnvCallback, the environment handle must be passed as the parameter ehndlp to retrieve information.

The OCIEnvCallback() function is called internally by OCI at the very end of the OCIEnvInit() or OCIEnvCreate() calls if the ORA_OCI_UCBPKG operating system environment variable is set to the name of the DLL. For example, it can be set as:

setenv ORA_OCI_UCBPKG ociucb

The setting and unsetting of this operating-system environment variable controls whether or not the DLL would be loaded. An application has the option of further disabling the call to OCIEnvCallback() by invoking OCIEnvInit() with a mode value of OCI_ENV_NO_UCB or OCIEnvCreate() with a mode value of OCI_NO_UCB. If OCIEnvInit() or OCIEnvCreate() is called with this mode, then OCIEnvCallback() is not called even if the ORA_OCI_UCBPKG environment variable is set. However, the default is to allow loading of OCIEnvCallback().

User Callback Chaining

User callbacks can both be registered statically in the application itself or dynamically at runtime in the DLL. A mechanism is needed to allow the application to override a previously registered callback and then later invoke the overridden one in the newly registered callback to preserve the behavior intended by the dynamic registrations. This can result in chaining of user callbacks.

For this purpose, the OCIUserCallbackGet() function is provided to find out which function and context is registered for an OCI call. See OCIUserCallbackGet() for the syntax of this call.

For example, the OCIEnvCallback() registers a callback named stmtprep_entry_dyncbk_fn for OCIStmtPrepare(). The application itself would like to register the callback function stmtprep_entry_statcbk_fn for the same call. The application can call the OCIUserCallbackGet() function to find out which callback is registered for OCIStmtPrepare(). It can then save the dynamic callback function and the dynamic context in the static context and override the callback by registering the stmtprep_entry_statcbk_fn as the entry callback for OCIStmtPrepare(). When the control passes to the statically registered callback, it can invoke the dynamic callback either before or after its own code.

Accessing Other Data Sources Through OCI

Because Oracle is the predominant database accessed, applications can take advantage of the OCI interface to access non-Oracle data by using the user callbacks to access them. This allows an application written in OCI to access Oracle data without any performance penalty. To access non-Oracle data sources, drivers can be written that would access the non-Oracle data in user callbacks. Because OCI provides a very rich interface, there is usually a straight forward mapping of OCI calls to most data sources. This solution is better than writing applications for other middle layers such as ODBC which introduce performance penalty for all data sources. Using OCI would not incur any penalty for the common case of accessing Oracle data sources, and would incur the same penalty that ODBC does for non-Oracle data sources.

Restrictions on Callback Functions

There are certain restrictions on the usage of callback functions, including OCIEnvCallback:

OCI Callbacks From External Procedures

There are several OCI functions that can be used as callbacks from external procedures. These functions are listed in Chapter 18, "OCI External Procedure Functions". For information about writing C subroutines that can be called from PL/SQL code, including a list of which OCI calls can be used, and some example code, refer to the Oracle8i Application Developer's Guide - Fundamentals.

Application Failover Callbacks

Application failover callbacks can be used in the event of the failure of one database instance, and failover to another instance. Because of the delay which can occur during failover, the application developer may want to inform the user that failover is in progress, and request that the user stand by. Additionally, the session on the initial instance may have received some ALTER SESSION commands. These will not be automatically replayed on the second instance. Consequently, the developer may wish to replay these ALTER SESSION commands on the second instance.

See Also: For more detailed information about application failover, refer to the Oracle8i Parallel Server Concepts and Administration manual.

Failover Callback Overview

To address the problems described above, the application developer can register a failover callback function. In the event of failover, the callback function is invoked several times during the course of reestablishing the user's session.

The first call to the callback function occurs when Oracle first detects an instance connection loss. This callback is intended to allow the application to inform the user of an upcoming delay. If failover is successful, a second call to the callback function occurs when the connection is reestablished and usable. At this time the client may wish to replay ALTER SESSION commands and inform the user that failover has happened. If failover is unsuccessful, then the callback is called to inform the application that failover will not take place. Additionally, the callback is called each time a user handle besides the primary handle is reauthenticated on the new connection. Since each user handle represents a server-side session, the client may wish to replay ALTER SESSION commands for that session.

An initial attempt at failover may not always successful. The OCI provides a mechanism for retrying failover after an unsuccessful attempt. See "Handling OCI_FO_ERROR" for more information about this scenario.

Failover Callback Structure and Parameters

The basic structure of a user-defined application failover callback function is as follows:

sb4 appfocallback_fn ( dvoid      * svchp, 
                       dvoid      * envhp, 
                       dvoid      * fo_ctx, 
                       ub4        fo_type, 
                       ub4        fo_event );

An example is provided in the section "Failover Callback Example" for the following parameters:

svchp

The first parameter, svchp, is the service context handle. It is of type dvoid *.

envhp

The second parameter, envhp, is the OCI environment handle. It is of type dvoid *.

fo_ctx

The third parameter, fo_ctx, is a client context. It is a pointer to memory specified by the client. In this area the client can keep any necessary state or context. It is passed as a dvoid *.

fo_type

The fourth parameter, fo_type, is the failover type. This lets the callback know what type of failover the client has requested. The usual values are:

fo_event

The last parameter is the failover event. This indicates to the callback why it is being called. It has several possible values:

Failover Callback Registration

For the failover callback to be used, it must be registered on the server context handle. This registration is done by creating a callback definition structure and setting the OCI_ATTR_FOCBK attribute of the server handle to this structure.

The callback definition structure must be of type OCIFocbkStruct. It has two fields: callback_function, which contains the address of the function to call, and fo_ctx which contains the address of the client context.

An example of callback registration is included as part of the example in the next section.

Failover Callback Example

The following code shows an example of a simple user-defined callback function definition and registration.

Part 1, Failover Callback Definition

sb4  callback_fn(svchp, envhp, fo_ctx, fo_type, fo_event )
dvoid * svchp;
dvoid * envhp;
dvoid *fo_ctx;
ub4 fo_type;
ub4 fo_event;
{
switch (fo_event) 
   {
   case OCI_FO_BEGIN:
   {
     printf(" Failing Over ... Please stand by \n");
     printf(" Failover type was found to be %s \n",
                     ((fo_type==OCI_FO_SESSION) ? "SESSION" 
                     :(fo_type==OCI_FO_SELECT) ? "SELECT"
                     : "UNKNOWN!")); 
     printf(" Failover Context is :%s\n", 
                    (fo_ctx?(char *)fo_ctx:"NULL POINTER!"));
     break;
   }
   case OCI_FO_ABORT:
   {
     printf(" Failover aborted. Failover will not take place.\n");
     break;
   }
   case    OCI_FO_END:
   {
       printf(" Failover ended ...resuming services\n");
     break;
   }
   case OCI_FO_REAUTH:
   {
       printf(" Failed over user. Resuming services\n");
     break;
   }
   default:
   {
     printf("Bad Failover Event: %d.\n",  fo_event);
     break;
   }
   }
   return 0;
}

Part 2, Failover Callback Registration

int register_callback(svrh, errh)
dvoid *svrh; /* the server handle */
OCIError *errh; /* the error handle */
{
  OCIFocbkStruct failover;                 /*  failover callback structure */
  /* allocate memory for context */
  if (!(failover.fo_ctx = (dvoid *)malloc(strlen("my context."))))
    return(1);
  /* initialize the context. */
  strcpy((char *)failover.context_function, "my context.");
  failover.callback_function = &callback_fn;
  /* do the registration */
  if (OCIAttrSet(srvh, (ub4) OCI_HTYPE_SRV,
                (dvoid *) &failover, (ub4) 0,
                (ub4) OCI_ATTR_FOCBK, errh)  != OCI_SUCCESS)
    return(2); 
  /* successful conclusion */
  return (0);
}

Handling OCI_FO_ERROR

A failover attempt is not always successful. If the attempt fails, the callback function receives a value of OCI_FO_ABORT or OCI_FO_ERROR in the fo_event parameter. A value of OCI_FO_ABORT indicates that failover was unsuccessful, and no further failover attempts are possible. OCI_FO_ERROR, on the other hand, provides the callback function with the opportunity to handle the error in some way. For example, the callback may choose to wait a specified period of time and then indicate to the OCI library that it should reattempt failover.

Note: This functionality is only available to applications linked with the 8.0.5 or later OCI libraries running against any Oracle8i server.

Consider the following timeline of events:

Time   Event  

T0  

Database crashes (crash lasts until T5).  

T1  

Failover triggered by user activity.  

T2  

User attempts to reconnect; attempt fails.  

T3  

Failover callback invoked with OCI_FO_ERROR.  

T4  

Failover callback enters predetermined sleep period.  

T5  

Database comes back up again.  

T6  

Failover callback triggers new failover attempt; it is successful.  

T7  

User successfully reconnects  

The callback function triggers the new failover attempt by returning a value of OCI_FO_RETRY from the function.

The following example code shows a callback function which might be used to implement the failover strategy similar to the scenario described above. In this case the failover callback enters a loop in which it sleeps and then reattempts failover until it is successful:

/*--------------------------------------------------------------------*/
/* the user defined failover callback  */
/*--------------------------------------------------------------------*/
sb4  callback_fn(svchp, envhp, fo_ctx, fo_type, fo_event )
dvoid * svchp;
dvoid * envhp;
dvoid *fo_ctx;
ub4 fo_type;
ub4 fo_event;
{
   OCIError *errhp;
   OCIHandleAlloc(envhp, (dvoid **)&errhp, (ub4) OCI_HTYPE_ERROR,
              (size_t) 0, (dvoid **) 0);
   switch (fo_event) 
   {
   case OCI_FO_BEGIN:
   {
     printf(" Failing Over ... Please stand by \n");
     printf(" Failover type was found to be %s \n",
            ((fo_type==OCI_FO_NONE) ? "NONE"
             :(fo_type==OCI_FO_SESSION) ? "SESSION" 
             :(fo_type==OCI_FO_SELECT) ? "SELECT"
             :(fo_type==OCI_FO_TXNAL) ? "TRANSACTION"
             : "UNKNOWN!")); 
     printf(" Failover Context is :%s\n", 
            (fo_ctx?(char *)fo_ctx:"NULL POINTER!"));
     break;
   }
   case OCI_FO_ABORT:
   {
     printf(" Failover aborted. Failover will not take place.\n");
     break;
   }
   case    OCI_FO_END:
   { 
       printf("\n Failover ended ...resuming services\n");
     break;
   }
   case OCI_FO_REAUTH:
   { 
       printf(" Failed over user. Resuming services\n");
     break;
   }
   case OCI_FO_ERROR:
   {
     /* all invocations of this can only generate one line. The newline
      * will be put at fo_end time.
      */
     printf(" Failover error gotten. Sleeping...");
     sleep(3);
     printf("Retrying. ");
     return (OCI_FO_RETRY);
     break;
   }
   default:
   {
     printf("Bad Failover Event: %d.\n",  fo_event);
     break;
   }
   }
   return 0;
}

The following is sample output from a program containing this failover callback function:

executing select...
7369    SMITH    CLERK
7499    ALLEN    SALESMAN
 Failing Over ... Please stand by 
 Failover type was found to be SELECT 
 Failover Context is :My context.
 Failover error gotten. Sleeping...Retrying.  Failover error gotten. 
Sleeping...Retrying.  Failover error gotten. Sleeping...Retrying.  Failover 
error gotten. Sleeping...Retrying.  Failover error gotten. Sleeping...Retrying.  
Failover error gotten. Sleeping...Retrying.  Failover error gotten. 
Sleeping...Retrying.  Failover error gotten. Sleeping...Retrying.  Failover 
error gotten. Sleeping...Retrying.  Failover error gotten. Sleeping...Retrying. 
 Failover ended ...resuming services
7521    WARD    SALESMAN
7566    JONES    MANAGER
7654    MARTIN    SALESMAN
7698    BLAKE    MANAGER
7782    CLARK    MANAGER
7788    SCOTT    ANALYST
7839    KING    PRESIDENT
7844    TURNER    SALESMAN
7876    ADAMS    CLERK
7900    JAMES    CLERK
7902    FORD    ANALYST

OCI and Advanced Queuing

The OCI provides an interface to Oracle's Advanced Queuing feature. Oracle AQ provides message queuing as an integrated part of the Oracle server. Oracle AQ provides this functionality by integrating the queuing system with the database, thereby creating a message-enabled database. By providing an integrated solution Oracle AQ frees application developers to devote their efforts to their specific business logic rather than having to construct a messaging infrastructure.

Note: In order to use advanced queuing, you must be using the Oracle8i Enterprise Edition.

See Also: For detailed information about AQ, including concepts, features, and examples, refer to the chapter on Advanced Queuing in the Oracle8i Application Developer's Guide - Advanced Queuing.

For example code demonstrating the use of the OCI with AQ, refer to the description of OCIAQEnq().

OCI Advanced Queuing Functions

The OCI library includes several functions related to advanced queuing:

Chapter 15, "OCI Relational Functions", contains complete descriptions of these functions and their parameters.

OCI Advanced Queuing Descriptors

The following descriptors are used by OCI AQ operations:

You can allocate these descriptors with respect to the service handle using the standard OCIDescriptorAlloc() call. The following code shows examples of this:

OCIDescriptorAlloc(svch, &enqueue_options, OCI_DTYPE_AQENQ_OPTIONS, 0, 0 ); 
OCIDescriptorAlloc(svch, &dequeue_options, OCI_DTYPE_AQDEQ_OPTIONS, 0, 0 ); 
OCIDescriptorAlloc(svch, &message_properties, OCI_DTYPE_AQMSG_PROPERTIES, 0, 0);
OCIDescriptorAlloc(svch, &agent, OCI_DTYPE_AQAGENT, 0, 0 ); 

As with other OCI descriptors, the structure of these descriptors is opaque to the user. Each descriptor has a variety of attributes which can be set and/or read. These attributes are described in more detail in "Advanced Queueing Descriptor Attributes".

Advanced Queuing in OCI vs. PL/SQL

The following tables compare functions, parameters, and options for OCI AQ functions and descriptors, and PL/SQL AQ functions in the dbms_aq package.

PL/SQL Function   OCI Function  

DBMS_AQ.ENQUEUE  

OCIAQEnq()  

DBMS_AQ.DEQUEUE  

OCIAQDeq()  

DBMS_AQ.LISTEN  

OCIAQListen()  

DBMS_AQ.ENQUEUE Parameter   OCIAQEnq() Parameter  

queue_name  

queue_name  

enqueue_options  

enqueue_options  

message_properties  

message_properties  

payload  

payload  

msgid  

msgid  

Note: OCIAQEnq() also requires the following additional parameters: svch, errh, payload_tdo, payload_ind, and flags  

DBMS_AQ.DEQUEUE Parameter   OCIAQDeq() Parameter  

queue_name  

queue_name  

dequeue_options  

dequeue_options  

message_properties  

message_properties  

payload  

payload  

msgid  

msgid  

Note: OCIAQDeq() also requires the following additional parameters: svch, errh, payload_tdo, payload_ind, and flags  

DBMS_AQ.LISTEN Parameter   OCIAQListen() Parameter  

agent_list  

agent_list  

wait  

wait  

agent  

agent  

Note: OCIAQListen() also requires the following additional parameters: svchp, errhp, num_agents, and flags  

PL/SQL Agent Parameter   OCIAQAgent Attribute  

name  

OCI_ATTR_AGENT_NAME  

address  

OCI_ATTR_AGENT_ADDRESS  

protocol  

OCI_ATTR_AGENT_PROTOCOL  

PL/SQL Message Property   OCIAQMsgProperties Attribute  

priority  

OCI_ATTR_PRIORITY  

delay  

OCI_ATTR_DELAY  

expiration  

OCI_ATTR_EXPIRATION  

correlation  

OCI_ATTR_CORRELATION  

attempts  

OCI_ATTR_ATTEMPTS  

recipient_list  

OCI_ATTR_RECIPIENT_LIST  

exception_queue  

OCI_ATTR_EXCEPTION_QUEUE  

enqueue_time  

OCI_ATTR_ENQ_TIME  

state  

OCI_ATTR_MSG_STATE  

sender_id  

OCI_ATTR_SENDER_ID  

original_msgid  

OCI_ATTR_ORIGINAL_MSGID  

PL/SQL Enqueue Option   OCIAQEnqOptions Attribute  

visibility  

OCI_ATTR_VISIBILITY  

relative_msgid  

OCI_ATTR_RELATIVE_MSGID  

sequence_deviation  

OCI_ATTR_SEQUENCE_DEVIATION  

PL/SQL Dequeue Option   OCIAQDeqOptions Attribute  

consumer_name  

OCI_ATTR_CONSUMER_NAME  

dequeue_mode  

OCI_ATTR_DEQ_MODE  

navigation  

OCI_ATTR_NAVIGATION  

visibility  

OCI_ATTR_VISIBILITY  

wait  

OCI_ATTR_WAIT  

msgid  

OCI_ATTR_DEQ_MSGID  

correlation  

OCI_ATTR_CORRELATION  

Publish-Subscribe Notification

The publish-subscribe notification feature allows an OCI application to receive client notifications. Figure 9-1, "Publish-Subscribe Model" illustrates the process. An OCI application can:

Registered clients are notified asynchronously when events are triggered or by an explicit AQ enqueue. Clients do not need to be connected to a database.

For information on Advanced Queuing, see "OCI and Advanced Queuing".

See Also: For information on creating queues and about AQ, including concepts, features, and examples, refer to the chapter on Advanced Queuing in the Oracle8i Application Developer's Guide - Advanced Queuing. For information on creating triggers, refer to the chapter on Commands in the Oracle8i SQL Reference.

Figure 9-1 Publish-Subscribe Model


Publish-Subscribe Functions

The following steps are required in an OCI application to register and receive notifications for events. It is assumed that the appropriate event trigger or AQ queue has been set up. Also, the initialization parameter COMPATIBLE must be set to 8.1 or higher.

Detailed descriptions of the functions noted can be found in Chapter 15, "OCI Relational Functions". For examples of the use of these functions in an application, see "Publish-Subscribe Example".

Note: The publish-subscribe feature is only available on multi-threaded platforms.

  1. Execute OCIInitialize() with OCI_EVENTS mode to specify that the application is interested in registering for and receiving notifications. This starts a dedicated listening process for notifications on the client.

  2. Execute OCIHandleAlloc() with handle type OCI_HTYPE_SUBSCRIPTION to allocate a subscription handle.

  3. Execute OCIAttrSet() to set the subscription handle attributes for:

    • OCI_ATTR_SUBSCR_NAME - subscription name

    • OCI_ATTR_SUBSCR_NAMESPACE - subscription namespace

    • OCI_ATTR_SUBSCR_CALLBACK - notification callback

    • OCI_ATTR_SUBSCR_CTX - callback context

    • OCI_ATTR_SUBSCR_PAYLOAD - payload buffer for posting

    All these attributes, except OCI_ATTR_SUBSCR_PAYLOAD, must be set before registering a subscription. OCI_ATTR_SUBSCR_PAYLOAD is required before posting to a subscription. For information on these attributes, see "Subscription Handle Attributes".

  4. Define the callback routine to be used with the subscription handle. For information, see "Notification Callback".

  5. Execute OCISubscriptionRegister() to register with the subscription(s). This call can register interest in several subscriptions at the same time.

The following functions are used to manage publish-subscribe notification. Detailed descriptions of each function can be found in Chapter 15, "OCI Relational Functions".

Table 9-1 Publish-Subscribe Functions
Function  Purpose 

OCISvcCtxToLda()  

Disables a subscription.  

OCISubscriptionEnable()  

Enables a subscription.  

OCISubscriptionPost()  

Posts a subscription.  

OCISubscriptionRegister()  

Registers a subscription.  

OCISubscriptionUnRegister()  

Unregisters a subscription.  

Notification Callback

The client needs to register a notification callback that gets invoked when there is some activity on the subscription for which interest has been registered. In the AQ namespace, for instance, this occurs when a message of interest is enqueued.

This callback is typically set via the OCI_ATTR_SUBSCR_CALLBACK attribute of the subscription handle. For information, see "Subscription Handle Attributes" .

The callback must return a value of OCI_CONTINUE and adhere to the following specification:

typedef ub4 (*OCISubscriptionNotify) ( dvoid *pCtx,
                                       OCISubscription *pSubscrHp,
                                       dvoid *pPayload,
                                       ub4 *iPayloadLen,
                                       dvoid *pDescriptor,
                                       ub4 iMode);

The parameters are described as follows:

pCtx (IN)

A user-defined context specified when the callback was registered.

pSubscrHp (IN)

The subscription handle specified when the callback was registered.

pPayload (IN)

The payload for this notification. For this release, only ub1 * (a sequence of bytes) for the payload is supported.

iPayloadLen (IN)

The length of the payload for this notification.

pDescriptor (IN)

The namespace-specific descriptor. Namespace-specific parameters can be extracted from this descriptor. The structure of this descriptor is opaque to the user and its type is dependent on the namespace.

The attributes of the descriptor are namespace-specific. For advanced queuing, the descriptor is OCI_DTYPE_AQNFY. The attributes of this descriptor are:

For more information about OCI and advanced queueing, refer to "OCI and Advanced Queuing".

iMode (IN)

Call-specific mode. Valid value:

Publish-Subscribe Example

This example shows how system events, client notification, and Advanced Queuing work together to implement publish/subscription notification.

The following PL/SQL code creates all objects necessary to support a publish-subscribe mechanism under the user schema, pubsub. In this code, the Agent snoop subscribes to messages that are published at logon events. Note that the user pubsub needs AQ_ADMINISTRATOR_ROLE and AQ_USER_ROLE privileges to use Advance Queuing functionality. Also, the initialization parameter _SYSTEM_TRIG_ENABLED must be set to TRUE (default) to enable triggers for system events.


Rem ------------------------------------------------------
REM create queue table for persistent multiple consumers
Rem ------------------------------------------------------
connect pubsub/pubsub;
Rem  Create or replace a queue table
begin
  DBMS_AQADM.CREATE_QUEUE_TABLE(
  QUEUE_TABLE=>'pubsub.raw_msg_table', 
  MULTIPLE_CONSUMERS => TRUE,
  QUEUE_PAYLOAD_TYPE =>'RAW',
  COMPATIBLE => '8.1.5');
end;
/
Rem ------------------------------------------------------
Rem  Create a persistent queue for publishing messages
Rem ------------------------------------------------------
Rem  Create a queue for logon events
begin
  DBMS_AQADM.CREATE_QUEUE(QUEUE_NAME=>'pubsub.logon',
  QUEUE_TABLE=>'pubsub.raw_msg_table',
  COMMENT=>'Q for error triggers');
end;
/
Rem ------------------------------------------------------
Rem  Start the queue
Rem ------------------------------------------------------
begin
  DBMS_AQADM.START_QUEUE('pubsub.logon');
end;
/
Rem ------------------------------------------------------
Rem  define new_enqueue for convenience
Rem ------------------------------------------------------
create or replace procedure new_enqueue(queue_name  in varchar2,
                                        payload  in raw ,
correlation in varchar2 := NULL,
exception_queue in varchar2 := NULL)
as
  enq_ct     dbms_aq.enqueue_options_t;
  msg_prop   dbms_aq.message_properties_t;
  enq_msgid  raw(16);
  userdata   raw(1000);
begin
  msg_prop.exception_queue := exception_queue;
  msg_prop.correlation := correlation;
  userdata := payload;
  DBMS_AQ.ENQUEUE(queue_name,enq_ct, msg_prop,userdata,enq_msgid);
end;
/
Rem ------------------------------------------------------
Rem  add subscriber with rule based on current user name, 
Rem  using correlation_id
Rem ------------------------------------------------------
declare
subscriber sys.aq$_agent;
begin
  subscriber := sys.aq$_agent('SNOOP', null, null);
  dbms_aqadm.add_subscriber(queue_name => 'pubsub.logon',
                            subscriber => subscriber,
                            rule => 'CORRID = ''SCOTT'' ');
end;
/
Rem ------------------------------------------------------
Rem  create a trigger on logon on database
Rem ------------------------------------------------------
Rem  create trigger on after logon
create or replace trigger systrig2
   AFTER LOGON
   ON DATABASE
   begin
     new_enqueue('pubsub.logon', hextoraw('9999'), dbms_standard.login_user);
   end;
/

After the subscriptions are created, the client needs to register for notification using callback functions. The following sample code performs necessary steps for registration. The initial steps of allocating and initializing session handles are omitted here for sake of clarity.


ub4 namespace = OCI_SUBSCR_NAMESPACE_AQ;
/* callback function for notification of logon of user 'scott' on database */
ub4 notifySnoop(ctx, subscrhp, pay, payl, desc, mode)
    dvoid *ctx;
    OCISubscription *subscrhp;
    dvoid *pay;
    ub4 payl;
    dvoid *desc;
    ub4 mode;
{
    printf("Notification : User Scott Logged on\n");
}
int main()
{
    OCISession *authp = (OCISession *) 0;
    OCISubscription *subscrhpSnoop = (OCISubscription *)0;

/*****************************************************
Initialize OCI Process/Environment
Initialize Server Contexts
Connect to Server
Set Service Context
******************************************************/
    /* Registration Code Begins */
/* Each call to initSubscriptionHn allocates 
           and Initialises a Registration Handle */
    initSubscriptionHn(    &subscrhpSnoop,    /* subscription handle */
        "PUBSUB.SNOOP:ADMIN", /* subscription name */ 
/* <queue_name>:<agent_name> */
        (dvoid*)notifySnoop); /* callback function */

/*****************************************************
The Client Process does not need a live Session for Callbacks
End Session and Detach from Server
******************************************************/
    OCISessionEnd ( svchp,  errhp, authp, (ub4) OCI_DEFAULT);
    /* detach from server */
    OCIServerDetach( srvhp, errhp, OCI_DEFAULT);
    while (1)     /* wait for callback */
        sleep(1);
}
void initSubscriptionHn (subscrhp,
                         subscriptionName,
                         func)
OCISubscription **subscrhp;
  char* subscriptionName;
  dvoid * func;
{
    /* allocate subscription handle */
    (void) OCIHandleAlloc((dvoid *) envhp, (dvoid **)subscrhp, 
        (ub4) OCI_HTYPE_SUBSCRIPTION,
        (size_t) 0, (dvoid **) 0);

    /* set subscription name in handle */
    (void) OCIAttrSet((dvoid *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
        (dvoid *) subscriptionName, 
        (ub4) strlen((char *)subscriptionName),
        (ub4) OCI_ATTR_SUBSCR_NAME, errhp);

    /* set callback function in handle */
    (void) OCIAttrSet((dvoid *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
        (dvoid *) func, (ub4) 0,
        (ub4) OCI_ATTR_SUBSCR_CALLBACK, errhp);
    (void) OCIAttrSet((dvoid *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
        (dvoid *) 0, (ub4) 0,
        (ub4) OCI_ATTR_SUBSCR_CTX, errhp);

    /* set namespace in handle */
    (void) OCIAttrSet((dvoid *) *subscrhp, (ub4) OCI_HTYPE_SUBSCRIPTION,
        (dvoid *) &namespace, (ub4) 0,
        (ub4) OCI_ATTR_SUBSCR_NAMESPACE, errhp);
    checkerr(errhp, OCISubscriptionRegister(svchp, subscrhp, 1, errhp,
        OCI_DEFAULT));
}

If user SCOTT logs on to the database, the client is notified and the call back function notifySnoop is called.

Direct Path Loading

The direct path load interface allows an OCI application to access the direct path load engine of the Oracle database server to perform the functions of the Oracle SQL*Loader utility. This functionality provides the ability to load data from external files into Oracle database objects, either a table or a partition of a partitioned table.

Figure 9-2 Direct Path Loading


The OCI direct path load interface has the ability to load multiple rows by loading a direct path stream which contains data for multiple rows.

To use the direct path API, the client application performs the following steps:

  1. Perform the OCI initialization.

  2. Allocate a direct path context handle and set the attributes.

  3. Supply the name of the object (table, partition, or sub-partition) to be loaded.

  4. Describe the external data types of the columns of the object(s).

  5. Prepare the direct path interface.

  6. Allocate one or more column arrays.

  7. Allocate one or more direct path streams.

  8. Set entries in the column array to point to the input data value for each column.

  9. Convert a column array to a direct path stream format.

  10. Either load the direct path stream, or save the direct path stream to a file to be loaded at a later time.

  11. Retrieve any errors which may have occurred.

  12. Invoke the direct path finishing function.

  13. Free handles and data structures.

  14. Disconnect from the server.

A direct load operation requires that the object being loaded is locked to prevent DML on the object. Note that queries are lock free and are allowed while the object is being loaded. The mode of the DML lock, and which DML locks are obtained depend upon the specification of the OCI_DIRPATH_PARALLEL_LOAD option, and if a partition or sub-partition load is being done as opposed to an entire table load. For more information on OCI_DIRPATH_PARALLEL_LOAD, see OCIDirPathPrepare().

Limitations and Restrictions

The direct path load interface has the following limitations which are the same as SQL*Loader:

Datatypes Supported

The following external datatypes are valid for columns in a direct path load operation: SQLT_CHR, SQLT_DAT, SQLT_INT, SQLT_UIN, SQLT_FLT, SQLT_PDN, SQLT_BIN, or SQLT_NUM. For information on setting or retrieving the datatype of a column, see OCI_ATTR_DATA_TYPE. For information on datatypes, see Chapter 3, "Datatypes".

Direct Path Handles

A direct path load corresponds to a direct path array insert operation. The direct path load interface uses the following handles to keep track of the objects loaded and the specification of the data operated on:

For information about the attributes of direct path load handles, refer to "Direct Path Loading Handle Attributes". For information about column parameter attributes, see "Direct Path Column Parameter Attributes".

Direct Path Context

This handle needs to be allocated for each object, either a table or a partition of a partitioned table, being loaded. Because a OCIDirPathCtx handle is the parent handle of the OCIDirPathColArray and OCIDirPathStream handles, freeing a OCIDirPathCtx handle frees its child handles also. A direct path context is allocated with OCIHandleAlloc().

OCIEnv *envp;
OCIDirPathCtx *dpctx;
sword error;
     
error = OCIHandleAlloc((dvoid *)envp, (dvoid **)&dpctx,
     
                    OCI_HTYPE_DIRPATH_CTX, 0,(dvoid **)0);

Note that the parent handle of a direct path context is always the environment handle. A direct path context is freed with OCIHandleFree().

error = OCIHandleFree(dpctx, OCI_HTYPE_DIRPATH_CTX);

Direct Path Column Array

This handle is used to present an array of rows to the direct path interface. A row is represented by three arrays: column values, column lengths, and column flags. Methods on a column array include: allocate the array handle and set/get values corresponding to an array entry.

A direct path column array handle is allocated with OCIHandleAlloc(). The following code fragment shows explicit allocation of the direct path column array handle:

OCIDirPathCtx *dpctx;
OCIDirPathColArray *dpca;
sword error;
error = OCIHandleAlloc((dvoid *)dpctx, (dvoid **)&dpca,
     
               OCI_HTYPE_DIRPATH_COLUMN_ARRAY, 0, (dvoid **)0);

A direct path column array is freed with OCIHandleFree().

error = OCIHandleFree(dpca, OCI_HTYPE_DIRPATH_COLUMN_ARRAY);

Freeing a OCIDirPathColArray handle also frees the column array associated with the handle.

Direct Path Stream

This handle is used by the conversion operation, OCIDirPathColArrayToStream(), and by the load operation, OCIDirPathLoadStream().

Direct path stream handles is allocated by the client with OCIHandleAlloc(). The structure of a OCIDirPathStream handle can be thought of as a pair in the form (buffer, buffer length).

A direct path stream is a linear representation of Oracle table data. The conversion operations always append to the end of the stream. Load operations always start from the beginning of the stream. After a stream is completely loaded, the stream must be reset by calling OCIDirPathStreamReset().

The following example shows a direct path stream handle allocated with OCIHandleAlloc(). The parent handle is always a OCIDirPathCtx handle:

OCIDirPathCtx *dpctx;
OCIDirPathStream *dpstr;
sword error;
error = OCIHandleAlloc((dvoid *)dpctx, (dvoid **)&dpstr,
     
               OCI_HTYPE_DIRPATH_STREAM, 0,(dvoid **)0);

A direct path stream handle is freed via OCIHandleFree().

error = OCIHandleFree(dpstr, OCI_HTYPE_DIRPATH_STREAM);

Note that freeing the direct path stream handle will also free any stream buffer allocated by OCIDirPathStreamAlloc().

Direct Path Interface Functions

The functions listed in this section are used with the direct path load interface. Detailed descriptions of each function can be found in Chapter 15, "OCI Relational Functions".

Operations on the direct path context are performed by the functions in Table 9-2, "Direct Path Context Functions".

Table 9-2 Direct Path Context Functions

Function   Purpose  

OCIDirPathAbort()  

Aborts a direct path operation  

OCIDirPathFinish()  

Commits the loaded data  

OCIDirPathPrepare()  

Prepares direct path interface to convert or load rows  

OCIDirPathLoadStream()  

Loads data that has been converted to direct path stream format  

Operations on the direct path column array are performed by the functions in Table 9-3, "Direct Path Column Array Functions".

Table 9-3 Direct Path Column Array Functions

Function   Purpose  

OCIDirPathColArrayEntryGet()  

Gets a specified entry in a column array  

OCIDirPathColArrayEntrySet()  

Sets a specified entry in a column array to a specific value  

OCIDirPathColArrayRowGet()  

Gets the base row pointers for a specified row number  

OCIDirPathColArrayReset()  

Resets the row array state  

OCIDirPathColArrayToStream()  

Converts from a column array format to a direct path stream format  

Operations on the direct path stream are performed by the functions inTable 9-4, "Direct Path Stream Functions".

Table 9-4 Direct Path Stream Functions

Function   Purpose  

OCIDirPathStreamReset()  

Resets the direct stream state  

Direct Path Load Example

The following sample code illustrates the use of several of the OCI direct path interfaces. It is not a complete code example.

The following data structure is used in the example.

/* load control structure */
struct loadctl
{
  ub4                 nrow_ctl;            /* number of rows in column array */
  ub2                 ncol_ctl;         /* number of columns in column array */
  OCIEnv             *envhp_ctl;                       /* environment handle */
  OCIServer          *srvhp_ctl;                            /* server handle */
  OCIError           *errhp_ctl;                             /* error handle */
  OCIError           *errhp2_ctl;                    /* another error handle */
  OCISvcCtx          *svchp_ctl;                          /* service context */
  OCISession         *authp_ctl;                   /* authentication context */
  OCIParam           *colLstDesc_ctl;        /* column list parameter handle */
  OCIDirPathCtx      *dpctx_ctl;                      /* direct path context */
  OCIDirPathColArray *dpca_ctl;           /* direct path column array handle */
  OCIDirPathStream   *dpstr_ctl;                /* direct path stream handle */
  ub1                *buf_ctl;    /* pre-alloc'd buffer for out-of-line data */
  ub4                 bufsz_ctl;                 /* size of buf_ctl in bytes */
  ub4                 bufoff_ctl; /* offset into buf_ctl which is not in use */
  ub4                *otor_ctl;                  /* Offset to Recnum mapping */
  ub1                *inbuf_ctl;                 /* buffer for input records */
  struct pctx         pctx_ctl;                     /* partial field context */
  };

The init_load function performs a direct path load using the direct path API on the table described by tblp. The loadctl structure given by ctlp has an appropriately initialized environment and service context. A connection has been made to the server.


STATICF void
init_load(ctlp, tblp)
struct loadctl *ctlp;
struct tbl     *tblp;
{
  struct  col   *colp;
  struct  fld   *fldp;
  sword          ociret;                       /* return code from OCI calls */
  OCIDirPathCtx *dpctx;                               /* direct path context */
  OCIParam      *colDesc;                     /* column parameter descriptor */
  ub1            parmtyp;
  ub1           *timestamp = (ub1 *)0;
  ub4            size;
  ub4            i;
  ub4            pos;

  /* allocate and initialize a direct path context */
  OCI_CHECK(ctlp->envhp_ctl, OCI_HTYPE_ENV, ociret, ctlp,
            OCIHandleAlloc((dvoid *)ctlp->envhp_ctl,
                           (dvoid **)&ctlp->dpctx_ctl,
                           (ub4)OCI_HTYPE_DIRPATH_CTX,
                           (size_t)0, (dvoid **)0));

  dpctx = ctlp->dpctx_ctl;                                      /* shorthand */

  OCI_CHECK(ctlp->errhp_ctl, OCI_HTYPE_ERROR, ociret, ctlp,
            OCIAttrSet((dvoid *)dpctx, (ub4)OCI_HTYPE_DIRPATH_CTX,
                       (dvoid *)tblp->name_tbl,
                       (ub4)strlen((const char *)tblp->name_tbl),
                       (ub4)OCI_ATTR_NAME, ctlp->errhp_ctl));
...

Additional attributes, such as OCI_ATTR_SUB_NAME and OCI_ATTR_SCHEMA_NAME, are also set here. After the attributes have been set, prepare the load.

  OCI_CHECK(ctlp->errhp_ctl, OCI_HTYPE_ERROR, ociret, ctlp,
            OCIDirPathPrepare(dpctx, ctlp->svchp_ctl, ctlp->errhp_ctl));

Allocate the column array and stream handles. Note that the direct path context handle is the parent handle for the column array and stream handles. Also note that Oracle errors are returned with the environment handle associated with the direct path context.


   OCI_CHECK(ctlp->envhp_ctl, OCI_HTYPE_ENV, ociret, ctlp,
            OCIHandleAlloc((dvoid *)ctlp->dpctx_ctl, (dvoid **)&ctlp->dpca_ctl,
                           (ub4)OCI_HTYPE_DIRPATH_COLUMN_ARRAY,
                           (size_t)0, (dvoid **)0));

  OCI_CHECK(ctlp->envhp_ctl, OCI_HTYPE_ENV, ociret, ctlp,
            OCIHandleAlloc((dvoid *)ctlp->dpctx_ctl,(dvoid **)&ctlp->dpstr_ctl,
                           (ub4)OCI_HTYPE_DIRPATH_STREAM,
                           (size_t)0, (dvoid **)0));

Get number of rows and columns in the column array just allocated.


  OCI_CHECK(ctlp->errhp_ctl, OCI_HTYPE_ERROR, ociret, ctlp,
            OCIAttrGet(ctlp->dpca_ctl, (ub4)OCI_HTYPE_DIRPATH_COLUMN_ARRAY,
                       &ctlp->nrow_ctl, 0, OCI_ATTR_NUM_ROWS,
                       ctlp->errhp_ctl));

   OCI_CHECK(ctlp->errhp_ctl, OCI_HTYPE_ERROR, ociret, ctlp,
            OCIAttrGet(ctlp->dpca_ctl, (ub4)OCI_HTYPE_DIRPATH_COLUMN_ARRAY,
                       &ctlp->ncol_ctl, 0, OCI_ATTR_NUM_COLS,
                       ctlp->errhp_ctl));

Set the input data fields to their corresponding data columns.

OCIDirPathColArrayEntrySet(ctlp->dpca_ctl, ctlp->errhp_ctl,
                                         rowoff, colp->id_col,
                                         cval, clen, cflg));

Reset column array state in case a previous conversion needed to be continued or a row is expecting more data.

(void) OCIDirPathColArrayReset(ctlp->dpca_ctl, ctlp->errhp_ctl);

Reset the stream state to start a new stream. Otherwise, data in the stream is appended to existing data.

(void) OCIDirPathStreamReset(ctlp->dpstr_ctl, ctlp->errhp_ctl);

After inputting the data, convert the data in the column array to stream format and filter out any bad records.

ocierr = OCIDirPathColArrayToStream(ctlp->dpca_ctl, ctlp->dpctx_ctl,
                                        ctlp->dpstr_ctl, ctlp->errhp_ctl,
                                        rowcnt, startoff);
    

Load the stream. Note that the position in the stream is maintained internally to the stream handle, along with offset information for the column array which produced the stream. When the conversion to stream format is done, the data is appended to the stream. It is the responsibility of the caller to reset the stream when appropriate. On errors, the position is moved to the next row, or the end of the stream if the error occurs on the last row. The next OCIDirPathLoadStream() call starts on the next row, if any. If a OCIDirPathLoadStream() call is made, and the end of a stream has been reached, OCI_NO_DATA is returned.

ocierr = OCIDirPathLoadStream(ctlp->dpctx_ctl, ctlp->dpstr_ctl,
                                ctlp->errhp_ctl);

Finish the direct path load.

OCIDirPathFinish(ctlp->dpctx_ctl, ctlp->errhp_ctl);

Free all the direct path handles allocated. Note that direct path column array and stream handles are freed when the parent direct path context handle is freed. The following code statements that free the direct path column array and stream handles are not necessary but included here as examples.

ociret = OCIHandleFree((dvoid *)ctlp->dpca_ctl,
                           OCI_HTYPE_DIRPATH_COLUMN_ARRAY);
ociret = OCIHandleFree((dvoid *)ctlp->dpstr_ctl,
                           OCI_HTYPE_DIRPATH_STREAM);
ociret = OCIHandleFree((dvoid *)ctlp->dpctx_ctl, 
                          OCI_HTYPE_DIRPATH_CTX);




Prev

Next
Oracle
Copyright © 1999 Oracle Corporation.

All Rights Reserved.

Library

Product

Contents

Index