//-----------------------------------------------------------------------------
// Copyright (c) 2016-2018 Oracle and/or its affiliates.  All rights reserved.
// This program is free software: you can modify it and/or redistribute it
// under the terms of:
//
// (i)  the Universal Permissive License v 1.0 or at your option, any
//      later version (http://oss.oracle.com/licenses/upl); and/or
//
// (ii) the Apache License v 2.0. (http://www.apache.org/licenses/LICENSE-2.0)
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// dpiConn.c
//   Implementation of connection.
//-----------------------------------------------------------------------------

#include "dpiImpl.h"
#include <time.h>

// forward declarations of internal functions only used in this file
static int dpiConn__createStandalone(dpiConn *conn, const char *userName,
        uint32_t userNameLength, const char *password, uint32_t passwordLength,
        const char *connectString, uint32_t connectStringLength,
        const dpiCommonCreateParams *commonParams,
        const dpiConnCreateParams *createParams, dpiError *error);
static int dpiConn__get(dpiConn *conn, const char *userName,
        uint32_t userNameLength, const char *password, uint32_t passwordLength,
        const char *connectString, uint32_t connectStringLength,
        dpiConnCreateParams *createParams, dpiPool *pool, dpiError *error);
static int dpiConn__getHandles(dpiConn *conn, dpiError *error);
static int dpiConn__getServerCharset(dpiConn *conn, dpiError *error);
static int dpiConn__getServerVersion(dpiConn *conn, dpiError *error);
static int dpiConn__getSession(dpiConn *conn, uint32_t mode,
        const char *connectString, uint32_t connectStringLength,
        dpiConnCreateParams *params, void *authInfo, dpiError *error);
static int dpiConn__setAttributesFromCreateParams(dpiConn *conn, void *handle,
        uint32_t handleType, const char *userName, uint32_t userNameLength,
        const char *password, uint32_t passwordLength,
        const dpiConnCreateParams *params, void **shardingKey,
        void **superShardingKey, dpiError *error);
static int dpiConn__setShardingKey(dpiConn *conn, void **shardingKey,
        void *handle, uint32_t handleType, uint32_t attribute,
        const char *action, dpiShardingKeyColumn *columns, uint8_t numColumns,
        dpiError *error);
static int dpiConn__setShardingKeyValue(dpiConn *conn, void *shardingKey,
        dpiShardingKeyColumn *column, dpiError *error);


