Sample C Code Using an OCI Session Pool

Use an OCI session pool to connect to the primary database and True Cache from a C application.

Warning: Do not use this code in a production environment.

This example mirrors the flow from Sample Java Code Using the JDBC Thin Driver:

  1. Create a session pool from the primary database service.
  2. Get a default session and verify that it is connected to the primary database.
  3. Get a read-only session and verify that it is connected to True Cache.
  4. Get a read-prefer session and verify that it uses True Cache when available; otherwise, it uses the primary database.

Before you compile and run the example, ensure that you have Oracle Client or Oracle AI Database 23.26.2 or later, a primary database service with an associated True Cache service, and a database user that can connect to both services.

Set ORACLE_HOME to the Oracle home, compile the program, and run it with the primary database connect string, user name, and password.

export ORACLE_HOME=/path/to/oracle/home
cc -O2 -Wall -Wextra -I$ORACLE_HOME/rdbms/public \
   -o minimal_truecache_oci minimal_truecache_oci.c \
   -L$ORACLE_HOME/lib -Wl,-rpath,$ORACLE_HOME/lib -lclntsh

export LD_LIBRARY_PATH=$ORACLE_HOME/lib
./minimal_truecache_oci primary_host:1521/primary_service username password

The application passes only the primary database connect string. OCI selects the True Cache service when the application checks out a pooled session with OCI_SESSGET_READ_ONLY or OCI_SESSGET_READ_PREFER.

#include <oci.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void print_oci_error(OCIError *errhp, const char *where) {
    text errbuf[1024];
    sb4 errcode = 0;
    memset(errbuf, 0, sizeof(errbuf));
    OCIErrorGet((dvoid *)errhp, 1, NULL, &errcode, errbuf, sizeof(errbuf), OCI_HTYPE_ERROR);
    fprintf(stderr, "%s: ORA-%d: %s\n", where, (int)errcode, errbuf);
}

static int set_auth_attr(OCIError *errhp, OCIAuthInfo *authhp, ub4 attr, const char *value, const char *label) {
    sword rc = OCIAttrSet((dvoid *)authhp, OCI_HTYPE_AUTHINFO,
                          (dvoid *)value, (ub4)strlen(value),
                          attr, errhp);
    if (rc != OCI_SUCCESS && rc != OCI_SUCCESS_WITH_INFO) {
        print_oci_error(errhp, label);
        return -1;
    }
    return 0;
}

static OCIAuthInfo *make_auth(OCIEnv *envhp, OCIError *errhp, const char *user, const char *password) {
    OCIAuthInfo *authhp = NULL;
    sword rc = OCIHandleAlloc((dvoid *)envhp, (dvoid **)&authhp, OCI_HTYPE_AUTHINFO, 0, NULL);
    if (rc != OCI_SUCCESS) {
        print_oci_error(errhp, "OCIHandleAlloc(OCI_HTYPE_AUTHINFO)");
        return NULL;
    }

    if (set_auth_attr(errhp, authhp, OCI_ATTR_USERNAME, user, "OCIAttrSet(OCI_ATTR_USERNAME)") != 0 ||
        set_auth_attr(errhp, authhp, OCI_ATTR_PASSWORD, password, "OCIAttrSet(OCI_ATTR_PASSWORD)") != 0) {
        OCIHandleFree((dvoid *)authhp, OCI_HTYPE_AUTHINFO);
        return NULL;
    }

    return authhp;
}

/*
 * Check out one pooled session, run a lightweight verification query, print
 * the routed instance/service, and release the session back to the pool.
 */

