//-----------------------------------------------------------------------------
// Copyright (c) 2016, 2019, 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)
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// dpiPool.c
//   Implementation of session pools.
//-----------------------------------------------------------------------------

#include "dpiImpl.h"

//-----------------------------------------------------------------------------
// dpiPool__acquireConnection() [INTERNAL]
//   Internal method used for acquiring a connection from a pool.
//-----------------------------------------------------------------------------
int dpiPool__acquireConnection(dpiPool *pool, const char *userName,
        uint32_t userNameLength, const char *password, uint32_t passwordLength,
        dpiConnCreateParams *params, dpiConn **conn, dpiError *error)
{
    dpiConn *tempConn;

    // allocate new connection
    if (dpiGen__allocate(DPI_HTYPE_CONN, pool->env, (void**) &tempConn,
            error) < 0)
        return DPI_FAILURE;
    error->env = pool->env;

    // create the connection
    if (dpiConn__create(tempConn, pool->env->context, userName, userNameLength,
            password, passwordLength, pool->name, pool->nameLength, pool,
            NULL, params, error) < 0) {
        dpiConn__free(tempConn, error);
        return DPI_FAILURE;
    }

    *conn = tempConn;
    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiPool__checkConnected() [INTERNAL]
//   Determine if the session pool is connected to the database. If not, an
// error is raised.
//-----------------------------------------------------------------------------
static int dpiPool__checkConnected(dpiPool *pool, const char *fnName,
        dpiError *error)
{
    if (dpiGen__startPublicFn(pool, DPI_HTYPE_POOL, fnName, error) < 0)
        return DPI_FAILURE;
    if (!pool->handle)
        return dpiError__set(error, "check pool", DPI_ERR_NOT_CONNECTED);
    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiPool__create() [INTERNAL]
//   Internal method for creating a session pool.
//-----------------------------------------------------------------------------
static int dpiPool__create(dpiPool *pool, const char *userName,
        uint32_t userNameLength, const char *password, uint32_t passwordLength,
        const char *connectString, uint32_t connectStringLength,
        const dpiCommonCreateParams *commonParams,
        dpiPoolCreateParams *createParams, dpiError *error)
{
    uint32_t poolMode;
    uint8_t getMode;
    void *authInfo;

    // validate parameters
    if (createParams->externalAuth &&
            ((userName && userNameLength > 0) ||
             (password && passwordLength > 0)))
        return dpiError__set(error, "check mixed credentials",
                DPI_ERR_EXT_AUTH_WITH_CREDENTIALS);

    // create the session pool handle
    if (dpiOci__handleAlloc(pool->env->handle, &pool->handle,
            DPI_OCI_HTYPE_SPOOL, "allocate pool handle", error) < 0)
        return DPI_FAILURE;

    // prepare pool mode
    poolMode = DPI_OCI_SPC_STMTCACHE;
    if (createParams->homogeneous)
        poolMode |= DPI_OCI_SPC_HOMOGENEOUS;

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

    // set context attributes
    if (dpiUtils__setAttributesFromCommonCreateParams(authInfo,
            DPI_OCI_HTYPE_AUTHINFO, commonParams, error) < 0)
        return DPI_FAILURE;

    // set PL/SQL session state fixup callback, if applicable
    if (createParams->plsqlFixupCallback &&
            createParams->plsqlFixupCallbackLength > 0) {
        if (dpiUtils__checkClientVersion(pool->env->versionInfo, 12, 2,
                error) < 0)
            return DPI_FAILURE;
        if (dpiOci__attrSet(authInfo, DPI_OCI_HTYPE_AUTHINFO,
                    (void*) createParams->plsqlFixupCallback,
                    createParams->plsqlFixupCallbackLength,
                    DPI_OCI_ATTR_FIXUP_CALLBACK,
                    "set PL/SQL session state fixup callback", error) < 0)
            return DPI_FAILURE;
    }

    // set authorization info on session pool
    if (dpiOci__attrSet(pool->handle, DPI_OCI_HTYPE_SPOOL, (void*) authInfo, 0,
            DPI_OCI_ATTR_SPOOL_AUTH, "set auth info", error) < 0)
        return DPI_FAILURE;

    // set the get mode on the pool
    getMode = (uint8_t) createParams->getMode;
    if (dpiOci__attrSet(pool->handle, DPI_OCI_HTYPE_SPOOL, (void*) &getMode, 0,
            DPI_OCI_ATTR_SPOOL_GETMODE, "set get mode", error) < 0)
        return DPI_FAILURE;

    // set the session timeout on the pool
    if (dpiOci__attrSet(pool->handle, DPI_OCI_HTYPE_SPOOL, (void*)
            &createParams->timeout, 0, DPI_OCI_ATTR_SPOOL_TIMEOUT,
            "set timeout", error) < 0)
        return DPI_FAILURE;

    // set the wait timeout on the pool (valid in 12.2 and higher)
    if (pool->env->versionInfo->versionNum > 12 ||
            (pool->env->versionInfo->versionNum == 12 &&
             pool->env->versionInfo->releaseNum >= 2)) {
        if (dpiOci__attrSet(pool->handle, DPI_OCI_HTYPE_SPOOL, (void*)
                &createParams->waitTimeout, 0, DPI_OCI_ATTR_SPOOL_WAIT_TIMEOUT,
                "set wait timeout", error) < 0)
            return DPI_FAILURE;
    }

    // set the maximum lifetime session on the pool (valid in 12.1 and higher)
    if (pool->env->versionInfo->versionNum >= 12) {
        if (dpiOci__attrSet(pool->handle, DPI_OCI_HTYPE_SPOOL, (void*)
                &createParams->maxLifetimeSession, 0,
                DPI_OCI_ATTR_SPOOL_MAX_LIFETIME_SESSION,
                "set max lifetime session", error) < 0)
            return DPI_FAILURE;
    }

    // set the maximum number of sessions per shard (valid in 18.3 and higher)
    if (pool->env->versionInfo->versionNum > 18 ||
            (pool->env->versionInfo->versionNum == 18 &&
             pool->env->versionInfo->releaseNum >= 3)) {
        if (dpiOci__attrSet(pool->handle, DPI_OCI_HTYPE_SPOOL, (void*)
                &createParams->maxSessionsPerShard, 0,
                DPI_OCI_ATTR_SPOOL_MAX_PER_SHARD,
                "set max sessions per shard", error) < 0)
            return DPI_FAILURE;
    }

    // create pool
    if (dpiOci__sessionPoolCreate(pool, connectString, connectStringLength,
            createParams->minSessions, createParams->maxSessions,
            createParams->sessionIncrement, userName, userNameLength, password,
            passwordLength, poolMode, error) < 0)
        return DPI_FAILURE;

    // set reamining attributes directly
    pool->homogeneous = createParams->homogeneous;
    pool->externalAuth = createParams->externalAuth;
    pool->pingInterval = createParams->pingInterval;
    pool->pingTimeout = createParams->pingTimeout;

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiPool__free() [INTERNAL]
//   Free any memory associated with the pool.
//-----------------------------------------------------------------------------
void dpiPool__free(dpiPool *pool, dpiError *error)
{
    if (pool->handle) {
        dpiOci__sessionPoolDestroy(pool, DPI_OCI_SPD_FORCE, 0, error);
        pool->handle = NULL;
    }
    if (pool->env) {
        dpiEnv__free(pool->env, error);
        pool->env = NULL;
    }
    dpiUtils__freeMemory(pool);
}


//-----------------------------------------------------------------------------
// dpiPool__getAttributeUint() [INTERNAL]
//   Return the value of the attribute as an unsigned integer.
//-----------------------------------------------------------------------------
static int dpiPool__getAttributeUint(dpiPool *pool, uint32_t attribute,
        uint32_t *value, const char *fnName)
{
    int status, supported = 1;
    dpiError error;

    if (dpiPool__checkConnected(pool, fnName, &error) < 0)
        return dpiGen__endPublicFn(pool, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(pool, value)
    switch (attribute) {
        case DPI_OCI_ATTR_SPOOL_MAX_LIFETIME_SESSION:
            if (pool->env->versionInfo->versionNum < 12)
                supported = 0;
            break;
        case DPI_OCI_ATTR_SPOOL_WAIT_TIMEOUT:
            if (pool->env->versionInfo->versionNum < 12 ||
                    (pool->env->versionInfo->versionNum == 12 &&
                     pool->env->versionInfo->releaseNum < 2))
                supported = 0;
            break;
        case DPI_OCI_ATTR_SPOOL_BUSY_COUNT:
        case DPI_OCI_ATTR_SPOOL_OPEN_COUNT:
        case DPI_OCI_ATTR_SPOOL_STMTCACHESIZE:
        case DPI_OCI_ATTR_SPOOL_TIMEOUT:
            break;
        default:
            supported = 0;
            break;
    }
    if (supported)
        status = dpiOci__attrGet(pool->handle, DPI_OCI_HTYPE_SPOOL, value,
                NULL, attribute, "get attribute value", &error);
    else status = dpiError__set(&error, "get attribute value",
            DPI_ERR_NOT_SUPPORTED);
    return dpiGen__endPublicFn(pool, status, &error);
}


//-----------------------------------------------------------------------------
// dpiPool__setAttributeUint() [INTERNAL]
//   Set the value of the OCI attribute as an unsigned integer.
//-----------------------------------------------------------------------------
static int dpiPool__setAttributeUint(dpiPool *pool, uint32_t attribute,
        uint32_t value, const char *fnName)
{
    int status, supported = 1;
    void *ociValue = &value;
    uint8_t shortValue;
    dpiError error;

    // make sure session pool is connected
    if (dpiPool__checkConnected(pool, fnName, &error) < 0)
        return dpiGen__endPublicFn(pool, DPI_FAILURE, &error);

    // determine pointer to pass (OCI uses different sizes)
    switch (attribute) {
        case DPI_OCI_ATTR_SPOOL_GETMODE:
            shortValue = (uint8_t) value;
            ociValue = &shortValue;
            break;
        case DPI_OCI_ATTR_SPOOL_MAX_LIFETIME_SESSION:
            if (pool->env->versionInfo->versionNum < 12)
                supported = 0;
            break;
        case DPI_OCI_ATTR_SPOOL_WAIT_TIMEOUT:
            if (pool->env->versionInfo->versionNum < 12 ||
                    (pool->env->versionInfo->versionNum == 12 &&
                     pool->env->versionInfo->releaseNum < 2))
                supported = 0;
            break;
        case DPI_OCI_ATTR_SPOOL_STMTCACHESIZE:
        case DPI_OCI_ATTR_SPOOL_TIMEOUT:
            break;
        default:
            supported = 0;
            break;
    }

    // set value in the OCI
    if (supported)
        status = dpiOci__attrSet(pool->handle, DPI_OCI_HTYPE_SPOOL, ociValue,
                0, attribute, "set attribute value", &error);
    else status = dpiError__set(&error, "set attribute value",
            DPI_ERR_NOT_SUPPORTED);
    return dpiGen__endPublicFn(pool, status, &error);
}


//-----------------------------------------------------------------------------
// dpiPool_acquireConnection() [PUBLIC]
//   Acquire a connection from the pool.
//-----------------------------------------------------------------------------
int dpiPool_acquireConnection(dpiPool *pool, const char *userName,
        uint32_t userNameLength, const char *password, uint32_t passwordLength,
        dpiConnCreateParams *params, dpiConn **conn)
{
    dpiConnCreateParams localParams;
    dpiError error;
    int status;

    // validate parameters
    if (dpiPool__checkConnected(pool, __func__, &error) < 0)
        return dpiGen__endPublicFn(pool, DPI_FAILURE, &error);
    DPI_CHECK_PTR_AND_LENGTH(pool, userName)
    DPI_CHECK_PTR_AND_LENGTH(pool, password)
    DPI_CHECK_PTR_NOT_NULL(pool, conn)

    // use default parameters if none provided
    if (!params) {
        dpiContext__initConnCreateParams(&localParams);
        params = &localParams;
    }

    // the username must be enclosed within [] if external authentication
    // with proxy is desired
    if (pool->externalAuth && userName && userNameLength > 0 &&
            (userName[0] != '[' || userName[userNameLength - 1] != ']')) {
        dpiError__set(&error, "verify proxy user name with external auth",
                DPI_ERR_EXT_AUTH_INVALID_PROXY);
        return dpiGen__endPublicFn(pool, DPI_FAILURE, &error );
    }

    status = dpiPool__acquireConnection(pool, userName, userNameLength,
            password, passwordLength, params, conn, &error);
    return dpiGen__endPublicFn(pool, status, &error);
}


//-----------------------------------------------------------------------------
// dpiPool_addRef() [PUBLIC]
//   Add a reference to the pool.
//-----------------------------------------------------------------------------
int dpiPool_addRef(dpiPool *pool)
{
    return dpiGen__addRef(pool, DPI_HTYPE_POOL, __func__);
}


//-----------------------------------------------------------------------------
// dpiPool_close() [PUBLIC]
//   Destroy the pool now, not when the reference count reaches zero.
//-----------------------------------------------------------------------------
int dpiPool_close(dpiPool *pool, dpiPoolCloseMode mode)
{
    dpiError error;

    if (dpiPool__checkConnected(pool, __func__, &error) < 0)
        return dpiGen__endPublicFn(pool, DPI_FAILURE, &error);
    if (dpiOci__sessionPoolDestroy(pool, mode, 1, &error) < 0)
        return dpiGen__endPublicFn(pool, DPI_FAILURE, &error);
    return dpiGen__endPublicFn(pool, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiPool_create() [PUBLIC]
//   Create a new session pool and return it.
//-----------------------------------------------------------------------------
int dpiPool_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,
        dpiPoolCreateParams *createParams, dpiPool **pool)
{
    dpiCommonCreateParams localCommonParams;
    dpiPoolCreateParams localCreateParams;
    dpiPool *tempPool;
    dpiError error;

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

    // use default parameters if none provided
    if (!commonParams) {
        dpiContext__initCommonCreateParams(context, &localCommonParams);
        commonParams = &localCommonParams;
    }
    if (!createParams) {
        dpiContext__initPoolCreateParams(&localCreateParams);
        createParams = &localCreateParams;
    }

    // allocate memory for pool
    if (dpiGen__allocate(DPI_HTYPE_POOL, NULL, (void**) &tempPool, &error) < 0)
        return dpiGen__endPublicFn(context, DPI_FAILURE, &error);

    // initialize environment
    if (dpiEnv__init(tempPool->env, context, commonParams, NULL, &error) < 0) {
        dpiPool__free(tempPool, &error);
        return dpiGen__endPublicFn(context, DPI_FAILURE, &error);
    }

    // perform remaining steps required to create pool
    if (dpiPool__create(tempPool, userName, userNameLength, password,
            passwordLength, connectString, connectStringLength, commonParams,
            createParams, &error) < 0) {
        dpiPool__free(tempPool, &error);
        return dpiGen__endPublicFn(context, DPI_FAILURE, &error);
    }

    createParams->outPoolName = tempPool->name;
    createParams->outPoolNameLength = tempPool->nameLength;
    *pool = tempPool;
    dpiHandlePool__release(tempPool->env->errorHandles, &error.handle);
    return dpiGen__endPublicFn(context, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiPool_getBusyCount() [PUBLIC]
//   Return the pool's busy count.
//-----------------------------------------------------------------------------
int dpiPool_getBusyCount(dpiPool *pool, uint32_t *value)
{
    return dpiPool__getAttributeUint(pool, DPI_OCI_ATTR_SPOOL_BUSY_COUNT,
            value, __func__);
}


//-----------------------------------------------------------------------------
// dpiPool_getEncodingInfo() [PUBLIC]
//   Get the encoding information from the pool.
//-----------------------------------------------------------------------------
int dpiPool_getEncodingInfo(dpiPool *pool, dpiEncodingInfo *info)
{
    dpiError error;
    int status;

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


//-----------------------------------------------------------------------------
// dpiPool_getGetMode() [PUBLIC]
//   Return the pool's "get" mode.
//-----------------------------------------------------------------------------
int dpiPool_getGetMode(dpiPool *pool, dpiPoolGetMode *value)
{
    dpiError error;

    if (dpiPool__checkConnected(pool, __func__, &error) < 0)
        return dpiGen__endPublicFn(pool, DPI_FAILURE, &error);
    DPI_CHECK_PTR_NOT_NULL(pool, value)
    if (dpiOci__attrGet(pool->handle, DPI_OCI_HTYPE_SPOOL, value, NULL,
            DPI_OCI_ATTR_SPOOL_GETMODE, "get attribute value", &error) < 0)
        return dpiGen__endPublicFn(pool, DPI_FAILURE, &error);
    return dpiGen__endPublicFn(pool, DPI_SUCCESS, &error);
}


//-----------------------------------------------------------------------------
// dpiPool_getMaxLifetimeSession() [PUBLIC]
//   Return the pool's maximum lifetime session.
//-----------------------------------------------------------------------------
int dpiPool_getMaxLifetimeSession(dpiPool *pool, uint32_t *value)
{
    return dpiPool__getAttributeUint(pool,
            DPI_OCI_ATTR_SPOOL_MAX_LIFETIME_SESSION, value, __func__);
}


//-----------------------------------------------------------------------------
// dpiPool_getOpenCount() [PUBLIC]
//   Return the pool's open count.
//-----------------------------------------------------------------------------
int dpiPool_getOpenCount(dpiPool *pool, uint32_t *value)
{
    return dpiPool__getAttributeUint(pool, DPI_OCI_ATTR_SPOOL_OPEN_COUNT,
            value, __func__);
}


//-----------------------------------------------------------------------------
// dpiPool_getStmtCacheSize() [PUBLIC]
//   Return the pool's default statement cache size.
//-----------------------------------------------------------------------------
int dpiPool_getStmtCacheSize(dpiPool *pool, uint32_t *value)
{
    return dpiPool__getAttributeUint(pool, DPI_OCI_ATTR_SPOOL_STMTCACHESIZE,
            value, __func__);
}


//-----------------------------------------------------------------------------
// dpiPool_getTimeout() [PUBLIC]
//   Return the pool's timeout value.
//-----------------------------------------------------------------------------
int dpiPool_getTimeout(dpiPool *pool, uint32_t *value)
{
    return dpiPool__getAttributeUint(pool, DPI_OCI_ATTR_SPOOL_TIMEOUT, value,
            __func__);
}


//-----------------------------------------------------------------------------
// dpiPool_getWaitTimeout() [PUBLIC]
//   Return the pool's wait timeout value.
//-----------------------------------------------------------------------------
int dpiPool_getWaitTimeout(dpiPool *pool, uint32_t *value)
{
    return dpiPool__getAttributeUint(pool, DPI_OCI_ATTR_SPOOL_WAIT_TIMEOUT,
            value, __func__);
}


//-----------------------------------------------------------------------------
// dpiPool_release() [PUBLIC]
//   Release a reference to the pool.
//-----------------------------------------------------------------------------
int dpiPool_release(dpiPool *pool)
{
    return dpiGen__release(pool, DPI_HTYPE_POOL, __func__);
}


//-----------------------------------------------------------------------------
// dpiPool_setGetMode() [PUBLIC]
//   Set the pool's "get" mode.
//-----------------------------------------------------------------------------
int dpiPool_setGetMode(dpiPool *pool, dpiPoolGetMode value)
{
    return dpiPool__setAttributeUint(pool, DPI_OCI_ATTR_SPOOL_GETMODE, value,
            __func__);
}


//-----------------------------------------------------------------------------
// dpiPool_setMaxLifetimeSession() [PUBLIC]
//   Set the pool's maximum lifetime session.
//-----------------------------------------------------------------------------
int dpiPool_setMaxLifetimeSession(dpiPool *pool, uint32_t value)
{
    return dpiPool__setAttributeUint(pool,
            DPI_OCI_ATTR_SPOOL_MAX_LIFETIME_SESSION, value, __func__);
}


//-----------------------------------------------------------------------------
// dpiPool_setStmtCacheSize() [PUBLIC]
//   Set the pool's default statement cache size.
//-----------------------------------------------------------------------------
int dpiPool_setStmtCacheSize(dpiPool *pool, uint32_t value)
{
    return dpiPool__setAttributeUint(pool, DPI_OCI_ATTR_SPOOL_STMTCACHESIZE,
            value, __func__);
}


//-----------------------------------------------------------------------------
// dpiPool_setTimeout() [PUBLIC]
//   Set the pool's timeout value.
//-----------------------------------------------------------------------------
int dpiPool_setTimeout(dpiPool *pool, uint32_t value)
{
    return dpiPool__setAttributeUint(pool, DPI_OCI_ATTR_SPOOL_TIMEOUT, value,
            __func__);
}


//-----------------------------------------------------------------------------
// dpiPool_setWaitTimeout() [PUBLIC]
//   Set the pool's wait timeout value.
//-----------------------------------------------------------------------------
int dpiPool_setWaitTimeout(dpiPool *pool, uint32_t value)
{
    return dpiPool__setAttributeUint(pool, DPI_OCI_ATTR_SPOOL_WAIT_TIMEOUT,
            value, __func__);
}