//-----------------------------------------------------------------------------
// dpiConn__checkConnected() [INTERNAL]
//   Validate the connection handle and determine the error structure to use.
// Check to see that the connection is connected to the database.
//-----------------------------------------------------------------------------
static int dpiConn__checkConnected(dpiConn *conn, const char *fnName,
        dpiError *error)
{
    if (dpiGen__startPublicFn(conn, DPI_HTYPE_CONN, fnName, 1, error) < 0)
        return DPI_FAILURE;
    if (!conn->handle || conn->closing)
        return dpiError__set(error, "check connected", DPI_ERR_NOT_CONNECTED);
    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiConn__close() [INTERNAL]
//   Internal method used for closing the connection. Any transaction is rolled
// back and any handles allocated are freed. For connections acquired from a
// pool and that aren't marked as needed to be dropped, the last time used is
// updated. This is called from dpiConn_close() where errors are expected to be
// propagated and from dpiConn__free() where errors are ignored.
//-----------------------------------------------------------------------------
static int dpiConn__close(dpiConn *conn, uint32_t mode, const char *tag,
        uint32_t tagLength, int propagateErrors, dpiError *error)
{
    int status, txnInProgress;
    uint32_t serverStatus, i;
    time_t *lastTimeUsed;
    dpiStmt *stmt;
    dpiLob *lob;

    // rollback any outstanding transaction, if one is in progress; drop the
    // session if any errors take place
    txnInProgress = 0;
    if (!conn->deadSession && conn->sessionHandle) {
        txnInProgress = 1;
        if (conn->env->versionInfo->versionNum >= 12)
            dpiOci__attrGet(conn->sessionHandle, DPI_OCI_HTYPE_SESSION,
                    &txnInProgress, NULL, DPI_OCI_ATTR_TRANSACTION_IN_PROGRESS,
                    NULL, error);
    }
    if (txnInProgress &&
            dpiOci__transRollback(conn, propagateErrors, error) < 0)
        conn->deadSession = 1;

    // close all open statements; note that no references are retained by the
    // handle list (otherwise all statements would be left open until an
    // explicit close was made of either the statement or the connection) so
    // a reference needs to be acquired first, as otherwise the statement may
    // be freed while the close is being performed!
    if (conn->openStmts) {
        for (i = 0; i < conn->openStmts->numSlots; i++) {
            stmt = (dpiStmt*) conn->openStmts->handles[i];
            if (!stmt)
                continue;
            if (conn->env->threaded) {
                dpiMutex__acquire(conn->env->mutex);
                status = dpiGen__checkHandle(stmt, DPI_HTYPE_STMT, NULL, NULL);
                if (status == DPI_SUCCESS)
                    stmt->refCount += 1;
                dpiMutex__release(conn->env->mutex);
                if (status < 0)
                    continue;
            }
            status = dpiStmt__close(stmt, NULL, 0, propagateErrors, error);
            if (conn->env->threaded)
                dpiGen__setRefCount(stmt, error, -1);
            if (status < 0)
                return DPI_FAILURE;
        }
    }

    // close all open LOBs; the same comments apply as for statements
    if (conn->openLobs) {
        for (i = 0; i < conn->openLobs->numSlots; i++) {
            lob = (dpiLob*) conn->openLobs->handles[i];
            if (!lob)
                continue;
            if (conn->env->threaded) {
                dpiMutex__acquire(conn->env->mutex);
                status = dpiGen__checkHandle(lob, DPI_HTYPE_LOB, NULL, NULL);
                if (status == DPI_SUCCESS)
                    lob->refCount += 1;
                dpiMutex__release(conn->env->mutex);
                if (status < 0)
                    continue;
            }
            status = dpiLob__close(lob, propagateErrors, error);
            if (conn->env->threaded)
                dpiGen__setRefCount(lob, error, -1);
            if (status < 0)
                return DPI_FAILURE;
        }
    }

    // handle connections created with an external handle
    if (conn->externalHandle) {
        conn->sessionHandle = NULL;

    // handle standalone connections
    } else if (conn->standalone) {

        // end session and free session handle
        if (dpiOci__sessionEnd(conn, propagateErrors, error) < 0)
            return DPI_FAILURE;
        dpiOci__handleFree(conn->sessionHandle, DPI_OCI_HTYPE_SESSION);
        conn->sessionHandle = NULL;

        // detach from server and free server handle
        if (dpiOci__serverDetach(conn, propagateErrors, error) < 0)
            return DPI_FAILURE;
        dpiOci__handleFree(conn->serverHandle, DPI_OCI_HTYPE_SERVER);

        // free service context handle
        dpiOci__handleFree(conn->handle, DPI_OCI_HTYPE_SVCCTX);

    // handle pooled connections
    } else {

        // if the session isn't marked as needing to be dropped, update the
        // last time used (this is checked when the session is acquired)
        // NOTE: this is only needed for clients earlier than 12.2
        if (!conn->deadSession && conn->sessionHandle &&
                (conn->env->versionInfo->versionNum < 12 ||
                        (conn->env->versionInfo->versionNum == 12 &&
                         conn->env->versionInfo->releaseNum < 2))) {

            // get the pointer from the context
            lastTimeUsed = NULL;
            if (dpiOci__contextGetValue(conn, DPI_CONTEXT_LAST_TIME_USED,
                    (uint32_t) (sizeof(DPI_CONTEXT_LAST_TIME_USED) - 1),
                    (void**) &lastTimeUsed, propagateErrors, error) < 0)
                return DPI_FAILURE;

            // if no pointer available, allocate and set it
            if (!lastTimeUsed) {
                if (dpiOci__memoryAlloc(conn, (void**) &lastTimeUsed,
                        sizeof(time_t), propagateErrors, error) < 0)
                    return DPI_FAILURE;
                if (dpiOci__contextSetValue(conn, DPI_CONTEXT_LAST_TIME_USED,
                        (uint32_t) (sizeof(DPI_CONTEXT_LAST_TIME_USED) - 1),
                        lastTimeUsed, propagateErrors, error) < 0)
                    dpiOci__memoryFree(conn, lastTimeUsed, error);
            }

            // set last time used
            if (lastTimeUsed)
                *lastTimeUsed = time(NULL);

        }

        // check server status; if not connected, ensure session is dropped
        if (conn->serverHandle) {
            if (dpiOci__attrGet(conn->serverHandle, DPI_OCI_HTYPE_SERVER,
                    &serverStatus, NULL, DPI_OCI_ATTR_SERVER_STATUS,
                    "get server status", error) < 0 ||
                    serverStatus != DPI_OCI_SERVER_NORMAL)
                conn->deadSession = 1;
        }

        // release session
        if (conn->deadSession)
            mode |= DPI_OCI_SESSRLS_DROPSESS;
        if (dpiOci__sessionRelease(conn, tag, tagLength, mode, propagateErrors,
                error) < 0)
            return DPI_FAILURE;
        conn->sessionHandle = NULL;

    }

    conn->handle = NULL;
    conn->serverHandle = NULL;
    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiConn__create() [PRIVATE]
//   Perform internal initialization of the connection.
//-----------------------------------------------------------------------------
int dpiConn__create(dpiConn *conn, const dpiContext *context,
        const char *userName, uint32_t userNameLength, const char *password,
        uint32_t passwordLength, const char *connectString,
        uint32_t connectStringLength, dpiPool *pool,
        const dpiCommonCreateParams *commonParams,
        dpiConnCreateParams *createParams, dpiError *error)
{
    // allocate handle lists for statements and LOBs
    if (dpiHandleList__create(&conn->openStmts, error) < 0)
        return DPI_FAILURE;
    if (dpiHandleList__create(&conn->openLobs, error) < 0)
        return DPI_FAILURE;

    // initialize environment (for non-pooled connections)
    if (!pool && dpiEnv__init(conn->env, context, commonParams, error) < 0)
        return DPI_FAILURE;

    // if a handle is specified, use it
    if (createParams->externalHandle) {
        conn->handle = createParams->externalHandle;
        conn->externalHandle = 1;
        return dpiConn__getHandles(conn, error);
    }

    // connection class, sharding and the use of session pools require the use
    // of the OCISessionGet() method; all other cases use the OCISessionBegin()
    // method which is more capable
    if (pool || (createParams->connectionClass &&
            createParams->connectionClassLength > 0) ||
            createParams->shardingKeyColumns ||
            createParams->superShardingKeyColumns)
        return dpiConn__get(conn, userName, userNameLength, password,
                passwordLength, connectString, connectStringLength,
                createParams, pool, error);
    return dpiConn__createStandalone(conn, userName, userNameLength, password,
            passwordLength, connectString, connectStringLength, commonParams,
            createParams, error);
}


//-----------------------------------------------------------------------------
// dpiConn__createStandalone() [PRIVATE]
//   Create a standalone connection to the database using the parameters
// specified.
//-----------------------------------------------------------------------------
static int dpiConn__createStandalone(dpiConn *conn, const char *userName,
        uint32_t userNameLength, const char *password, uint32_t passwordLength,
        const char *connectString, uint32_t connectStringLength,
        const dpiCommonCreateParams *commonParams,
        const dpiConnCreateParams *createParams, dpiError *error)
{
    uint32_t credentialType, authMode;

    // mark the connection as a standalone connection
    conn->standalone = 1;

    // allocate the server handle
    if (dpiOci__handleAlloc(conn->env->handle, &conn->serverHandle,
            DPI_OCI_HTYPE_SERVER, "allocate server handle", error) < 0)
        return DPI_FAILURE;

    // attach to the server
    if (dpiOci__serverAttach(conn, connectString, connectStringLength,
            error) < 0)
        return DPI_FAILURE;

    // allocate the service context handle
    if (dpiOci__handleAlloc(conn->env->handle, &conn->handle,
            DPI_OCI_HTYPE_SVCCTX, "allocate service context handle",
            error) < 0)
        return DPI_FAILURE;

    // set attribute for server handle
    if (dpiOci__attrSet(conn->handle, DPI_OCI_HTYPE_SVCCTX, conn->serverHandle,
            0, DPI_OCI_ATTR_SERVER, "set server handle", error) < 0)
        return DPI_FAILURE;

    // allocate the session handle
    if (dpiOci__handleAlloc(conn->env->handle, &conn->sessionHandle,
            DPI_OCI_HTYPE_SESSION, "allocate session handle", error) < 0)
        return DPI_FAILURE;

    // driver name and edition are only relevant for standalone connections
    if (dpiUtils__setAttributesFromCommonCreateParams(conn->sessionHandle,
            DPI_OCI_HTYPE_SESSION, commonParams, error) < 0)
        return DPI_FAILURE;

    // populate attributes on the session handle
    if (dpiConn__setAttributesFromCreateParams(conn, conn->sessionHandle,
            DPI_OCI_HTYPE_SESSION, userName, userNameLength, password,
            passwordLength, createParams, NULL, NULL, error) < 0)
        return DPI_FAILURE;

    // set the session handle on the service context handle
    if (dpiOci__attrSet(conn->handle, DPI_OCI_HTYPE_SVCCTX,
            conn->sessionHandle, 0, DPI_OCI_ATTR_SESSION, "set session handle",
            error) < 0)
        return DPI_FAILURE;

    // if a new password is specified, change it (this also creates the session
    // so a call to OCISessionBegin() is not needed)
    if (createParams->newPassword && createParams->newPasswordLength > 0) {
        authMode = DPI_OCI_AUTH;
        if (createParams->authMode & DPI_MODE_AUTH_SYSDBA)
            authMode |= DPI_OCI_CPW_SYSDBA;
        if (createParams->authMode & DPI_MODE_AUTH_SYSOPER)
            authMode |= DPI_OCI_CPW_SYSOPER;
        if (createParams->authMode & DPI_MODE_AUTH_SYSASM)
            authMode |= DPI_OCI_CPW_SYSASM;
        if (createParams->authMode & DPI_MODE_AUTH_SYSBKP)
            authMode |= DPI_OCI_CPW_SYSBKP;
        if (createParams->authMode & DPI_MODE_AUTH_SYSDGD)
            authMode |= DPI_OCI_CPW_SYSDGD;
        if (createParams->authMode & DPI_MODE_AUTH_SYSKMT)
            authMode |= DPI_OCI_CPW_SYSKMT;
        return dpiOci__passwordChange(conn, userName, userNameLength, password,
                passwordLength, createParams->newPassword,
                createParams->newPasswordLength, authMode, error);
    }

    // begin the session
    credentialType = (createParams->externalAuth) ? DPI_OCI_CRED_EXT :
            DPI_OCI_CRED_RDBMS;
    authMode = createParams->authMode | DPI_OCI_STMT_CACHE;
    if (dpiOci__sessionBegin(conn, credentialType, authMode, error) < 0)
        return DPI_FAILURE;
    return dpiConn__getServerCharset(conn, error);
}


//-----------------------------------------------------------------------------
// dpiConn__free() [INTERNAL]
//   Free the memory and any resources associated with the connection.
//-----------------------------------------------------------------------------
void dpiConn__free(dpiConn *conn, dpiError *error)
{
    if (conn->handle)
        dpiConn__close(conn, DPI_MODE_CONN_CLOSE_DEFAULT, NULL, 0, 0,
                error);
    if (conn->pool) {
        dpiGen__setRefCount(conn->pool, error, -1);
        conn->pool = NULL;
        conn->env = NULL;
    }
    if (conn->env) {
        dpiEnv__free(conn->env, error);
        conn->env = NULL;
    }
    if (conn->releaseString) {
        dpiUtils__freeMemory((void*) conn->releaseString);
        conn->releaseString = NULL;
    }
    if (conn->openStmts) {
        dpiHandleList__free(conn->openStmts);
        conn->openStmts = NULL;
    }
    if (conn->openLobs) {
        dpiHandleList__free(conn->openLobs);
        conn->openLobs = NULL;
    }
    dpiUtils__freeMemory(conn);
}


//-----------------------------------------------------------------------------
// dpiConn__get() [INTERNAL]
//   Create a connection to the database using the parameters specified. This
// method uses the simplified OCI session creation protocol which is required
// when using pools and session tagging.
//-----------------------------------------------------------------------------
static int dpiConn__get(dpiConn *conn, const char *userName,
        uint32_t userNameLength, const char *password, uint32_t passwordLength,
        const char *connectString, uint32_t connectStringLength,
        dpiConnCreateParams *createParams, dpiPool *pool, dpiError *error)
{
    void *shardingKey = NULL, *superShardingKey = NULL;
    int externalAuth, status;
    void *authInfo;
    uint32_t mode;

    // set things up for the call to acquire a session
    if (pool) {
        dpiGen__setRefCount(pool, error, 1);
        conn->pool = pool;
        mode = DPI_OCI_SESSGET_SPOOL;
        externalAuth = pool->externalAuth;
        if (userName && pool->homogeneous)
            return dpiError__set(error, "check proxy", DPI_ERR_INVALID_PROXY);

        // if the userName is provided but no password is provided and external
        // authentication is not being used, proxy authentication is taking
        // place
        if (userName && !password && !externalAuth)
            mode |= DPI_OCI_SESSGET_CREDPROXY;
        if (createParams->matchAnyTag)
            mode |= DPI_OCI_SESSGET_SPOOL_MATCHANY;
    } else {
        mode = DPI_OCI_SESSGET_STMTCACHE;
        externalAuth = createParams->externalAuth;
    }
    if (createParams->authMode & DPI_MODE_AUTH_SYSDBA)
        mode |= DPI_OCI_SESSGET_SYSDBA;
    if (externalAuth)
        mode |= DPI_OCI_SESSGET_CREDEXT;

    // create authorization handle
    if (dpiOci__handleAlloc(conn->env->handle, &authInfo,
            DPI_OCI_HTYPE_AUTHINFO, "allocate authinfo handle", error) < 0)
        return DPI_FAILURE;

    // set attributes for create parameters
    if (dpiConn__setAttributesFromCreateParams(conn, authInfo,
            DPI_OCI_HTYPE_AUTHINFO, userName, userNameLength, password,
            passwordLength, createParams, &shardingKey, &superShardingKey,
            error) < 0) {
        dpiOci__handleFree(authInfo, DPI_OCI_HTYPE_AUTHINFO);
        return DPI_FAILURE;
    }

    // get a session from the pool
    status = dpiConn__getSession(conn, mode, connectString,
            connectStringLength, createParams, authInfo, error);
    if (status == DPI_SUCCESS && pool) {
        if (shardingKey)
            dpiOci__descriptorFree(shardingKey, DPI_OCI_DTYPE_SHARDING_KEY);
        if (superShardingKey)
            dpiOci__descriptorFree(superShardingKey,
                    DPI_OCI_DTYPE_SHARDING_KEY);
    }
    dpiOci__handleFree(authInfo, DPI_OCI_HTYPE_AUTHINFO);
    if (status < 0)
        return status;
    return dpiConn__getServerCharset(conn, error);
}


//-----------------------------------------------------------------------------
// dpiConn__getAttributeText() [INTERNAL]
//   Get the value of the OCI attribute from a text string.
//-----------------------------------------------------------------------------
static int dpiConn__getAttributeText(dpiConn *conn, uint32_t attribute,
        const char **value, uint32_t *valueLength, const char *fnName)
{
    dpiError error;
    int status;

    // validate parameters
    if (dpiConn__checkConnected(conn, fnName, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(conn, value)
    DPI_CHECK_PTR_NOT_NULL(conn, valueLength)

    // determine pointer to pass (OCI uses different sizes)
    switch (attribute) {
        case DPI_OCI_ATTR_CURRENT_SCHEMA:
        case DPI_OCI_ATTR_LTXID:
        case DPI_OCI_ATTR_EDITION:
            status = dpiOci__attrGet(conn->sessionHandle,
                    DPI_OCI_HTYPE_SESSION, (void*) value, valueLength,
                    attribute, "get session value", &error);
            break;
        case DPI_OCI_ATTR_INTERNAL_NAME:
        case DPI_OCI_ATTR_EXTERNAL_NAME:
            status = dpiOci__attrGet(conn->serverHandle, DPI_OCI_HTYPE_SERVER,
                    (void*) value, valueLength, attribute, "get server value",
                    &error);
            break;
        default:
            status = dpiError__set(&error, "get attribute text",
                    DPI_ERR_NOT_SUPPORTED);
            break;
    }

    return dpiGen__endPublicFn(conn, status, &error);
}


//-----------------------------------------------------------------------------
// dpiConn__getHandles() [INTERNAL]
//   Get the server and session handle from the service context handle.
//-----------------------------------------------------------------------------
static int dpiConn__getHandles(dpiConn *conn, dpiError *error)
{
    if (dpiOci__attrGet(conn->handle, DPI_OCI_HTYPE_SVCCTX,
            (void*) &conn->sessionHandle, NULL, DPI_OCI_ATTR_SESSION,
            "get session handle", error) < 0)
        return DPI_FAILURE;
    if (dpiOci__attrGet(conn->handle, DPI_OCI_HTYPE_SVCCTX,
            (void*) &conn->serverHandle, NULL, DPI_OCI_ATTR_SERVER,
            "get server handle", error) < 0)
        return DPI_FAILURE;

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiConn__getServerCharset() [INTERNAL]
//   Internal method used for retrieving the server character set. This is used
// to determine if any conversion is required when transferring strings between
// the client and the server.
//-----------------------------------------------------------------------------
static int dpiConn__getServerCharset(dpiConn *conn, dpiError *error)
{
    return dpiOci__attrGet(conn->serverHandle, DPI_OCI_HTYPE_SERVER,
            &conn->charsetId, NULL, DPI_OCI_ATTR_CHARSET_ID,
            "get server charset id", error);
}


//-----------------------------------------------------------------------------
// dpiConn__getServerVersion() [INTERNAL]
//   Internal method used for ensuring that the server version has been cached
// on the connection.
//-----------------------------------------------------------------------------
static int dpiConn__getServerVersion(dpiConn *conn, dpiError *error)
{
    uint32_t serverRelease;
    char buffer[512];

    // nothing to do if the server version has been determined earlier
    if (conn->releaseString)
        return DPI_SUCCESS;

    // get server version
    if (dpiOci__serverRelease(conn, buffer, sizeof(buffer), &serverRelease,
            error) < 0)
        return DPI_FAILURE;
    conn->releaseStringLength = (uint32_t) strlen(buffer);
    if (dpiUtils__allocateMemory(1, conn->releaseStringLength, 0,
            "allocate release string", (void**) &conn->releaseString,
            error) < 0)
        return DPI_FAILURE;
    strncpy( (char*) conn->releaseString, buffer, conn->releaseStringLength);
    conn->versionInfo.versionNum = (int)((serverRelease >> 24) & 0xFF);
    conn->versionInfo.releaseNum = (int)((serverRelease >> 20) & 0x0F);
    conn->versionInfo.updateNum = (int)((serverRelease >> 12) & 0xFF);
    conn->versionInfo.portReleaseNum = (int)((serverRelease >> 8) & 0x0F);
    conn->versionInfo.portUpdateNum = (int)((serverRelease) & 0xFF);
    conn->versionInfo.fullVersionNum = (uint32_t)
            DPI_ORACLE_VERSION_TO_NUMBER(conn->versionInfo.versionNum,
                    conn->versionInfo.releaseNum,
                    conn->versionInfo.updateNum,
                    conn->versionInfo.portReleaseNum,
                    conn->versionInfo.portUpdateNum);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiConn__getSession() [INTERNAL]
//   Ping and loop until we get a good session. When a database instance goes
// down, it can leave several bad connections that need to be flushed out
// before a good connection can be acquired. If the connection is brand new
// (ping time context value has not been set) there is no need to do a ping.
// This also ensures that the loop cannot run forever!
//   Note as well that this is only needed for clients less than 12.2. In the
// 12.2 release a much faster internal check is performed that makes these
// checks unnecessary.
//-----------------------------------------------------------------------------
static int dpiConn__getSession(dpiConn *conn, uint32_t mode,
        const char *connectString, uint32_t connectStringLength,
        dpiConnCreateParams *params, void *authInfo, dpiError *error)
{
    uint8_t savedBreakOnTimeout, breakOnTimeout;
    uint32_t savedTimeout;
    time_t *lastTimeUsed;

    while (1) {

        // acquire the new session
        if (dpiOci__sessionGet(conn->env->handle, &conn->handle, authInfo,
                connectString, connectStringLength, params->tag,
                params->tagLength, &params->outTag, &params->outTagLength,
                &params->outTagFound, mode, error) < 0)
            return DPI_FAILURE;

        // get session and server handles
        if (dpiConn__getHandles(conn, error) < 0)
            return DPI_FAILURE;

        // Oracle client 12.2 already has better support so do nothing in
        // that case
        if (conn->env->versionInfo->versionNum > 12 ||
                (conn->env->versionInfo->versionNum == 12 &&
                conn->env->versionInfo->releaseNum >= 2))
            break;

        // get last time used from session context
        lastTimeUsed = NULL;
        if (dpiOci__contextGetValue(conn, DPI_CONTEXT_LAST_TIME_USED,
                (uint32_t) (sizeof(DPI_CONTEXT_LAST_TIME_USED) - 1),
                (void**) &lastTimeUsed, 1, error) < 0)
            return DPI_FAILURE;

        // if value is not found, a new connection has been created and there
        // is no need to perform a ping; nor if we are creating a standalone
        // connection
        if (!lastTimeUsed || !conn->pool)
            break;

        // if ping interval is negative or the ping interval (in seconds)
        // has not been exceeded yet, there is also no need to perform a ping
        if (conn->pool->pingInterval < 0 ||
                *lastTimeUsed + conn->pool->pingInterval > time(NULL))
            break;

        // ping needs to be done at this point; set parameters to ensure that
        // the ping does not take too long to complete; keep original values
        dpiOci__attrGet(conn->serverHandle,
                DPI_OCI_HTYPE_SERVER, &savedTimeout, NULL,
                DPI_OCI_ATTR_RECEIVE_TIMEOUT, NULL, error);
        dpiOci__attrSet(conn->serverHandle, DPI_OCI_HTYPE_SERVER,
                &conn->pool->pingTimeout, 0, DPI_OCI_ATTR_RECEIVE_TIMEOUT,
                NULL, error);
        if (conn->env->versionInfo->versionNum >= 12) {
            dpiOci__attrGet(conn->serverHandle,
                    DPI_OCI_HTYPE_SERVER, &savedBreakOnTimeout, NULL,
                    DPI_OCI_ATTR_BREAK_ON_NET_TIMEOUT, NULL, error);
            breakOnTimeout = 0;
            dpiOci__attrSet(conn->serverHandle, DPI_OCI_HTYPE_SERVER,
                    &breakOnTimeout, 0, DPI_OCI_ATTR_BREAK_ON_NET_TIMEOUT,
                    NULL, error);
        }

        // if ping is successful, the connection is valid and can be returned
        // restore original network parameters
        if (dpiOci__ping(conn, error) == 0) {
            dpiOci__attrSet(conn->serverHandle, DPI_OCI_HTYPE_SERVER,
                    &savedTimeout, 0, DPI_OCI_ATTR_RECEIVE_TIMEOUT, NULL,
                    error);
            if (conn->env->versionInfo->versionNum >= 12)
                dpiOci__attrSet(conn->serverHandle, DPI_OCI_HTYPE_SERVER,
                        &savedBreakOnTimeout, 0,
                        DPI_OCI_ATTR_BREAK_ON_NET_TIMEOUT, NULL, error);
            break;
        }

        // session is bad, need to release and drop it
        dpiOci__sessionRelease(conn, NULL, 0, DPI_OCI_SESSRLS_DROPSESS, 0,
                error);
        conn->handle = NULL;
        conn->serverHandle = NULL;
        conn->sessionHandle = NULL;

    }

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiConn__setAppContext() [INTERNAL]
//   Populate the session handle with the application context.
//-----------------------------------------------------------------------------
static int dpiConn__setAppContext(void *handle, uint32_t handleType,
        const dpiConnCreateParams *params, dpiError *error)
{
    void *listHandle, *entryHandle;
    dpiAppContext *entry;
    uint32_t i;

    // set the number of application context entries
    if (dpiOci__attrSet(handle, handleType, (void*) &params->numAppContext,
            sizeof(params->numAppContext), DPI_OCI_ATTR_APPCTX_SIZE,
            "set app context size", error) < 0)
        return DPI_FAILURE;

    // get the application context list handle
    if (dpiOci__attrGet(handle, handleType, &listHandle, NULL,
            DPI_OCI_ATTR_APPCTX_LIST, "get context list handle", error) < 0)
        return DPI_FAILURE;

    // set each application context entry
    for (i = 0; i < params->numAppContext; i++) {
        entry = &params->appContext[i];

        // retrieve the context element descriptor
        if (dpiOci__paramGet(listHandle, DPI_OCI_DTYPE_PARAM,
                &entryHandle, i + 1, "get context entry handle", error) < 0)
            return DPI_FAILURE;

        // set the namespace name
        if (dpiOci__attrSet(entryHandle, DPI_OCI_DTYPE_PARAM,
                (void*) entry->namespaceName, entry->namespaceNameLength,
                DPI_OCI_ATTR_APPCTX_NAME, "set namespace name", error) < 0)
            return DPI_FAILURE;

        // set the name
        if (dpiOci__attrSet(entryHandle, DPI_OCI_DTYPE_PARAM,
                (void*) entry->name, entry->nameLength,
                DPI_OCI_ATTR_APPCTX_ATTR, "set name", error) < 0)
            return DPI_FAILURE;

        // set the value
        if (dpiOci__attrSet(entryHandle, DPI_OCI_DTYPE_PARAM,
                (void*) entry->value, entry->valueLength,
                DPI_OCI_ATTR_APPCTX_VALUE, "set value", error) < 0)
            return DPI_FAILURE;

    }

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiConn__setAttributesFromCreateParams() [INTERNAL]
//   Populate the authorization info structure or session handle using the
// create parameters specified.
//-----------------------------------------------------------------------------
static int dpiConn__setAttributesFromCreateParams(dpiConn *conn, void *handle,
        uint32_t handleType, const char *userName, uint32_t userNameLength,
        const char *password, uint32_t passwordLength,
        const dpiConnCreateParams *params, void **shardingKey,
        void **superShardingKey, dpiError *error)
{
    uint32_t purity;

    // set credentials
    if (userName && userNameLength > 0 && dpiOci__attrSet(handle,
            handleType, (void*) userName, userNameLength,
            DPI_OCI_ATTR_USERNAME, "set user name", error) < 0)
        return DPI_FAILURE;
    if (password && passwordLength > 0 && dpiOci__attrSet(handle,
            handleType, (void*) password, passwordLength,
            DPI_OCI_ATTR_PASSWORD, "set password", error) < 0)
        return DPI_FAILURE;

    // set connection class and purity parameters
    if (params->connectionClass && params->connectionClassLength > 0 &&
            dpiOci__attrSet(handle, handleType,
                    (void*) params->connectionClass,
                    params->connectionClassLength,
                    DPI_OCI_ATTR_CONNECTION_CLASS, "set connection class",
                    error) < 0)
        return DPI_FAILURE;
    if (params->purity != DPI_OCI_ATTR_PURITY_DEFAULT) {
        purity = params->purity;
        if (dpiOci__attrSet(handle, handleType, &purity,
                sizeof(purity), DPI_OCI_ATTR_PURITY, "set purity", error) < 0)
            return DPI_FAILURE;
    }

    // set sharding key and super sharding key parameters
    if (params->shardingKeyColumns && params->numShardingKeyColumns > 0) {
        if (dpiConn__setShardingKey(conn, shardingKey, handle, handleType,
                DPI_OCI_ATTR_SHARDING_KEY, "set sharding key",
                params->shardingKeyColumns, params->numShardingKeyColumns,
                error) < 0)
            return DPI_FAILURE;
    }
    if (params->superShardingKeyColumns &&
            params->numSuperShardingKeyColumns > 0) {
        if (dpiConn__setShardingKey(conn, superShardingKey, handle, handleType,
                DPI_OCI_ATTR_SUPER_SHARDING_KEY, "set super sharding key",
                params->superShardingKeyColumns,
                params->numSuperShardingKeyColumns, error) < 0)
            return DPI_FAILURE;
    }

    // set application context, if applicable
    if (handleType == DPI_OCI_HTYPE_SESSION && params->numAppContext > 0)
        return dpiConn__setAppContext(handle, handleType, params, error);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiConn__setAttributeText() [INTERNAL]
//   Set the value of the OCI attribute from a text string.
//-----------------------------------------------------------------------------
static int dpiConn__setAttributeText(dpiConn *conn, uint32_t attribute,
        const char *value, uint32_t valueLength, const char *fnName)
{
    dpiError error;
    int status;

    // validate parameters
    if (dpiConn__checkConnected(conn, fnName, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_AND_LENGTH(conn, value)

    // determine pointer to pass (OCI uses different sizes)
    switch (attribute) {
        case DPI_OCI_ATTR_ACTION:
        case DPI_OCI_ATTR_CLIENT_IDENTIFIER:
        case DPI_OCI_ATTR_CLIENT_INFO:
        case DPI_OCI_ATTR_CURRENT_SCHEMA:
        case DPI_OCI_ATTR_EDITION:
        case DPI_OCI_ATTR_MODULE:
        case DPI_OCI_ATTR_DBOP:
            status = dpiOci__attrSet(conn->sessionHandle,
                    DPI_OCI_HTYPE_SESSION, (void*) value, valueLength,
                    attribute, "set session value", &error);
            break;
        case DPI_OCI_ATTR_INTERNAL_NAME:
        case DPI_OCI_ATTR_EXTERNAL_NAME:
            status = dpiOci__attrSet(conn->serverHandle, DPI_OCI_HTYPE_SERVER,
                    (void*) value, valueLength, attribute, "set server value",
                    &error);
            break;
        default:
            status = dpiError__set(&error, "set attribute text",
                    DPI_ERR_NOT_SUPPORTED);
            break;
    }

    return dpiGen__endPublicFn(conn, status, &error);
}


//-----------------------------------------------------------------------------
// dpiConn__setShardingKey() [INTERNAL]
//   Using the specified columns, create a sharding key and set it on the given
// handle.
//-----------------------------------------------------------------------------
static int dpiConn__setShardingKey(dpiConn *conn, void **shardingKey,
        void *handle, uint32_t handleType, uint32_t attribute,
        const char *action, dpiShardingKeyColumn *columns, uint8_t numColumns,
        dpiError *error)
{
    uint8_t i;

    // this is only supported on 12.2 and higher clients
    if (conn->env->context->versionInfo->versionNum < 12 ||
            (conn->env->context->versionInfo->versionNum == 12 &&
             conn->env->context->versionInfo->releaseNum < 2))
        return dpiError__set(error, action, DPI_ERR_NOT_SUPPORTED);

    // create sharding key descriptor, if necessary
    if (dpiOci__descriptorAlloc(conn->env->handle, shardingKey,
            DPI_OCI_DTYPE_SHARDING_KEY, "allocate sharding key", error) < 0)
        return DPI_FAILURE;

    // add each column to the sharding key
    for (i = 0; i < numColumns; i++) {
        if (dpiConn__setShardingKeyValue(conn, *shardingKey, &columns[i],
                error) < 0)
            return DPI_FAILURE;
    }

    // add the sharding key to the handle
    if (dpiOci__attrSet(handle, handleType, *shardingKey, 0, attribute, action,
            error) < 0)
        return DPI_FAILURE;

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiConn__setShardingKeyValue() [INTERNAL]
//   Using the specified columns, create a sharding key and set it on the given
// handle.
//-----------------------------------------------------------------------------
static int dpiConn__setShardingKeyValue(dpiConn *conn, void *shardingKey,
        dpiShardingKeyColumn *column, dpiError *error)
{
    const dpiOracleType *oracleType;
    dpiOciNumber numberValue;
    dpiOciDate dateValue;
    uint32_t colLen = 0;
    void *col = NULL;
    uint16_t colType;
    int convertOk;

    oracleType = dpiOracleType__getFromNum(column->oracleTypeNum, error);
    if (!oracleType)
        return DPI_FAILURE;
    convertOk = 0;
    colType = oracleType->oracleType;
    switch (column->oracleTypeNum) {
        case DPI_ORACLE_TYPE_VARCHAR:
        case DPI_ORACLE_TYPE_CHAR:
        case DPI_ORACLE_TYPE_RAW:
            if (column->nativeTypeNum == DPI_NATIVE_TYPE_BYTES) {
                col = column->value.asBytes.ptr;
                colLen = column->value.asBytes.length;
                convertOk = 1;
            }
            break;
        case DPI_ORACLE_TYPE_NUMBER:
            col = &numberValue;
            colLen = sizeof(numberValue);
            if (column->nativeTypeNum == DPI_NATIVE_TYPE_DOUBLE) {
                if (dpiDataBuffer__toOracleNumberFromDouble(&column->value,
                        error, &numberValue) < 0)
                    return DPI_FAILURE;
                convertOk = 1;
            } else if (column->nativeTypeNum == DPI_NATIVE_TYPE_INT64) {
                if (dpiDataBuffer__toOracleNumberFromInteger(&column->value,
                        error, &numberValue) < 0)
                    return DPI_FAILURE;
                convertOk = 1;
            } else if (column->nativeTypeNum == DPI_NATIVE_TYPE_UINT64) {
                if (dpiDataBuffer__toOracleNumberFromUnsignedInteger(
                        &column->value, error, &numberValue) < 0)
                    return DPI_FAILURE;
                convertOk = 1;
            } else if (column->nativeTypeNum == DPI_NATIVE_TYPE_BYTES) {
                if (dpiDataBuffer__toOracleNumberFromText(&column->value,
                        conn->env, error, &numberValue) < 0)
                    return DPI_FAILURE;
                convertOk = 1;
            }
            break;
        case DPI_ORACLE_TYPE_DATE:
            col = &dateValue;
            colLen = sizeof(dateValue);
            colType = DPI_SQLT_DAT;
            if (column->nativeTypeNum == DPI_NATIVE_TYPE_TIMESTAMP) {
                if (dpiDataBuffer__toOracleDate(&column->value,
                        &dateValue) < 0)
                    return DPI_FAILURE;
                convertOk = 1;
            }
            break;
        default:
            break;
    }
    if (!convertOk)
        return dpiError__set(error, "check type", DPI_ERR_NOT_SUPPORTED);

    return dpiOci__shardingKeyColumnAdd(shardingKey, col, colLen, colType,
            error);
}


//-----------------------------------------------------------------------------
// dpiConn_addRef() [PUBLIC]
//   Add a reference to the connection.
//-----------------------------------------------------------------------------
int dpiConn_addRef(dpiConn *conn)
{
    return dpiGen__addRef(conn, DPI_HTYPE_CONN, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_beginDistribTrans() [PUBLIC]
//   Begin a distributed transaction.
//-----------------------------------------------------------------------------
int dpiConn_beginDistribTrans(dpiConn *conn, long formatId,
        const char *transactionId, uint32_t transactionIdLength,
        const char *branchId, uint32_t branchIdLength)
{
    void *transactionHandle;
    dpiError error;
    dpiOciXID xid;
    int status;

    // validate parameters
    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_AND_LENGTH(conn, transactionId)
    DPI_CHECK_PTR_AND_LENGTH(conn, branchId)
    if (transactionIdLength > DPI_XA_MAXGTRIDSIZE) {
        dpiError__set(&error, "check size of transaction id",
                DPI_ERR_TRANS_ID_TOO_LARGE, transactionIdLength,
                DPI_XA_MAXGTRIDSIZE);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }
    if (branchIdLength > DPI_XA_MAXBQUALSIZE) {
        dpiError__set(&error, "check size of branch id",
                DPI_ERR_BRANCH_ID_TOO_LARGE, branchIdLength,
                DPI_XA_MAXBQUALSIZE);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }

    // determine if a transaction handle was previously allocated
    if (dpiOci__attrGet(conn->handle, DPI_OCI_HTYPE_SVCCTX,
            (void*) &transactionHandle, NULL, DPI_OCI_ATTR_TRANS,
            "get transaction handle", &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);

    // if one was not found, create one and associate it with the connection
    if (!transactionHandle) {

        // create new handle
        if (dpiOci__handleAlloc(conn->env->handle, &transactionHandle,
                DPI_OCI_HTYPE_TRANS, "create transaction handle", &error) < 0)
            return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);

        // associate the transaction with the connection
        if (dpiOci__attrSet(conn->handle, DPI_OCI_HTYPE_SVCCTX,
                transactionHandle, 0, DPI_OCI_ATTR_TRANS,
                "associate transaction", &error) < 0) {
            dpiOci__handleFree(transactionHandle, DPI_OCI_HTYPE_TRANS);
            return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
        }

    }

    // set the XID for the transaction, if applicable
    if (formatId != -1) {
        xid.formatID = formatId;
        xid.gtrid_length = transactionIdLength;
        xid.bqual_length = branchIdLength;
        if (transactionIdLength > 0)
            strncpy(xid.data, transactionId, transactionIdLength);
        if (branchIdLength > 0)
            strncpy(&xid.data[transactionIdLength], branchId, branchIdLength);
        if (dpiOci__attrSet(transactionHandle, DPI_OCI_HTYPE_TRANS, &xid,
                sizeof(dpiOciXID), DPI_OCI_ATTR_XID, "set XID", &error) < 0)
            return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }

    // start the transaction
    status = dpiOci__transStart(conn, &error);
    return dpiGen__endPublicFn(conn, status, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_breakExecution() [PUBLIC]
//   Break (interrupt) the currently executing operation.
//-----------------------------------------------------------------------------
int dpiConn_breakExecution(dpiConn *conn)
{
    dpiError error;
    int status;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    status = dpiOci__break(conn, &error);
    return dpiGen__endPublicFn(conn, status, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_changePassword() [PUBLIC]
//   Change the password for the specified user.
//-----------------------------------------------------------------------------
int dpiConn_changePassword(dpiConn *conn, const char *userName,
        uint32_t userNameLength, const char *oldPassword,
        uint32_t oldPasswordLength, const char *newPassword,
        uint32_t newPasswordLength)
{
    dpiError error;
    int status;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_AND_LENGTH(conn, userName)
    DPI_CHECK_PTR_AND_LENGTH(conn, oldPassword)
    DPI_CHECK_PTR_AND_LENGTH(conn, newPassword)
    status = dpiOci__passwordChange(conn, userName, userNameLength,
            oldPassword, oldPasswordLength, newPassword, newPasswordLength,
            DPI_OCI_DEFAULT, &error);
    return dpiGen__endPublicFn(conn, status, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_close() [PUBLIC]
//   Close the connection and ensure it can no longer be used.
//-----------------------------------------------------------------------------
int dpiConn_close(dpiConn *conn, dpiConnCloseMode mode, const char *tag,
        uint32_t tagLength)
{
    int propagateErrors = !(mode & DPI_MODE_CONN_CLOSE_DROP);
    dpiError error;
    int closing;

    // validate parameters
    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_AND_LENGTH(conn, tag)
    if (mode && !conn->pool) {
        dpiError__set(&error, "check in pool", DPI_ERR_CONN_NOT_IN_POOL);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }
    if (conn->externalHandle) {
        dpiError__set(&error, "check external", DPI_ERR_CONN_IS_EXTERNAL);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }

    // determine whether connection is already being closed and if not, mark
    // connection as being closed; this MUST be done while holding the lock
    // (if in threaded mode) to avoid race conditions!
    if (conn->env->threaded)
        dpiMutex__acquire(conn->env->mutex);
    closing = conn->closing;
    conn->closing = 1;
    if (conn->env->threaded)
        dpiMutex__release(conn->env->mutex);

    // if connection is already being closed, raise an exception
    if (closing) {
        dpiError__set(&error, "check closing", DPI_ERR_NOT_CONNECTED);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }

    // if actual close fails, reset closing flag; again, this must be done
    // while holding the lock (if in threaded mode) in order to avoid race
    // conditions!
    if (dpiConn__close(conn, mode, tag, tagLength, propagateErrors,
            &error) < 0) {
        if (conn->env->threaded)
            dpiMutex__acquire(conn->env->mutex);
        conn->closing = 0;
        if (conn->env->threaded)
            dpiMutex__release(conn->env->mutex);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }

    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_commit() [PUBLIC]
//   Commit the transaction associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_commit(dpiConn *conn)
{
    dpiError error;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiOci__transCommit(conn, conn->commitMode, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    conn->commitMode = DPI_OCI_DEFAULT;
    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_create() [PUBLIC]
//   Create a standalone connection to the database using the parameters
// specified.
//-----------------------------------------------------------------------------
int dpiConn_create(const dpiContext *context, const char *userName,
        uint32_t userNameLength, const char *password, uint32_t passwordLength,
        const char *connectString, uint32_t connectStringLength,
        const dpiCommonCreateParams *commonParams,
        dpiConnCreateParams *createParams, dpiConn **conn)
{
    dpiCommonCreateParams localCommonParams;
    dpiConnCreateParams localCreateParams;
    dpiConn *tempConn;
    dpiError error;
    int status;

    // validate parameters
    if (dpiGen__startPublicFn(context, DPI_HTYPE_CONTEXT, __func__, 0,
            &error) < 0)
        return dpiGen__endPublicFn(context, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(context, conn)
    DPI_CHECK_PTR_AND_LENGTH(context, userName)
    DPI_CHECK_PTR_AND_LENGTH(context, password)
    DPI_CHECK_PTR_AND_LENGTH(context, connectString)

    // use default parameters if none provided
    if (!commonParams) {
        dpiContext__initCommonCreateParams(&localCommonParams);
        commonParams = &localCommonParams;
    }
    if (!createParams || context->dpiMinorVersion == 0) {
        dpiContext__initConnCreateParams(&localCreateParams);
        if (createParams)
            memcpy(&localCreateParams, createParams,
                    sizeof(dpiConnCreateParams__v20));
        createParams = &localCreateParams;
    }

    // ensure that username and password are not specified if external
    // authentication is desired
    if (createParams->externalAuth &&
            ((userName && userNameLength > 0) ||
             (password && passwordLength > 0))) {
        dpiError__set(&error, "check mixed credentials",
                DPI_ERR_EXT_AUTH_WITH_CREDENTIALS);
        return dpiGen__endPublicFn(context, DPI_FAILURE, &error);
    }

    // connectionClass and edition cannot be specified at the same time
    if (createParams->connectionClass &&
            createParams->connectionClassLength > 0 &&
            commonParams->edition && commonParams->editionLength > 0) {
        dpiError__set(&error, "check edition/conn class",
                DPI_ERR_NO_EDITION_WITH_CONN_CLASS);
        return dpiGen__endPublicFn(context, DPI_FAILURE, &error);
    }

    // newPassword and edition cannot be specified at the same time
    if (createParams->newPassword && createParams->newPasswordLength > 0 &&
            commonParams->edition && commonParams->editionLength > 0) {
        dpiError__set(&error, "check edition/new password",
                DPI_ERR_NO_EDITION_WITH_NEW_PASSWORD);
        return dpiGen__endPublicFn(context, DPI_FAILURE, &error);
    }

    // handle case where pool is specified
    if (createParams->pool) {
        if (dpiGen__checkHandle(createParams->pool, DPI_HTYPE_POOL,
                "verify pool", &error) < 0)
            return dpiGen__endPublicFn(context, DPI_FAILURE, &error);
        if (!createParams->pool->handle) {
            dpiError__set(&error, "check pool", DPI_ERR_NOT_CONNECTED);
            return dpiGen__endPublicFn(context, DPI_FAILURE, &error);
        }
        if (dpiEnv__initError(createParams->pool->env, &error) < 0)
            return dpiGen__endPublicFn(context, DPI_FAILURE, &error);
        status = dpiPool__acquireConnection(createParams->pool, userName,
                userNameLength, password, passwordLength, createParams, conn,
                &error);
        return dpiGen__endPublicFn(context, status, &error);
    }

    // create connection
    if (dpiGen__allocate(DPI_HTYPE_CONN, NULL, (void**) &tempConn, &error) < 0)
        return dpiGen__endPublicFn(context, DPI_FAILURE, &error);
    if (dpiConn__create(tempConn, context, userName, userNameLength,
            password, passwordLength, connectString, connectStringLength,
            NULL, commonParams, createParams, &error) < 0) {
        dpiConn__free(tempConn, &error);
        return dpiGen__endPublicFn(context, DPI_FAILURE, &error);
    }

    *conn = tempConn;
    dpiHandlePool__release(tempConn->env->errorHandles, error.handle, &error);
    error.handle = NULL;
    return dpiGen__endPublicFn(context, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_deqObject() [PUBLIC]
//   Dequeue a message from the specified queue.
//-----------------------------------------------------------------------------
int dpiConn_deqObject(dpiConn *conn, const char *queueName,
        uint32_t queueNameLength, dpiDeqOptions *options, dpiMsgProps *props,
        dpiObject *payload, const char **msgId, uint32_t *msgIdLength)
{
    void *ociMsgId = NULL;
    dpiError error;

    // validate parameters
    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiGen__checkHandle(options, DPI_HTYPE_DEQ_OPTIONS, "verify options",
            &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiGen__checkHandle(props, DPI_HTYPE_MSG_PROPS,
            "verify message properties", &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiGen__checkHandle(payload, DPI_HTYPE_OBJECT, "verify payload",
            &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_AND_LENGTH(conn, queueName)
    DPI_CHECK_PTR_NOT_NULL(conn, msgId)
    DPI_CHECK_PTR_NOT_NULL(conn, msgIdLength)

    // dequeue message
    if (dpiOci__aqDeq(conn, queueName, options->handle, props->handle,
            payload->type->tdo, &payload->instance, &payload->indicator,
            &ociMsgId, &error) < 0) {
        if (error.buffer->code == 25228) {
            if (ociMsgId)
                dpiOci__rawResize(conn->env->handle, &ociMsgId, 0, &error);
            *msgId = NULL;
            *msgIdLength = 0;
            return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
        }
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }
    if (dpiMsgProps__extractMsgId(props, ociMsgId, msgId, msgIdLength,
            &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_enqObject() [PUBLIC]
//   Enqueue a message to the specified queue.
//-----------------------------------------------------------------------------
int dpiConn_enqObject(dpiConn *conn, const char *queueName,
        uint32_t queueNameLength, dpiEnqOptions *options, dpiMsgProps *props,
        dpiObject *payload, const char **msgId, uint32_t *msgIdLength)
{
    void *ociMsgId = NULL;
    dpiError error;

    // validate parameters
    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiGen__checkHandle(options, DPI_HTYPE_ENQ_OPTIONS, "verify options",
            &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiGen__checkHandle(props, DPI_HTYPE_MSG_PROPS,
            "verify message properties", &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiGen__checkHandle(payload, DPI_HTYPE_OBJECT, "verify payload",
            &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_AND_LENGTH(conn, queueName)
    DPI_CHECK_PTR_NOT_NULL(conn, msgId)
    DPI_CHECK_PTR_NOT_NULL(conn, msgIdLength)

    // enqueue message
    if (dpiOci__aqEnq(conn, queueName, options->handle, props->handle,
            payload->type->tdo, &payload->instance, &payload->indicator,
            &ociMsgId, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiMsgProps__extractMsgId(props, ociMsgId, msgId, msgIdLength,
            &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_getCurrentSchema() [PUBLIC]
//   Return the current schema associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_getCurrentSchema(dpiConn *conn, const char **value,
        uint32_t *valueLength)
{
    return dpiConn__getAttributeText(conn, DPI_OCI_ATTR_CURRENT_SCHEMA, value,
            valueLength, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_getEdition() [PUBLIC]
//   Return the edition associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_getEdition(dpiConn *conn, const char **value,
        uint32_t *valueLength)
{
    return dpiConn__getAttributeText(conn, DPI_OCI_ATTR_EDITION, value,
            valueLength, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_getEncodingInfo() [PUBLIC]
//   Get the encodings from the connection.
//-----------------------------------------------------------------------------
int dpiConn_getEncodingInfo(dpiConn *conn, dpiEncodingInfo *info)
{
    dpiError error;
    int status;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    status = dpiEnv__getEncodingInfo(conn->env, info);
    return dpiGen__endPublicFn(conn, status, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_getExternalName() [PUBLIC]
//   Return the external name associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_getExternalName(dpiConn *conn, const char **value,
        uint32_t *valueLength)
{
    return dpiConn__getAttributeText(conn, DPI_OCI_ATTR_EXTERNAL_NAME, value,
            valueLength, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_getHandle() [PUBLIC]
//   Get the OCI service context handle associated with the connection. This is
// available in order to allow for extensions to the library using OCI
// directly.
//-----------------------------------------------------------------------------
int dpiConn_getHandle(dpiConn *conn, void **handle)
{
    dpiError error;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(conn, handle)
    *handle = conn->handle;
    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_getInternalName() [PUBLIC]
//   Return the internal name associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_getInternalName(dpiConn *conn, const char **value,
        uint32_t *valueLength)
{
    return dpiConn__getAttributeText(conn, DPI_OCI_ATTR_INTERNAL_NAME, value,
            valueLength, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_getLTXID() [PUBLIC]
//   Return the logical transaction id associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_getLTXID(dpiConn *conn, const char **value, uint32_t *valueLength)
{
    return dpiConn__getAttributeText(conn, DPI_OCI_ATTR_LTXID, value,
            valueLength, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_getObjectType() [PUBLIC]
//   Look up an object type given its name and return it.
//-----------------------------------------------------------------------------
int dpiConn_getObjectType(dpiConn *conn, const char *name, uint32_t nameLength,
        dpiObjectType **objType)
{
    void *describeHandle, *param, *tdo;
    int status, useTypeByFullName;
    dpiError error;

    // validate parameters
    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(conn, name)
    DPI_CHECK_PTR_NOT_NULL(conn, objType)

    // allocate describe handle
    if (dpiOci__handleAlloc(conn->env->handle, &describeHandle,
            DPI_OCI_HTYPE_DESCRIBE, "allocate describe handle", &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);

    // Oracle Client 12.1 is capable of using OCITypeByFullName() but will
    // fail if accessing an Oracle 11.2 database
    useTypeByFullName = 1;
    if (conn->env->versionInfo->versionNum < 12)
        useTypeByFullName = 0;
    else if (dpiConn__getServerVersion(conn, &error) < 0)
        return DPI_FAILURE;
    else if (conn->versionInfo.versionNum < 12)
        useTypeByFullName = 0;

    // new API is supported so use it
    if (useTypeByFullName) {
        if (dpiOci__typeByFullName(conn, name, nameLength, &tdo, &error) < 0) {
            dpiOci__handleFree(describeHandle, DPI_OCI_HTYPE_DESCRIBE);
            return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
        }
        if (dpiOci__describeAny(conn, tdo, 0, DPI_OCI_OTYPE_PTR,
                describeHandle, &error) < 0) {
            dpiOci__handleFree(describeHandle, DPI_OCI_HTYPE_DESCRIBE);
            return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
        }

    // use older API
    } else {
        if (dpiOci__describeAny(conn, (void*) name, nameLength,
                DPI_OCI_OTYPE_NAME, describeHandle, &error) < 0) {
            dpiOci__handleFree(describeHandle, DPI_OCI_HTYPE_DESCRIBE);
            return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
        }
    }

    // get the parameter handle
    if (dpiOci__attrGet(describeHandle,
            DPI_OCI_HTYPE_DESCRIBE, &param, 0, DPI_OCI_ATTR_PARAM,
            "get param", &error) < 0) {
        dpiOci__handleFree(describeHandle, DPI_OCI_HTYPE_DESCRIBE);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }

    // create object type
    status = dpiObjectType__allocate(conn, param, DPI_OCI_ATTR_NAME, objType,
            &error);
    dpiOci__handleFree(describeHandle, DPI_OCI_HTYPE_DESCRIBE);
    return dpiGen__endPublicFn(conn, status, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_getServerVersion() [PUBLIC]
//   Get the server version string from the database.
//-----------------------------------------------------------------------------
int dpiConn_getServerVersion(dpiConn *conn, const char **releaseString,
        uint32_t *releaseStringLength, dpiVersionInfo *versionInfo)
{
    dpiError error;

    // validate parameters
    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(conn, releaseString)
    DPI_CHECK_PTR_NOT_NULL(conn, releaseStringLength)
    DPI_CHECK_PTR_NOT_NULL(conn, versionInfo)

    // get server version
    if (dpiConn__getServerVersion(conn, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    *releaseString = conn->releaseString;
    *releaseStringLength = conn->releaseStringLength;
    memcpy(versionInfo, &conn->versionInfo, sizeof(dpiVersionInfo));
    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_getStmtCacheSize() [PUBLIC]
//   Return the current size of the statement cache.
//-----------------------------------------------------------------------------
int dpiConn_getStmtCacheSize(dpiConn *conn, uint32_t *cacheSize)
{
    dpiError error;
    int status;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(conn, cacheSize)
    status = dpiOci__attrGet(conn->handle, DPI_OCI_HTYPE_SVCCTX, cacheSize,
            NULL, DPI_OCI_ATTR_STMTCACHESIZE, "get stmt cache size", &error);
    return dpiGen__endPublicFn(conn, status, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_newDeqOptions() [PUBLIC]
//   Create a new dequeue options object and return it.
//-----------------------------------------------------------------------------
int dpiConn_newDeqOptions(dpiConn *conn, dpiDeqOptions **options)
{
    dpiDeqOptions *tempOptions;
    dpiError error;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(conn, options)
    if (dpiGen__allocate(DPI_HTYPE_DEQ_OPTIONS, conn->env,
            (void**) &tempOptions, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiDeqOptions__create(tempOptions, conn, &error) < 0) {
        dpiDeqOptions__free(tempOptions, &error);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }

    *options = tempOptions;
    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_newEnqOptions() [PUBLIC]
//   Create a new enqueue options object and return it.
//-----------------------------------------------------------------------------
int dpiConn_newEnqOptions(dpiConn *conn, dpiEnqOptions **options)
{
    dpiEnqOptions *tempOptions;
    dpiError error;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(conn, options)
    if (dpiGen__allocate(DPI_HTYPE_ENQ_OPTIONS, conn->env,
            (void**) &tempOptions, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiEnqOptions__create(tempOptions, conn, &error) < 0) {
        dpiEnqOptions__free(tempOptions, &error);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }

    *options = tempOptions;
    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_newTempLob() [PUBLIC]
//   Create a new temporary LOB and return it.
//-----------------------------------------------------------------------------
int dpiConn_newTempLob(dpiConn *conn, dpiOracleTypeNum lobType, dpiLob **lob)
{
    const dpiOracleType *type;
    dpiLob *tempLob;
    dpiError error;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(conn, lob)
    switch (lobType) {
        case DPI_ORACLE_TYPE_CLOB:
        case DPI_ORACLE_TYPE_BLOB:
        case DPI_ORACLE_TYPE_NCLOB:
            type = dpiOracleType__getFromNum(lobType, &error);
            break;
        default:
            dpiError__set(&error, "check lob type",
                    DPI_ERR_INVALID_ORACLE_TYPE, lobType);
            return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }
    if (dpiLob__allocate(conn, type, &tempLob, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiOci__lobCreateTemporary(tempLob, &error) < 0) {
        dpiLob__free(tempLob, &error);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }

    *lob = tempLob;
    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_newMsgProps() [PUBLIC]
//   Create a new message properties object and return it.
//-----------------------------------------------------------------------------
int dpiConn_newMsgProps(dpiConn *conn, dpiMsgProps **props)
{
    dpiMsgProps *tempProps;
    dpiError error;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(conn, props)
    if (dpiGen__allocate(DPI_HTYPE_MSG_PROPS, conn->env, (void**) &tempProps,
            &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiMsgProps__create(tempProps, conn, &error) < 0) {
        dpiMsgProps__free(tempProps, &error);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }

    *props = tempProps;
    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_newSubscription() [PUBLIC]
//   Create a new subscription and return it.
//-----------------------------------------------------------------------------
int dpiConn_newSubscription(dpiConn *conn, dpiSubscrCreateParams *params,
        dpiSubscr **subscr, uint64_t *subscrId)
{
    dpiSubscr *tempSubscr;
    dpiError error;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(conn, params)
    DPI_CHECK_PTR_NOT_NULL(conn, subscr)
    if (dpiGen__allocate(DPI_HTYPE_SUBSCR, conn->env, (void**) &tempSubscr,
            &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiSubscr__create(tempSubscr, conn, params, subscrId, &error) < 0) {
        dpiSubscr__free(tempSubscr, &error);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }

    *subscr = tempSubscr;
    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_newVar() [PUBLIC]
//   Create a new variable and return it.
//-----------------------------------------------------------------------------
int dpiConn_newVar(dpiConn *conn, dpiOracleTypeNum oracleTypeNum,
        dpiNativeTypeNum nativeTypeNum, uint32_t maxArraySize, uint32_t size,
        int sizeIsBytes, int isArray, dpiObjectType *objType, dpiVar **var,
        dpiData **data)
{
    dpiError error;
    int status;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(conn, var)
    DPI_CHECK_PTR_NOT_NULL(conn, data)
    status = dpiVar__allocate(conn, oracleTypeNum, nativeTypeNum, maxArraySize,
            size, sizeIsBytes, isArray, objType, var, data, &error);
    return dpiGen__endPublicFn(conn, status, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_ping() [PUBLIC]
//   Makes a round trip call to the server to confirm that the connection and
// server are still active.
//-----------------------------------------------------------------------------
int dpiConn_ping(dpiConn *conn)
{
    dpiError error;
    int status;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    status = dpiOci__ping(conn, &error);
    return dpiGen__endPublicFn(conn, status, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_prepareDistribTrans() [PUBLIC]
//   Prepare a distributed transaction for commit. A boolean is returned
// indicating if a commit is actually needed as an attempt to perform a commit
// when nothing is actually prepared results in ORA-24756 (transaction does not
// exist). This is determined by the return value from OCITransPrepare() which
// is OCI_SUCCESS_WITH_INFO if there is no transaction requiring commit.
//-----------------------------------------------------------------------------
int dpiConn_prepareDistribTrans(dpiConn *conn, int *commitNeeded)
{
    dpiError error;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(conn, commitNeeded)
    if (dpiOci__transPrepare(conn, commitNeeded, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (*commitNeeded)
        conn->commitMode = DPI_OCI_TRANS_TWOPHASE;
    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_prepareStmt() [PUBLIC]
//   Create a new statement and return it after preparing the specified SQL.
//-----------------------------------------------------------------------------
int dpiConn_prepareStmt(dpiConn *conn, int scrollable, const char *sql,
        uint32_t sqlLength, const char *tag, uint32_t tagLength,
        dpiStmt **stmt)
{
    dpiStmt *tempStmt;
    dpiError error;

    *stmt = NULL;
    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_AND_LENGTH(conn, sql)
    DPI_CHECK_PTR_AND_LENGTH(conn, tag)
    DPI_CHECK_PTR_NOT_NULL(conn, stmt)
    if (dpiStmt__allocate(conn, scrollable, &tempStmt, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiStmt__prepare(tempStmt, sql, sqlLength, tag, tagLength,
            &error) < 0) {
        dpiStmt__free(tempStmt, &error);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }
    *stmt = tempStmt;
    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_release() [PUBLIC]
//   Release a reference to the connection.
//-----------------------------------------------------------------------------
int dpiConn_release(dpiConn *conn)
{
    return dpiGen__release(conn, DPI_HTYPE_CONN, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_rollback() [PUBLIC]
//   Rollback the transaction associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_rollback(dpiConn *conn)
{
    dpiError error;
    int status;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    status = dpiOci__transRollback(conn, 1, &error);
    return dpiGen__endPublicFn(conn, status, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_setAction() [PUBLIC]
//   Set the action associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_setAction(dpiConn *conn, const char *value, uint32_t valueLength)
{
    return dpiConn__setAttributeText(conn, DPI_OCI_ATTR_ACTION, value,
            valueLength, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_setClientIdentifier() [PUBLIC]
//   Set the client identifier associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_setClientIdentifier(dpiConn *conn, const char *value,
        uint32_t valueLength)
{
    return dpiConn__setAttributeText(conn, DPI_OCI_ATTR_CLIENT_IDENTIFIER,
            value, valueLength, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_setClientInfo() [PUBLIC]
//   Set the client info associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_setClientInfo(dpiConn *conn, const char *value,
        uint32_t valueLength)
{
    return dpiConn__setAttributeText(conn, DPI_OCI_ATTR_CLIENT_INFO, value,
            valueLength, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_setCurrentSchema() [PUBLIC]
//   Set the current schema associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_setCurrentSchema(dpiConn *conn, const char *value,
        uint32_t valueLength)
{
    return dpiConn__setAttributeText(conn, DPI_OCI_ATTR_CURRENT_SCHEMA, value,
            valueLength, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_setDbOp() [PUBLIC]
//   Set the database operation associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_setDbOp(dpiConn *conn, const char *value, uint32_t valueLength)
{
    return dpiConn__setAttributeText(conn, DPI_OCI_ATTR_DBOP, value,
            valueLength, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_setExternalName() [PUBLIC]
//   Set the external name associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_setExternalName(dpiConn *conn, const char *value,
        uint32_t valueLength)
{
    return dpiConn__setAttributeText(conn, DPI_OCI_ATTR_EXTERNAL_NAME, value,
            valueLength, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_setInternalName() [PUBLIC]
//   Set the internal name associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_setInternalName(dpiConn *conn, const char *value,
        uint32_t valueLength)
{
    return dpiConn__setAttributeText(conn, DPI_OCI_ATTR_INTERNAL_NAME, value,
            valueLength, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_setModule() [PUBLIC]
//   Set the module associated with the connection.
//-----------------------------------------------------------------------------
int dpiConn_setModule(dpiConn *conn, const char *value, uint32_t valueLength)
{
    return dpiConn__setAttributeText(conn, DPI_OCI_ATTR_MODULE, value,
            valueLength, __func__);
}


//-----------------------------------------------------------------------------
// dpiConn_setStmtCacheSize() [PUBLIC]
//   Set the size of the statement cache.
//-----------------------------------------------------------------------------
int dpiConn_setStmtCacheSize(dpiConn *conn, uint32_t cacheSize)
{
    dpiError error;
    int status;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    status = dpiOci__attrSet(conn->handle, DPI_OCI_HTYPE_SVCCTX, &cacheSize, 0,
            DPI_OCI_ATTR_STMTCACHESIZE, "set stmt cache size", &error);
    return dpiGen__endPublicFn(conn, status, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_shutdownDatabase() [PUBLIC]
//   Shutdown the database. Note that this must be done in two phases except in
// the situation where the instance is being aborted.
//-----------------------------------------------------------------------------
int dpiConn_shutdownDatabase(dpiConn *conn, dpiShutdownMode mode)
{
    dpiError error;
    int status;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    status = dpiOci__dbShutdown(conn, mode, &error);
    return dpiGen__endPublicFn(conn, status, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_startupDatabase() [PUBLIC]
//   Startup the database. This is equivalent to "startup nomount" in SQL*Plus.
//-----------------------------------------------------------------------------
int dpiConn_startupDatabase(dpiConn *conn, dpiStartupMode mode)
{
    dpiError error;
    int status;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    status = dpiOci__dbStartup(conn, mode, &error);
    return dpiGen__endPublicFn(conn, status, &error);
}

//-----------------------------------------------------------------------------
// dpiConn_subscribe() [PUBLIC]
//   Subscribe to events in the database. A subscription is created and
// returned. This replaces dpiConn_newSubscription().
//-----------------------------------------------------------------------------
int dpiConn_subscribe(dpiConn *conn, dpiSubscrCreateParams *params,
        dpiSubscr **subscr)
{
    dpiSubscr *tempSubscr;
    dpiError error;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(conn, params)
    DPI_CHECK_PTR_NOT_NULL(conn, subscr)
    if (!conn->env->events) {
        dpiError__set(&error, "subscribe", DPI_ERR_EVENTS_MODE_REQUIRED);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }
    if (dpiGen__allocate(DPI_HTYPE_SUBSCR, conn->env, (void**) &tempSubscr,
            &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiSubscr__create(tempSubscr, conn, params, NULL, &error) < 0) {
        dpiSubscr__free(tempSubscr, &error);
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    }

    *subscr = tempSubscr;
    return dpiGen__endPublicFn(conn, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiConn_unsubscribe() [PUBLIC]
//   Unsubscribe from events in the database. Once this call completes
// successfully no further notifications will be sent.
//-----------------------------------------------------------------------------
int dpiConn_unsubscribe(dpiConn *conn, dpiSubscr *subscr)
{
    dpiError error;

    if (dpiConn__checkConnected(conn, __func__, &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (dpiGen__checkHandle(subscr, DPI_HTYPE_SUBSCR, "check subscription",
            &error) < 0)
        return dpiGen__endPublicFn(conn, DPI_FAILURE, &error);
    if (subscr->registered) {
        if (dpiOci__subscriptionUnRegister(conn, subscr, &error) < 0)
            return dpiGen__endPublicFn(subscr, DPI_FAILURE, &error);
        subscr->registered = 0;
    }

    dpiGen__setRefCount(subscr, &error, -1);
    return dpiGen__endPublicFn(subscr, DPI_SUCCESS, &error);
}