static int verify_connection(OCIEnv *envhp,
                             OCIError *errhp,
                             OCISPool *poolhp,
                             OraText *pool_name,
                             ub4 pool_name_len,
                             const char *user,
                             const char *password,
                             ub4 sessget_mode,
                             const char *label) {
    OCIAuthInfo *authhp = NULL;
    OCISvcCtx *svchp = NULL;
    OCIStmt *stmthp = NULL;
    OCIDefine *def1 = NULL;
    OCIDefine *def2 = NULL;
    OCIDefine *def3 = NULL;
    OCIDefine *def4 = NULL;
    char instance_name[128];
    char server_host[256];
    char service_name[256];
    char db_unique_name[128];
    sb2 ind1 = 0, ind2 = 0, ind3 = 0, ind4 = 0;
    sword rc;

    (void)poolhp;
    memset(instance_name, 0, sizeof(instance_name));
    memset(server_host, 0, sizeof(server_host));
    memset(service_name, 0, sizeof(service_name));
    memset(db_unique_name, 0, sizeof(db_unique_name));

    /*
     * Pass an OCIAuthInfo handle to OCISessionGet(). This is important for the
     * True Cache checkout path; using NULL can crash or fail, depending on the
     * client and server version and pool state.
     */

    authhp = make_auth(envhp, errhp, user, password);
    if (!authhp) return -1;

    /*
     * Use the pool name returned by OCISessionPoolCreate(), not the original
     * connect string.
     *
     * sessget_mode controls routing:
     *   OCI_DEFAULT               -> primary database
     *   OCI_SESSGET_READ_ONLY     -> strict True Cache checkout
     *   OCI_SESSGET_READ_PREFER   -> prefer True Cache, then primary database
     */

    rc = OCISessionGet(envhp, errhp, &svchp, authhp,
                       pool_name, pool_name_len,
                       NULL, 0, NULL, NULL, NULL,
                       OCI_SESSGET_SPOOL | sessget_mode);
    OCIHandleFree((dvoid *)authhp, OCI_HTYPE_AUTHINFO);
    authhp = NULL;

    if (rc != OCI_SUCCESS && rc != OCI_SUCCESS_WITH_INFO) {
        print_oci_error(errhp, "OCISessionGet");
        return -1;
    }

    rc = OCIHandleAlloc((dvoid *)envhp, (dvoid **)&stmthp, OCI_HTYPE_STMT, 0, NULL);
    if (rc != OCI_SUCCESS) {
        print_oci_error(errhp, "OCIHandleAlloc(OCI_HTYPE_STMT)");
        OCISessionRelease(svchp, errhp, NULL, 0, OCI_DEFAULT);
        return -1;
    }

    /*
     * SYS_CONTEXT works for application users and does not require access to
     * dynamic performance views such as V$DATABASE or V$INSTANCE.
     */
    const char *sql =
        "select sys_context('USERENV','INSTANCE_NAME'), "
        "       sys_context('USERENV','SERVER_HOST'), "
        "       sys_context('USERENV','SERVICE_NAME'), "
        "       sys_context('USERENV','DB_UNIQUE_NAME') "
        "from dual";

    rc = OCIStmtPrepare(stmthp, errhp, (text *)sql, (ub4)strlen(sql), OCI_NTV_SYNTAX, OCI_DEFAULT);
    if (rc != OCI_SUCCESS && rc != OCI_SUCCESS_WITH_INFO) {
        print_oci_error(errhp, "OCIStmtPrepare");
        OCIHandleFree((dvoid *)stmthp, OCI_HTYPE_STMT);
        OCISessionRelease(svchp, errhp, NULL, 0, OCI_DEFAULT);
        return -1;
    }

    OCIDefineByPos(stmthp, &def1, errhp, 1, instance_name, sizeof(instance_name), SQLT_STR, &ind1, NULL, NULL, OCI_DEFAULT);
    OCIDefineByPos(stmthp, &def2, errhp, 2, server_host, sizeof(server_host), SQLT_STR, &ind2, NULL, NULL, OCI_DEFAULT);
    OCIDefineByPos(stmthp, &def3, errhp, 3, service_name, sizeof(service_name), SQLT_STR, &ind3, NULL, NULL, OCI_DEFAULT);
    OCIDefineByPos(stmthp, &def4, errhp, 4, db_unique_name, sizeof(db_unique_name), SQLT_STR, &ind4, NULL, NULL, OCI_DEFAULT);

    rc = OCIStmtExecute(svchp, stmthp, errhp, 0, 0, NULL, NULL, OCI_DEFAULT);
    if (rc != OCI_SUCCESS && rc != OCI_SUCCESS_WITH_INFO) {
        print_oci_error(errhp, "OCIStmtExecute");
        OCIHandleFree((dvoid *)stmthp, OCI_HTYPE_STMT);
        OCISessionRelease(svchp, errhp, NULL, 0, OCI_DEFAULT);
        return -1;
    }

    rc = OCIStmtFetch2(stmthp, errhp, 1, OCI_FETCH_NEXT, 0, OCI_DEFAULT);
    if (rc != OCI_SUCCESS && rc != OCI_SUCCESS_WITH_INFO) {
        print_oci_error(errhp, "OCIStmtFetch2");
        OCIHandleFree((dvoid *)stmthp, OCI_HTYPE_STMT);
        OCISessionRelease(svchp, errhp, NULL, 0, OCI_DEFAULT);
        return -1;
    }

    printf("%s\n", label);
    printf("  instance_name : %s\n", instance_name);
    printf("  server_host   : %s\n", server_host);
    printf("  service_name  : %s\n", service_name);
    printf("  db_unique_name: %s\n", db_unique_name);

    OCIHandleFree((dvoid *)stmthp, OCI_HTYPE_STMT);
    OCISessionRelease(svchp, errhp, NULL, 0, OCI_DEFAULT);
    return 0;
}

int main(int argc, char **argv) {
    OCIEnv *envhp = NULL;
    OCIError *errhp = NULL;
    OCISPool *poolhp = NULL;
    OraText *pool_name = NULL;
    ub4 pool_name_len = 0;
    sword rc;

    if (argc != 4) {
        fprintf(stderr, "Usage: %s primary_host:1521/primary_service username password\n", argv[0]);
        return 2;
    }

    const char *primary_connstr = argv[1];
    const char *user = argv[2];
    const char *password = argv[3];

    rc = OCIEnvCreate(&envhp, OCI_THREADED, NULL, NULL, NULL, NULL, 0, NULL);
    if (rc != OCI_SUCCESS) {
        fprintf(stderr, "OCIEnvCreate failed\n");
        return 1;
    }

    OCIHandleAlloc((dvoid *)envhp, (dvoid **)&errhp, OCI_HTYPE_ERROR, 0, NULL);
    OCIHandleAlloc((dvoid *)envhp, (dvoid **)&poolhp, OCI_HTYPE_SPOOL, 0, NULL);

    printf("Creating OCI True Cache session pool from primary service:\n  %s\n", primary_connstr);

    /*
     * OCI_SPC_TRUECACHE enables multiple-service-pool mode for True Cache.
     * Use the primary database service as the connect string. OCI discovers
     * the True Cache target for read-only and read-prefer checkouts from the
     * service configuration.
     */

    rc = OCISessionPoolCreate(envhp, errhp, poolhp,
                              &pool_name, &pool_name_len,
                              (OraText *)primary_connstr, (ub4)strlen(primary_connstr),
                              1, 2, 1,
                              (OraText *)user, (ub4)strlen(user),
                              (OraText *)password, (ub4)strlen(password),
                              OCI_SPC_TRUECACHE);
    if (rc != OCI_SUCCESS && rc != OCI_SUCCESS_WITH_INFO) {
        print_oci_error(errhp, "OCISessionPoolCreate(OCI_SPC_TRUECACHE)");
        OCIHandleFree((dvoid *)poolhp, OCI_HTYPE_SPOOL);
        OCIHandleFree((dvoid *)errhp, OCI_HTYPE_ERROR);
        OCIHandleFree((dvoid *)envhp, OCI_HTYPE_ENV);
        return 1;
    }

    verify_connection(envhp, errhp, poolhp, pool_name, pool_name_len, user, password,
                      OCI_DEFAULT, "Default session, expected primary:");

    verify_connection(envhp, errhp, poolhp, pool_name, pool_name_len, user, password,
                      OCI_SESSGET_READ_ONLY, "Read-only session, expected True Cache:");

    verify_connection(envhp, errhp, poolhp, pool_name, pool_name_len, user, password,
                      OCI_SESSGET_READ_PREFER, "Read-prefer session, expected True Cache if available:");

    OCISessionPoolDestroy(poolhp, errhp, OCI_DEFAULT);
    OCIHandleFree((dvoid *)poolhp, OCI_HTYPE_SPOOL);
    OCIHandleFree((dvoid *)errhp, OCI_HTYPE_ERROR);
    OCIHandleFree((dvoid *)envhp, OCI_HTYPE_ENV);
    return 0;
}