//-----------------------------------------------------------------------------
// Copyright (c) 2019, 2025, Oracle and/or its affiliates.
//
// This software is dual-licensed to you under the Universal Permissive License
// (UPL) 1.0 as shown at https://oss.oracle.com/licenses/upl and Apache License
// 2.0 as shown at http://www.apache.org/licenses/LICENSE-2.0. You may choose
// either license.
//
// If you elect to accept the software under the Apache License, Version 2.0,
// the following applies:
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//    https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
// test_4100_binds.c
//   Test suite for all of the binding related test cases.
//-----------------------------------------------------------------------------

#include "TestLib.h"

//-----------------------------------------------------------------------------
// dpiTest__bindLobIn() [INTERNAL]
//   Verify that binding LOBs IN  works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest__bindLobIn(dpiTestCase *testCase, dpiOracleTypeNum oracleTypeNum,
        const char *lobType, const char *lobStr)
{
    dpiData *returnValue, *lobValue;
    dpiVar *returnVar, *lobVar;
    char sql[100];
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // generate SQL statement to execute
    sprintf(sql, "begin :1 := pkg_TestLobs.TestIn%s(:2); end;", lobType);

    // create variables and populate IN value
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &returnVar, &returnValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, oracleTypeNum, DPI_NATIVE_TYPE_LOB, 1, 0, 0, 0,
            NULL, &lobVar, &lobValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_setFromBytes(lobVar, 0, lobStr, strlen(lobStr)) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, lobVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(returnValue),
            strlen(lobStr)) < 0)
        return DPI_FAILURE;

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(lobVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest__bindLobInOut() [INTERNAL]
//   Verify that binding LOBs IN/OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest__bindLobInOut(dpiTestCase *testCase,
        dpiOracleTypeNum oracleTypeNum, const char *lobType,
        const char *lobInStr)
{
    char expectedLobValue[200], actualLobValue[200], sql[100];
    uint64_t numBytes;
    dpiData *lobValue;
    dpiVar *lobVar;
    dpiStmt *stmt;
    dpiConn *conn;
    dpiLob *lob;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // generate SQL statement to execute
    sprintf(sql, "begin pkg_TestLobs.TestInOut%s(:1); end;", lobType);

    // create variable and populate it
    if (dpiConn_newVar(conn, oracleTypeNum, DPI_NATIVE_TYPE_LOB, 1, 0, 0, 0,
            NULL, &lobVar, &lobValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_setFromBytes(lobVar, 0, lobInStr, strlen(lobInStr)) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, lobVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    sprintf(expectedLobValue, "%s (Modified)", lobInStr);
    lob = dpiData_getLOB(lobValue);
    numBytes = sizeof(actualLobValue);
    if (dpiLob_readBytes(lob, 1, numBytes, actualLobValue, &numBytes) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectStringEqual(testCase, actualLobValue, numBytes,
            expectedLobValue, strlen(expectedLobValue)) < 0)
        return DPI_FAILURE;

    // cleanup
    if (dpiVar_release(lobVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest__bindLobOut() [INTERNAL]
//   Verify that binding LOBs OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest__bindLobOut(dpiTestCase *testCase, dpiOracleTypeNum oracleTypeNum,
        const char *lobType, const char *expectedLobValue)
{
    char actualLobValue[200], sql[100];
    uint64_t numBytes;
    dpiData *lobValue;
    dpiVar *lobVar;
    dpiStmt *stmt;
    dpiConn *conn;
    dpiLob *lob;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // generate SQL statement to execute
    sprintf(sql, "begin pkg_TestLobs.TestOut%s(:1); end;", lobType);

    // create variable
    if (dpiConn_newVar(conn, oracleTypeNum, DPI_NATIVE_TYPE_LOB, 1, 0, 0, 0,
            NULL, &lobVar, &lobValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, lobVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    lob = dpiData_getLOB(lobValue);
    numBytes = sizeof(actualLobValue);
    if (dpiLob_readBytes(lob, 1, numBytes, actualLobValue, &numBytes) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectStringEqual(testCase, actualLobValue, numBytes,
            expectedLobValue, strlen(expectedLobValue)) < 0)
        return DPI_FAILURE;

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(lobVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4100()
//   Verify that binding unicode (NVARCHAR2) arrays IN work as expected (no
// error).
//-----------------------------------------------------------------------------
int dpiTest_4100(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin :1 := pkg_TestUnicodeArrays.TestInArrays(:2, :3); end;";
    dpiData *returnValue, *startingLenValue, *arrayValue;
    dpiVar *returnVar, *startingLenVar, *arrayVar;
    uint32_t numElements = 278, i;
    char buffer[300];
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &returnVar, &returnValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &startingLenVar, &startingLenValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NVARCHAR, DPI_NATIVE_TYPE_BYTES,
            numElements, 100, 0, 1, NULL, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(startingLenValue, 65);
    for (i = 0; i < numElements; i++) {
        sprintf(buffer, "\xe3\x81\x82 %u", i + 1);
        if (dpiVar_setFromBytes(arrayVar, i, buffer, strlen(buffer)) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_setNumElementsInArray(arrayVar, numElements) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and bind values
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, startingLenVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 3, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(returnValue),
            1347) < 0)
        return DPI_FAILURE;

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(startingLenVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4101()
//   Verify that binding unicode (NVARCHAR2) arrays IN/OUT work as expected (no
// error).
//-----------------------------------------------------------------------------
int dpiTest_4101(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin pkg_TestUnicodeArrays.TestInOutArrays(:1, :2); end;";
    const char *outValueFormat =
            "Converted element \xe3\x81\x82 # %u originally had length %u";
    const char *inValueFormat = "\xe3\x81\x82 in value %u";
    uint32_t numElements = 183, numElementsOut, i;
    dpiData *numElementsValue, *arrayValue;
    dpiVar *numElementsVar, *arrayVar;
    char buffer[300];
    size_t strLength;
    dpiBytes *bytes;
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &numElementsVar, &numElementsValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NVARCHAR, DPI_NATIVE_TYPE_BYTES,
            numElements, 100, 0, 1, NULL, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(numElementsValue, numElements);
    for (i = 0; i < numElements; i++) {
        sprintf(buffer, inValueFormat, i + 1);
        if (dpiVar_setFromBytes(arrayVar, i, buffer, strlen(buffer)) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_setNumElementsInArray(arrayVar, numElements) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and bind values
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // perform execution and verify return values match expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_getNumElementsInArray(arrayVar, &numElementsOut) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, numElementsOut, numElements) < 0)
        return DPI_FAILURE;
    for (i = 0; i < numElements; i++) {
        sprintf(buffer, inValueFormat, i + 1);
        strLength = strlen(buffer) - 2;
        sprintf(buffer, outValueFormat, i + 1, strLength);
        bytes = dpiData_getBytes(&arrayValue[i]);
        if (dpiTestCase_expectStringEqual(testCase, bytes->ptr, bytes->length,
                buffer, strlen(buffer)) < 0)
            return DPI_FAILURE;
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4102()
//   Verify that binding unicode (NVARCHAR2) arrays OUT work as expected (no
// error).
//-----------------------------------------------------------------------------
int dpiTest_4102(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin pkg_TestUnicodeArrays.TestOutArrays(:1, :2); end;";
    const char *outValueFormat = "Test out element \xe3\x81\x82 # %u";
    uint32_t numElements = 207, numElementsOut, i;
    dpiData *numElementsValue, *arrayValue;
    dpiVar *numElementsVar, *arrayVar;
    char buffer[300];
    dpiBytes *bytes;
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &numElementsVar, &numElementsValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NVARCHAR, DPI_NATIVE_TYPE_BYTES,
            numElements, 100, 0, 1, NULL, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(numElementsValue, numElements);
    if (dpiVar_setNumElementsInArray(arrayVar, 0) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and bind values
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // perform execution and verify return values match expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_getNumElementsInArray(arrayVar, &numElementsOut) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, numElementsOut, numElements) < 0)
        return DPI_FAILURE;
    for (i = 0; i < numElements; i++) {
        sprintf(buffer, outValueFormat, i + 1);
        bytes = dpiData_getBytes(&arrayValue[i]);
        if (dpiTestCase_expectStringEqual(testCase, bytes->ptr, bytes->length,
                buffer, strlen(buffer)) < 0)
            return DPI_FAILURE;
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4103()
//   Verify that binding NUMBER arrays IN work as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4103(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin :1 := pkg_TestNumberArrays.TestInArrays(:2, :3); end;";
    dpiData *returnValue, *startingValue, *arrayValue;
    dpiVar *returnVar, *startingVar, *arrayVar;
    uint32_t numElements = 295, i;
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &returnVar, &returnValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &startingVar, &startingValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64,
            numElements, 0, 0, 1, NULL, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(startingValue, 25);
    for (i = 0; i < numElements; i++)
        dpiData_setInt64(&arrayValue[i], i);
    if (dpiVar_setNumElementsInArray(arrayVar, numElements) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and bind values
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, startingVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 3, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(returnValue),
            43390) < 0)
        return DPI_FAILURE;

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(startingVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4104()
//   Verify that binding NUMBER arrays IN/OUT work as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4104(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin pkg_TestNumberArrays.TestInOutArrays(:1, :2); end;";
    uint32_t numElements = 179, i, numElementsOut;
    dpiData *numElementsValue, *arrayValue;
    dpiVar *numElementsVar, *arrayVar;
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &numElementsVar, &numElementsValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64,
            numElements, 0, 0, 1, NULL, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(numElementsValue, numElements);
    for (i = 0; i < numElements; i++)
        dpiData_setInt64(&arrayValue[i], (i + 1) * 2);
    if (dpiVar_setNumElementsInArray(arrayVar, numElements) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and bind values
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_getNumElementsInArray(arrayVar, &numElementsOut) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, numElementsOut, numElements) < 0)
        return DPI_FAILURE;
    for (i = 0; i < numElements; i++) {
        if (dpiTestCase_expectIntEqual(testCase,
                dpiData_getInt64(&arrayValue[i]), (i + 1) * 20) < 0)
            return DPI_FAILURE;
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4105()
//   Verify that binding NUMBER arrays IN/OUT work as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4105(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql = "begin pkg_TestNumberArrays.TestOutArrays(:1, :2); end;";
    uint32_t numElements = 243, i, numElementsOut;
    dpiData *numElementsValue, *arrayValue;
    dpiVar *numElementsVar, *arrayVar;
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &numElementsVar, &numElementsValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64,
            numElements, 0, 0, 1, NULL, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(numElementsValue, numElements);
    if (dpiVar_setNumElementsInArray(arrayVar, 0) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and bind values
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_getNumElementsInArray(arrayVar, &numElementsOut) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, numElementsOut, numElements) < 0)
        return DPI_FAILURE;
    for (i = 0; i < numElements; i++) {
        if (dpiTestCase_expectIntEqual(testCase,
                dpiData_getInt64(&arrayValue[i]), (i + 1) * 100) < 0)
            return DPI_FAILURE;
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4106()
//   Verify that binding DATE arrays IN work as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4106(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin :1 := pkg_TestDateArrays.TestInArrays(:2, :3, :4); end;";
    dpiData *returnValue, *startingValue, *baseDateValue, *arrayValue;
    dpiVar *returnVar, *startingVar, *baseDateVar, *arrayVar;
    uint32_t numElements = 279, i;
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &returnVar, &returnValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &startingVar, &startingValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_DATE, DPI_NATIVE_TYPE_TIMESTAMP,
            1, 0, 0, 0, NULL, &baseDateVar, &baseDateValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_DATE, DPI_NATIVE_TYPE_TIMESTAMP,
            numElements, 0, 0, 1, NULL, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(startingValue, 10);
    dpiData_setTimestamp(baseDateValue, 2020, 1, 1, 1, 2, 1, 0, 0, 0);
    for (i = 0; i < numElements; i++)
        dpiData_setTimestamp(&arrayValue[i], 2020, i % 12 + 1, 1, 1, 2, 1, 0,
                0, 0);
    if (dpiVar_setNumElementsInArray(arrayVar, numElements) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and bind values
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, startingVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 3, baseDateVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 4, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(returnValue),
            46285) < 0)
        return DPI_FAILURE;

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(startingVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(baseDateVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4107()
//   Verify that binding DATE arrays IN/OUT work as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4107(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql = "begin pkg_TestDateArrays.TestInOutArrays(:1, :2); end;";
    dpiData *numElementsValue, *arrayValue, temp;
    uint32_t numElements = 185, i, numElementsOut;
    dpiVar *numElementsVar, *arrayVar;
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &numElementsVar, &numElementsValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_DATE, DPI_NATIVE_TYPE_TIMESTAMP,
            numElements, 0, 0, 1, NULL, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(numElementsValue, numElements);
    for (i = 0; i < numElements; i++)
        dpiData_setTimestamp(&arrayValue[i], 2021, i % 12 + 1, 4, 1, 2, 1, 0,
                0, 0);
    if (dpiVar_setNumElementsInArray(arrayVar, numElements) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and bind values
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_getNumElementsInArray(arrayVar, &numElementsOut) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, numElementsOut, numElements) < 0)
        return DPI_FAILURE;
    for (i = 0; i < numElements; i++) {
        dpiData_setTimestamp(&temp, 2021, i % 12 + 1, 11, 1, 2, 1, 0, 0, 0);
        if (dpiTestCase_expectTimestampEqual(testCase,
                dpiData_getTimestamp(&arrayValue[i]),
                dpiData_getTimestamp(&temp)) < 0)
            return DPI_FAILURE;
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4108()
//   Verify that binding DATE arrays OUT work as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4108(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql = "begin pkg_TestDateArrays.TestOutArrays(:1, :2); end;";
    dpiData *numElementsValue, *arrayValue, temp;
    uint32_t numElements = 14, i, numElementsOut;
    dpiVar *numElementsVar, *arrayVar;
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &numElementsVar, &numElementsValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_DATE, DPI_NATIVE_TYPE_TIMESTAMP,
            numElements, 0, 0, 1, NULL, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(numElementsValue, numElements);
    if (dpiVar_setNumElementsInArray(arrayVar, 0) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and bind values
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_getNumElementsInArray(arrayVar, &numElementsOut) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, numElementsOut, numElements) < 0)
        return DPI_FAILURE;
    for (i = 0; i < numElements; i++) {
        dpiData_setTimestamp(&temp, 2002, 12, i + 13, 0, 0, 0, 0, 0, 0);
        if (dpiTestCase_expectTimestampEqual(testCase,
                dpiData_getTimestamp(&arrayValue[i]),
                dpiData_getTimestamp(&temp)) < 0)
            return DPI_FAILURE;
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4109()
//   Verify that binding object arrays IN works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4109(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin :1 := pkg_TestObjectArrays.TestInObjArray(:2, :3); end;";
    dpiData *returnValue, *startingLenValue, *arrayValue, tempData;
    dpiVar *returnVar, *startingLenVar, *arrayVar;
    const char *elementObjName = "UDT_SUBOBJECT";
    const char *arrayObjName = "UDT_OBJECTARRAY";
    dpiObjectType *arrayObjType, *elementObjType;
    dpiObject *arrayObj, *elementObj;
    uint32_t i, numElements = 10;
    dpiObjectAttr *attrs[2];
    char buffer[300];
    dpiStmt *stmt;
    dpiConn *conn;
    int32_t size;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // get object types and attributes
    if (dpiConn_getObjectType(conn, arrayObjName, strlen(arrayObjName),
            &arrayObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_getObjectType(conn, elementObjName, strlen(elementObjName),
            &elementObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_getAttributes(elementObjType, 2, attrs) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create objects
    if (dpiObjectType_createObject(arrayObjType, &arrayObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_createObject(elementObjType, &elementObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &returnVar, &returnValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &startingLenVar, &startingLenValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_OBJECT, DPI_NATIVE_TYPE_OBJECT, 1,
            0, 0, 0, arrayObjType, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(startingLenValue, numElements);
    for (i = 0; i < numElements; i++) {
        dpiData_setDouble(&tempData, i + 1);
        if (dpiObject_setAttributeValue(elementObj, attrs[0],
                DPI_NATIVE_TYPE_DOUBLE, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        sprintf(buffer, "Test Data In %u", i + 1);
        dpiData_setBytes(&tempData, buffer, strlen(buffer));
        if (dpiObject_setAttributeValue(elementObj, attrs[1],
                DPI_NATIVE_TYPE_BYTES, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setObject(&tempData, elementObj);
        if (dpiObject_appendElement(arrayObj, DPI_NATIVE_TYPE_OBJECT,
                &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_setFromObject(arrayVar, 0, arrayObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_getSize(arrayObj, &size) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, size, numElements) < 0)
        return DPI_FAILURE;

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, startingLenVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 3, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(returnValue),
            206) < 0)
        return DPI_FAILURE;

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_release(arrayObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_release(elementObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(arrayObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(elementObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    for (i = 0; i < 2; i++) {
        if (dpiObjectAttr_release(attrs[i]) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_release(returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(startingLenVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4110()
//   Verify that binding object arrays IN/OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4110(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin pkg_TestObjectArrays.TestInOutObjArray(:1, :2); end;";
    const char *outValueFormat = "Test in/out element number %u";
    const char *inValueFormat = "Test In/Out Data %u";
    dpiData *numElementsValue, *arrayValue, tempData;
    const char *elementObjName = "UDT_SUBOBJECT";
    const char *arrayObjName = "UDT_OBJECTARRAY";
    dpiObjectType *arrayObjType, *elementObjType;
    dpiVar *numElementsVar, *arrayVar;
    dpiObject *arrayObj, *elementObj;
    uint32_t i, numElements = 8;
    dpiObjectAttr *attrs[2];
    char buffer[300];
    dpiBytes *bytes;
    dpiStmt *stmt;
    dpiConn *conn;
    int32_t size;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // get object types and attributes
    if (dpiConn_getObjectType(conn, arrayObjName, strlen(arrayObjName),
            &arrayObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_getObjectType(conn, elementObjName, strlen(elementObjName),
            &elementObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_getAttributes(elementObjType, 2, attrs) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create objects
    if (dpiObjectType_createObject(arrayObjType, &arrayObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_createObject(elementObjType, &elementObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &numElementsVar, &numElementsValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_OBJECT, DPI_NATIVE_TYPE_OBJECT, 1,
            0, 0, 0, arrayObjType, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(numElementsValue, numElements);
    for (i = 0; i < numElements; i++) {
        dpiData_setDouble(&tempData, i);
        if (dpiObject_setAttributeValue(elementObj, attrs[0],
                DPI_NATIVE_TYPE_DOUBLE, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        sprintf(buffer, inValueFormat, i + 1);
        dpiData_setBytes(&tempData, buffer, strlen(buffer));
        if (dpiObject_setAttributeValue(elementObj, attrs[1],
                DPI_NATIVE_TYPE_BYTES, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setObject(&tempData, elementObj);
        if (dpiObject_appendElement(arrayObj, DPI_NATIVE_TYPE_OBJECT,
                &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_setFromObject(arrayVar, 0, arrayObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_getSize(arrayObj, &size) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, size, numElements) < 0)
        return DPI_FAILURE;
    if (dpiObject_release(arrayObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_release(elementObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql),
            NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return values matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    arrayObj = dpiData_getObject(arrayValue);
    if (dpiObject_getSize(arrayObj, &size) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, size, numElements) < 0)
        return DPI_FAILURE;
    for (i = 0; i < size; i++) {
        if (dpiObject_getElementValueByIndex(arrayObj, i,
                DPI_NATIVE_TYPE_OBJECT, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        elementObj = dpiData_getObject(&tempData);
        if (dpiObject_getAttributeValue(elementObj, attrs[0],
                DPI_NATIVE_TYPE_DOUBLE, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        if (dpiTestCase_expectDoubleEqual(testCase,
                dpiData_getDouble(&tempData), i + 1) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[1],
                DPI_NATIVE_TYPE_BYTES, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        sprintf(buffer, outValueFormat, i + 1);
        bytes = dpiData_getBytes(&tempData);
        if (dpiTestCase_expectStringEqual(testCase, bytes->ptr, bytes->length,
                buffer, strlen(buffer)) < 0)
            return DPI_FAILURE;
        if (dpiObject_release(elementObj) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(arrayObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(elementObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    for (i = 0; i < 2; i++) {
        if (dpiObjectAttr_release(attrs[i]) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_release(numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4111()
//   Verify that binding object arrays OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4111(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin pkg_TestObjectArrays.TestOutObjArray(:1, :2); end;";
    const char *outValueFormat = "Test out element number %u";
    dpiData *numElementsValue, *arrayValue, tempData;
    const char *elementObjName = "UDT_SUBOBJECT";
    const char *arrayObjName = "UDT_OBJECTARRAY";
    dpiObjectType *arrayObjType, *elementObjType;
    dpiVar *numElementsVar, *arrayVar;
    dpiObject *arrayObj, *elementObj;
    uint32_t i, numElements = 7;
    dpiObjectAttr *attrs[2];
    char buffer[300];
    dpiBytes *bytes;
    dpiStmt *stmt;
    dpiConn *conn;
    int32_t size;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // get object types and attributes
    if (dpiConn_getObjectType(conn, arrayObjName, strlen(arrayObjName),
            &arrayObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_getObjectType(conn, elementObjName, strlen(elementObjName),
            &elementObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_getAttributes(elementObjType, 2, attrs) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &numElementsVar, &numElementsValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_OBJECT, DPI_NATIVE_TYPE_OBJECT, 1,
            0, 0, 0, arrayObjType, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(numElementsValue, numElements);

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql),
            NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return values matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    arrayObj = dpiData_getObject(arrayValue);
    if (dpiObject_getSize(arrayObj, &size) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, size, numElements) < 0)
        return DPI_FAILURE;
    for (i = 0; i < size; i++) {
        if (dpiObject_getElementValueByIndex(arrayObj, i,
                DPI_NATIVE_TYPE_OBJECT, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        elementObj = dpiData_getObject(&tempData);
        if (dpiObject_getAttributeValue(elementObj, attrs[0],
                DPI_NATIVE_TYPE_DOUBLE, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        if (dpiTestCase_expectDoubleEqual(testCase,
                dpiData_getDouble(&tempData), i + 1) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[1],
                DPI_NATIVE_TYPE_BYTES, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        sprintf(buffer, outValueFormat, i + 1);
        bytes = dpiData_getBytes(&tempData);
        if (dpiTestCase_expectStringEqual(testCase, bytes->ptr, bytes->length,
                buffer, strlen(buffer)) < 0)
            return DPI_FAILURE;
        if (dpiObject_release(elementObj) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(arrayObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(elementObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    for (i = 0; i < 2; i++) {
        if (dpiObjectAttr_release(attrs[i]) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_release(numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4112()
//   Verify that binding Number Varray IN work as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4112(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin :1 := pkg_TestNumberVarrays.TestInArrays(:2, :3); end;";
    dpiData *returnValue, *startingLenValue, *arrayValue, tempData;
    dpiVar *returnVar, *startingLenVar, *arrayVar;
    const char *objName = "UDT_ARRAY";
    uint32_t i, numElements = 9;
    dpiObjectType *objType;
    dpiObject *obj;
    dpiStmt *stmt;
    dpiConn *conn;
    int32_t size;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create object
    if (dpiConn_getObjectType(conn, objName, strlen(objName), &objType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_createObject(objType, &obj) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &returnVar, &returnValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &startingLenVar, &startingLenValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_OBJECT, DPI_NATIVE_TYPE_OBJECT, 1,
            0, 0, 0, objType, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(startingLenValue, 10);
    for (i = 0; i < numElements; i++) {
        dpiData_setInt64(&tempData, i + 1);
        if (dpiObject_appendElement(obj, DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_setFromObject(arrayVar, 0, obj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_getSize(obj, &size) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, size, numElements) < 0)
        return DPI_FAILURE;

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, startingLenVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 3, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(returnValue),
            55) < 0)
        return DPI_FAILURE;

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_release(obj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(objType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(startingLenVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4113()
//   Verify that binding Number Varray IN/OUT work as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4113(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin pkg_TestNumberVarrays.TestInOutArrays(:1, :2); end;";
    dpiData *numElementsValue, *arrayValue, tempData;
    dpiVar *numElementsVar,*arrayVar;
    const char *objName = "UDT_ARRAY";
    uint32_t i, numElements = 6;
    dpiObjectType *objType;
    dpiObject *obj;
    dpiStmt *stmt;
    dpiConn *conn;
    int32_t size;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create object
    if (dpiConn_getObjectType(conn, objName, strlen(objName), &objType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_createObject(objType, &obj) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &numElementsVar, &numElementsValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_OBJECT, DPI_NATIVE_TYPE_OBJECT, 1,
            0, 0, 0, objType, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(numElementsValue, numElements);
    for (i = 0; i < numElements; i++) {
        dpiData_setInt64(&tempData, i);
        if (dpiObject_appendElement(obj, DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_setFromObject(arrayVar, 0, obj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_getSize(obj, &size) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, size, numElements) < 0)
        return DPI_FAILURE;

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_getSize(dpiData_getObject(arrayValue), &size) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, size, numElements) < 0)
        return DPI_FAILURE;
    for (i = 0; i < size; i++) {
        if (dpiObject_getElementValueByIndex(dpiData_getObject(arrayValue), i,
                DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        if (dpiTestCase_expectIntEqual(testCase,
                dpiData_getInt64(&tempData), i * 20) < 0)
            return DPI_FAILURE;
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_release(obj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(objType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4114()
//   Verify that binding Number Varray OUT work as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4114(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin pkg_TestNumberVarrays.TestOutArrays(:1, :2); end;";
    dpiData *numElementsValue, *arrayValue, tempData;
    const char *objName = "UDT_ARRAY";
    dpiVar *numElementsVar,*arrayVar;
    uint32_t i, numElements = 9;
    dpiObjectType *objType;
    dpiObject *obj;
    dpiStmt *stmt;
    dpiConn *conn;
    int32_t size;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // get object type
    if (dpiConn_getObjectType(conn, objName, strlen(objName), &objType) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &numElementsVar, &numElementsValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_OBJECT, DPI_NATIVE_TYPE_OBJECT, 1,
            0, 0, 0, objType, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(numElementsValue, numElements);

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return values matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    obj = dpiData_getObject(arrayValue);
    if (dpiObject_getSize(obj, &size) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, size, numElements) < 0)
        return DPI_FAILURE;

    // compare results
    for (i = 0; i < size; i++) {
        if (dpiObject_getElementValueByIndex(obj, i, DPI_NATIVE_TYPE_INT64,
                &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(&tempData),
                (i + 1) * 200) < 0)
            return DPI_FAILURE;
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(objType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4115()
//   Verify that binding string (VARCHAR2) arrays IN work as expected (no
// error).
//-----------------------------------------------------------------------------
int dpiTest_4115(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin :1 := pkg_TestStringArrays.TestInArrays(:2, :3); end;";
    const char alphaNum[] =
            "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
    dpiData *returnValue, *startingLenValue, *arrayValue;
    dpiVar *returnVar, *startingLenVar, *arrayVar;
    uint32_t numElements = 2500, i, strSize = 100;
    char buffer[100];
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &returnVar, &returnValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &startingLenVar, &startingLenValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_VARCHAR, DPI_NATIVE_TYPE_BYTES,
            numElements, strSize, 0, 1, NULL, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(startingLenValue, 65);
    for (i = 0; i < strSize; i++)
        buffer[i] = alphaNum[rand() % (sizeof(alphaNum) - 1)];
    for (i = 0; i < numElements; i++) {
        if (dpiVar_setFromBytes(arrayVar, i, buffer, strSize) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_setNumElementsInArray(arrayVar, numElements) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and bind values
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, startingLenVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 3, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(returnValue),
            250065) < 0)
        return DPI_FAILURE;

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(startingLenVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4116()
//   Verify that binding string (VARCHAR2) arrays IN/OUT work as expected (no
// error).
//-----------------------------------------------------------------------------
int dpiTest_4116(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin pkg_TestStringArrays.TestInOutArrays(:1, :2); end;";
    const char *outValueFormat =
            "Converted element # %u originally had length %u";
    const char *inValueFormat = "IN String in value %u";
    uint32_t numElements = 20000, numElementsOut, i;
    dpiData *numElementsValue, *arrayValue;
    dpiVar *numElementsVar, *arrayVar;
    char buffer[300];
    size_t strLength;
    dpiBytes *bytes;
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &numElementsVar, &numElementsValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_VARCHAR, DPI_NATIVE_TYPE_BYTES,
            numElements, 100, 0, 1, NULL, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(numElementsValue, numElements);
    for (i = 0; i < numElements; i++) {
        sprintf(buffer, inValueFormat, i + 1);
        if (dpiVar_setFromBytes(arrayVar, i, buffer, strlen(buffer)) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_setNumElementsInArray(arrayVar, numElements) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and bind values
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // perform execution and verify return values match expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_getNumElementsInArray(arrayVar, &numElementsOut) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, numElementsOut, numElements) < 0)
        return DPI_FAILURE;
    for (i = 0; i < numElements; i++) {
        sprintf(buffer, inValueFormat, i + 1);
        strLength = strlen(buffer);
        sprintf(buffer, outValueFormat, i + 1, strLength);
        bytes = dpiData_getBytes(&arrayValue[i]);
        if (dpiTestCase_expectStringEqual(testCase, bytes->ptr, bytes->length,
                buffer, strlen(buffer)) < 0)
            return DPI_FAILURE;
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4117()
//   Verify that binding string (VARCHAR2) arrays OUT work as expected (no
// error).
//-----------------------------------------------------------------------------
int dpiTest_4117(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql =
            "begin pkg_TestStringArrays.TestOutArrays(:1, :2); end;";
    const char *outValueFormat = "Test out element # %u";
    uint32_t numElements = 20007, numElementsOut, i;
    dpiData *numElementsValue, *arrayValue;
    dpiVar *numElementsVar, *arrayVar;
    char buffer[300];
    dpiBytes *bytes;
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &numElementsVar, &numElementsValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_VARCHAR, DPI_NATIVE_TYPE_BYTES,
            numElements, 100, 0, 1, NULL, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(numElementsValue, numElements);
    if (dpiVar_setNumElementsInArray(arrayVar, 0) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and bind values
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // perform execution and verify return values match expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_getNumElementsInArray(arrayVar, &numElementsOut) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, numElementsOut, numElements) < 0)
        return DPI_FAILURE;
    for (i = 0; i < numElements; i++) {
        sprintf(buffer, outValueFormat, i + 1);
        bytes = dpiData_getBytes(&arrayValue[i]);
        if (dpiTestCase_expectStringEqual(testCase, bytes->ptr, bytes->length,
                buffer, strlen(buffer)) < 0)
            return DPI_FAILURE;
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4118()
//   Verify that binding record IN works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4118(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *expectedValue =
            "udt_Record(99, 'Test Record', "
            "to_date('2017-06-01', 'YYYY-MM-DD'), "
            "to_timestamp('2018-08-02 03:02:01', 'YYYY-MM-DD HH24:MI:SS'),"
            " true, -2147483647, 2147483647)";
    const char *sql =
            "begin :1 := pkg_TestRecords.GetStringRep(:2); end;";
    const char *objectName = "PKG_TESTRECORDS.UDT_RECORD";
    dpiData *stringRepValue, *objectValue, tempData;
    dpiVar *stringRepVar, *objectVar;
    uint32_t i, numAttrs = 7;
    dpiObjectAttr *attrs[7];
    dpiObjectType *objType;
    dpiBytes *bytes;
    dpiObject *obj;
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_setSkippedIfVersionTooOld(testCase, 0, 12, 1) < 0)
        return DPI_FAILURE;
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // get object type and attributes
    if (dpiConn_getObjectType(conn, objectName, strlen(objectName),
            &objType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_getAttributes(objType, numAttrs, attrs) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create object
    if (dpiObjectType_createObject(objType, &obj) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_VARCHAR, DPI_NATIVE_TYPE_BYTES, 1,
            200, 0, 0, NULL, &stringRepVar, &stringRepValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_OBJECT, DPI_NATIVE_TYPE_OBJECT, 1,
            0, 0, 0, objType, &objectVar, &objectValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(&tempData, 99);
    if (dpiObject_setAttributeValue(obj, attrs[0], DPI_NATIVE_TYPE_INT64,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setBytes(&tempData, "Test Record", strlen("Test Record"));
    if (dpiObject_setAttributeValue(obj, attrs[1], DPI_NATIVE_TYPE_BYTES,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setTimestamp(&tempData, 2017, 6, 1, 1, 2, 1, 1, 0, 0);
    if (dpiObject_setAttributeValue(obj, attrs[2], DPI_NATIVE_TYPE_TIMESTAMP,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setTimestamp(&tempData, 2018, 8, 2, 3, 2, 1, 1, 0, 0);
    if (dpiObject_setAttributeValue(obj, attrs[3], DPI_NATIVE_TYPE_TIMESTAMP,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setBool(&tempData, 1);
    if (dpiObject_setAttributeValue(obj, attrs[4],
            DPI_NATIVE_TYPE_BOOLEAN, &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_setFromObject(objectVar, 0, obj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setInt64(&tempData, -2147483647);
    if (dpiObject_setAttributeValue(obj, attrs[5], DPI_NATIVE_TYPE_INT64,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setInt64(&tempData, 2147483647);
    if (dpiObject_setAttributeValue(obj, attrs[6], DPI_NATIVE_TYPE_INT64,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, stringRepVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, objectVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    bytes = dpiData_getBytes(stringRepValue);
    if (dpiTestCase_expectStringEqual(testCase, bytes->ptr, bytes->length,
                expectedValue, strlen(expectedValue)) < 0)
        return DPI_FAILURE;

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_release(obj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(objType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    for (i = 0; i < numAttrs; i++) {
        if (dpiObjectAttr_release(attrs[i]) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_release(stringRepVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(objectVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4119()
//   Verify that binding record IN/OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4119(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql = "begin pkg_TestRecords.TestInOut(:1); end;";
    const char *objectName = "PKG_TESTRECORDS.UDT_RECORD";
    const char *expectedValue = "String in/out record";
    dpiData *objectValue, tempData, tempData2;
    uint32_t i, numAttrs = 7;
    dpiObjectAttr *attrs[7];
    dpiObjectType *objType;
    dpiVar *objectVar;
    dpiBytes *bytes;
    dpiObject *obj;
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_setSkippedIfVersionTooOld(testCase, 0, 12, 1) < 0)
        return DPI_FAILURE;
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // get object type and attribute
    if (dpiConn_getObjectType(conn, objectName, strlen(objectName),
            &objType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_getAttributes(objType, numAttrs, attrs) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create object
    if (dpiObjectType_createObject(objType, &obj) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_OBJECT, DPI_NATIVE_TYPE_OBJECT, 1,
            0, 0, 0, objType, &objectVar, &objectValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(&tempData, 99);
    if (dpiObject_setAttributeValue(obj, attrs[0], DPI_NATIVE_TYPE_INT64,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setBytes(&tempData, "Test Record", strlen("Test Record"));
    if (dpiObject_setAttributeValue(obj, attrs[1], DPI_NATIVE_TYPE_BYTES,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setTimestamp(&tempData, 2017, 6, 5, 0, 0, 0, 0, 0, 0);
    if (dpiObject_setAttributeValue(obj, attrs[2], DPI_NATIVE_TYPE_TIMESTAMP,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setTimestamp(&tempData, 2018, 8, 9, 3, 2, 1, 0, 0, 0);
    if (dpiObject_setAttributeValue(obj, attrs[3], DPI_NATIVE_TYPE_TIMESTAMP,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setBool(&tempData, 1);
    if (dpiObject_setAttributeValue(obj, attrs[4], DPI_NATIVE_TYPE_BOOLEAN,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setInt64(&tempData, -214748);
    if (dpiObject_setAttributeValue(obj, attrs[5], DPI_NATIVE_TYPE_INT64,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setInt64(&tempData, 214748);
    if (dpiObject_setAttributeValue(obj, attrs[6], DPI_NATIVE_TYPE_INT64,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_setFromObject(objectVar, 0, obj) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, objectVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    obj = dpiData_getObject(objectValue);
    if (dpiObject_getAttributeValue(obj, attrs[0], DPI_NATIVE_TYPE_INT64,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(&tempData),
            990) < 0)
        return DPI_FAILURE;
    if (dpiObject_getAttributeValue(obj, attrs[1], DPI_NATIVE_TYPE_BYTES,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    bytes = dpiData_getBytes(&tempData);
    if (dpiTestCase_expectStringEqual(testCase, bytes->ptr, bytes->length,
            expectedValue, strlen(expectedValue)) < 0)
        return DPI_FAILURE;
    if (dpiObject_getAttributeValue(obj, attrs[2], DPI_NATIVE_TYPE_TIMESTAMP,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setTimestamp(&tempData2, 2017, 6, 9, 0, 0, 0, 0, 0, 0);
    if (dpiTestCase_expectTimestampEqual(testCase,
            dpiData_getTimestamp(&tempData),
            dpiData_getTimestamp(&tempData2)) < 0)
        return DPI_FAILURE;
    if (dpiObject_getAttributeValue(obj, attrs[3], DPI_NATIVE_TYPE_TIMESTAMP,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setTimestamp(&tempData2, 2018, 8, 8, 3, 2, 1, 0, 0, 0);
    if (dpiTestCase_expectTimestampEqual(testCase,
            dpiData_getTimestamp(&tempData),
            dpiData_getTimestamp(&tempData2)) < 0)
        return DPI_FAILURE;
    if (dpiObject_getAttributeValue(obj, attrs[4], DPI_NATIVE_TYPE_BOOLEAN,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectUintEqual(testCase, dpiData_getBool(&tempData),
            0) < 0)
        return DPI_FAILURE;
    if (dpiObject_getAttributeValue(obj, attrs[5], DPI_NATIVE_TYPE_INT64,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(&tempData),
            -2147480) < 0)
        return DPI_FAILURE;
    if (dpiObject_getAttributeValue(obj, attrs[6], DPI_NATIVE_TYPE_INT64,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(&tempData),
            2147480) < 0)
        return DPI_FAILURE;

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_release(obj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(objType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    for (i = 0; i < numAttrs; i++) {
        if (dpiObjectAttr_release(attrs[i]) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_release(objectVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}



//-----------------------------------------------------------------------------
// dpiTest_4120()
//   Verify that binding record OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4120(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql = "begin pkg_TestRecords.TestOut(:1); end;";
    const char *objectName = "PKG_TESTRECORDS.UDT_RECORD";
    const char *expectedValue = "String in record";
    dpiData *objectValue, tempData, tempData2;
    uint32_t i, numAttrs = 7;
    dpiObjectAttr *attrs[7];
    dpiObjectType *objType;
    dpiVar *objectVar;
    dpiBytes *bytes;
    dpiObject *obj;
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_setSkippedIfVersionTooOld(testCase, 0, 12, 1) < 0)
        return DPI_FAILURE;
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // get object type and attribute
    if (dpiConn_getObjectType(conn, objectName, strlen(objectName),
            &objType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_getAttributes(objType, numAttrs, attrs) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_OBJECT, DPI_NATIVE_TYPE_OBJECT, 1,
            0, 0, 0, objType, &objectVar, &objectValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, objectVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    obj = dpiData_getObject(objectValue);
    if (dpiObject_getAttributeValue(obj, attrs[0], DPI_NATIVE_TYPE_INT64,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(&tempData),
            25) < 0)
        return DPI_FAILURE;
    if (dpiObject_getAttributeValue(obj, attrs[1], DPI_NATIVE_TYPE_BYTES,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    bytes = dpiData_getBytes(&tempData);
    if (dpiTestCase_expectStringEqual(testCase, bytes->ptr, bytes->length,
            expectedValue, strlen(expectedValue)) < 0)
        return DPI_FAILURE;
    if (dpiObject_getAttributeValue(obj, attrs[2], DPI_NATIVE_TYPE_TIMESTAMP,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setTimestamp(&tempData2, 2016, 2, 16, 0, 0, 0, 0, 0, 0);
    if (dpiTestCase_expectTimestampEqual(testCase,
            dpiData_getTimestamp(&tempData),
            dpiData_getTimestamp(&tempData2)) < 0)
        return DPI_FAILURE;
    if (dpiObject_getAttributeValue(obj, attrs[3], DPI_NATIVE_TYPE_TIMESTAMP,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setTimestamp(&tempData2, 2016, 2, 16, 18, 23, 55, 0, 0, 0);
    if (dpiTestCase_expectTimestampEqual(testCase,
            dpiData_getTimestamp(&tempData),
            dpiData_getTimestamp(&tempData2)) < 0)
        return DPI_FAILURE;
    if (dpiObject_getAttributeValue(obj, attrs[4], DPI_NATIVE_TYPE_BOOLEAN,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectUintEqual(testCase, dpiData_getBool(&tempData),
            1) < 0)
        return DPI_FAILURE;
    if (dpiObject_getAttributeValue(obj, attrs[5], DPI_NATIVE_TYPE_INT64,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(&tempData),
            -214748) < 0)
        return DPI_FAILURE;
    if (dpiObject_getAttributeValue(obj, attrs[6], DPI_NATIVE_TYPE_INT64,
            &tempData) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(&tempData),
            214748) < 0)
        return DPI_FAILURE;

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(objType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    for (i = 0; i < numAttrs; i++) {
        if (dpiObjectAttr_release(attrs[i]) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_release(objectVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4121()
//   Verify that binding Record array IN works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4121(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql = "begin :1 := pkg_TestRecords.TestInArrays(:2); end;";
    const char *arrayObjName = "PKG_TESTRECORDS.UDT_RECORDARRAY";
    const char *elementObjName = "PKG_TESTRECORDS.UDT_RECORD";
    const char *inValueFormat = "Test Record In %u";
    dpiObjectType *arrayObjType, *elementObjType;
    dpiData *returnValue, *arrayValue, tempData;
    uint32_t i, numAttrs = 7, numElements = 10;
    dpiObject *arrayObj, *elementObj;
    dpiVar *returnVar, *arrayVar;
    dpiObjectAttr *attrs[7];
    char buffer[300];
    dpiStmt *stmt;
    dpiConn *conn;

    // get connection
    if (dpiTestCase_setSkippedIfVersionTooOld(testCase, 0, 12, 1) < 0)
        return DPI_FAILURE;
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // get object types and attributes
    if (dpiConn_getObjectType(conn, arrayObjName, strlen(arrayObjName),
            &arrayObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_getObjectType(conn, elementObjName, strlen(elementObjName),
            &elementObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_getAttributes(elementObjType, numAttrs, attrs) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create objects
    if (dpiObjectType_createObject(arrayObjType, &arrayObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_createObject(elementObjType, &elementObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &returnVar, &returnValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_OBJECT, DPI_NATIVE_TYPE_OBJECT, 1,
            0, 0, 0, arrayObjType, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    for (i = 0; i < numElements; i++) {
        dpiData_setInt64(&tempData, i + 1);
        if (dpiObject_setAttributeValue(elementObj, attrs[0],
                DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        sprintf(buffer, inValueFormat, i + 1);
        dpiData_setBytes(&tempData, buffer, strlen(buffer));
        if (dpiObject_setAttributeValue(elementObj, attrs[1],
                DPI_NATIVE_TYPE_BYTES, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setTimestamp(&tempData, 2017, 6, i + 1, 0, 0, 0, 0, 0, 0);
        if (dpiObject_setAttributeValue(elementObj, attrs[2],
                DPI_NATIVE_TYPE_TIMESTAMP, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setTimestamp(&tempData, 2018, 8, i + 1, 3, 2, 1, 0, 0, 0);
        if (dpiObject_setAttributeValue(elementObj, attrs[3],
                DPI_NATIVE_TYPE_TIMESTAMP, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setBool(&tempData, 1);
        if (dpiObject_setAttributeValue(elementObj, attrs[4],
                DPI_NATIVE_TYPE_BOOLEAN, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setInt64(&tempData, i + -21478);
        if (dpiObject_setAttributeValue(elementObj, attrs[5],
                DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setInt64(&tempData, i + 21478);
        if (dpiObject_setAttributeValue(elementObj, attrs[6],
                DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setObject(&tempData, elementObj);
        if (dpiObject_appendElement(arrayObj, DPI_NATIVE_TYPE_OBJECT,
                &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_setFromObject(arrayVar, 0, arrayObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(returnValue),
            55) < 0)
        return DPI_FAILURE;

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_release(arrayObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_release(elementObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(arrayObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(elementObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    for (i = 0; i < numAttrs; i++) {
        if (dpiObjectAttr_release(attrs[i]) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_release(returnVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4122()
//   Verify that binding Record array IN/OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4122(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql = "begin pkg_TestRecords.TestInOutArrays(:1); end;";
    const char *outValueFormat = "Converted in/out record # %u";
    const char *arrayObjName = "PKG_TESTRECORDS.UDT_RECORDARRAY";
    const char *elementObjName = "PKG_TESTRECORDS.UDT_RECORD";
    const char *inValueFormat = "Test Record In %u";
    dpiObjectType *arrayObjType, *elementObjType;
    uint32_t i, numAttrs = 7, numElements = 10;
    dpiData *arrayValue, tempData, tempData2;
    dpiObject *arrayObj, *elementObj;
    dpiObjectAttr *attrs[7];
    dpiVar *arrayVar;
    char buffer[300];
    dpiBytes *bytes;
    dpiStmt *stmt;
    dpiConn *conn;
    int32_t size;

    // get connection
    if (dpiTestCase_setSkippedIfVersionTooOld(testCase, 0, 12, 1) < 0)
        return DPI_FAILURE;
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // get object types and attributes
    if (dpiConn_getObjectType(conn, arrayObjName, strlen(arrayObjName),
            &arrayObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_getObjectType(conn, elementObjName, strlen(elementObjName),
            &elementObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_getAttributes(elementObjType, numAttrs, attrs) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create objects
    if (dpiObjectType_createObject(arrayObjType, &arrayObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_createObject(elementObjType, &elementObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_OBJECT, DPI_NATIVE_TYPE_OBJECT, 1,
            0, 0, 0, arrayObjType, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    for (i = 0; i < numElements; i++) {
        dpiData_setInt64(&tempData, i);
        if (dpiObject_setAttributeValue(elementObj, attrs[0],
                DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        sprintf(buffer, inValueFormat, i);
        dpiData_setBytes(&tempData, buffer, strlen(buffer));
        if (dpiObject_setAttributeValue(elementObj, attrs[1],
                DPI_NATIVE_TYPE_BYTES, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setTimestamp(&tempData, 2017, 6, i + 1, 0, 0, 0, 0, 0, 0);
        if (dpiObject_setAttributeValue(elementObj, attrs[2],
                DPI_NATIVE_TYPE_TIMESTAMP, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setTimestamp(&tempData, 2018, 8, i + 1, 3, 2, 1, 0, 0, 0);
        if (dpiObject_setAttributeValue(elementObj, attrs[3],
                DPI_NATIVE_TYPE_TIMESTAMP, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setBool(&tempData, 1);
        if (dpiObject_setAttributeValue(elementObj, attrs[4],
                DPI_NATIVE_TYPE_BOOLEAN, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setInt64(&tempData, i);
        if (dpiObject_setAttributeValue(elementObj, attrs[5],
                DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setInt64(&tempData, i);
        if (dpiObject_setAttributeValue(elementObj, attrs[6],
                DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setObject(&tempData, elementObj);
        if (dpiObject_appendElement(arrayObj, DPI_NATIVE_TYPE_OBJECT,
                &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_setFromObject(arrayVar, 0, arrayObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_release(arrayObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObject_release(elementObj) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    arrayObj = dpiData_getObject(arrayValue);
    if (dpiObject_getSize(arrayObj, &size) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, size, numElements) < 0)
        return DPI_FAILURE;
    for (i = 0; i < numElements; i++) {
        if (dpiObject_getElementValueByIndex(arrayObj, i,
                DPI_NATIVE_TYPE_OBJECT, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        elementObj = dpiData_getObject(&tempData);
        if (dpiObject_getAttributeValue(elementObj, attrs[0],
                DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(&tempData),
                i * 10) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[1],
                DPI_NATIVE_TYPE_BYTES, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        bytes = dpiData_getBytes(&tempData);
        sprintf(buffer, outValueFormat, i);
        if (dpiTestCase_expectStringEqual(testCase, bytes->ptr, bytes->length,
                buffer, strlen(buffer)) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[2],
                DPI_NATIVE_TYPE_TIMESTAMP, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setTimestamp(&tempData2, 2017, 6, i * 2 + 1, 0, 0, 0, 0, 0, 0);
        if (dpiTestCase_expectTimestampEqual(testCase,
                dpiData_getTimestamp(&tempData),
                dpiData_getTimestamp(&tempData2)) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[3],
                DPI_NATIVE_TYPE_TIMESTAMP, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setTimestamp(&tempData2, 2018, 8, i * 2 + 1, 3, 2, 1, 0, 0, 0);
        if (dpiTestCase_expectTimestampEqual(testCase,
                dpiData_getTimestamp(&tempData),
                dpiData_getTimestamp(&tempData2)) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[4],
                DPI_NATIVE_TYPE_BOOLEAN, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        if (dpiTestCase_expectUintEqual(testCase,
                dpiData_getBool(&tempData), i % 2) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[5],
                DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(&tempData),
                i * 10) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[6],
                DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(&tempData),
                i * 10) < 0)
            return DPI_FAILURE;
        if (dpiObject_release(elementObj) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(arrayObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(elementObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    for (i = 0; i < numAttrs; i++) {
        if (dpiObjectAttr_release(attrs[i]) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4123()
//   Verify that binding Record array OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4123(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *sql = "begin pkg_TestRecords.TestOutArrays(:1, :2); end;";
    const char *arrayObjName = "PKG_TESTRECORDS.UDT_RECORDARRAY";
    dpiData *numElementsValue, *arrayValue, tempData, tempData2;
    const char *elementObjName = "PKG_TESTRECORDS.UDT_RECORD";
    const char *outValueFormat = "Test OUT record # %u";
    dpiObjectType *arrayObjType, *elementObjType;
    uint32_t i, numAttrs = 7, numElements = 10;
    dpiVar *numElementsVar, *arrayVar;
    dpiObject *arrayObj, *elementObj;
    dpiObjectAttr *attrs[7];
    char buffer[300];
    dpiBytes *bytes;
    dpiStmt *stmt;
    dpiConn *conn;
    int32_t size;

    // get connection
    if (dpiTestCase_setSkippedIfVersionTooOld(testCase, 0, 12, 1) < 0)
        return DPI_FAILURE;
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;

    // get object types and attributes
    if (dpiConn_getObjectType(conn, arrayObjName, strlen(arrayObjName),
            &arrayObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_getObjectType(conn, elementObjName, strlen(elementObjName),
            &elementObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_getAttributes(elementObjType, numAttrs, attrs) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // create variables
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_NUMBER, DPI_NATIVE_TYPE_INT64, 1,
            0, 0, 0, NULL, &numElementsVar, &numElementsValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_newVar(conn, DPI_ORACLE_TYPE_OBJECT, DPI_NATIVE_TYPE_OBJECT, 1,
            0, 0, 0, arrayObjType, &arrayVar, &arrayValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // populate bind variables with values
    dpiData_setInt64(numElementsValue, numElements);

    // prepare statement and perform binds
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 1, numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindByPos(stmt, 2, arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // execute statement and verify return value matches expectations
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    arrayObj = dpiData_getObject(arrayValue);
    if (dpiObject_getSize(arrayObj, &size) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, size, numElements) < 0)
        return DPI_FAILURE;
    for (i = 0; i < size; i++) {
        if (dpiObject_getElementValueByIndex(arrayObj, i,
                DPI_NATIVE_TYPE_OBJECT, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        elementObj = dpiData_getObject(&tempData);
        if (dpiObject_getAttributeValue(elementObj, attrs[0],
                DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(&tempData),
                i * 10) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[1],
                DPI_NATIVE_TYPE_BYTES, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        bytes = dpiData_getBytes(&tempData);
        sprintf(buffer, outValueFormat, i);
        if (dpiTestCase_expectStringEqual(testCase, bytes->ptr, bytes->length,
                buffer, strlen(buffer)) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[2],
                DPI_NATIVE_TYPE_TIMESTAMP, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setTimestamp(&tempData2, 2016, 2, 16 + i, 0, 0, 0, 0, 0, 0);
        if (dpiTestCase_expectTimestampEqual(testCase,
                dpiData_getTimestamp(&tempData),
                dpiData_getTimestamp(&tempData2)) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[3],
                DPI_NATIVE_TYPE_TIMESTAMP, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        dpiData_setTimestamp(&tempData2, 2016, 2, 16 + i, 18, 23, 55, 0, 0, 0);
        if (dpiTestCase_expectTimestampEqual(testCase,
                dpiData_getTimestamp(&tempData),
                dpiData_getTimestamp(&tempData2)) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[4],
                DPI_NATIVE_TYPE_BOOLEAN, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        if (dpiTestCase_expectUintEqual(testCase,
                dpiData_getBool(&tempData), i%2) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[5],
                DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(&tempData),
                i * 7) < 0)
            return DPI_FAILURE;
        if (dpiObject_getAttributeValue(elementObj, attrs[6],
                DPI_NATIVE_TYPE_INT64, &tempData) < 0)
            return dpiTestCase_setFailedFromError(testCase);
        if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(&tempData),
                i * 5) < 0)
            return DPI_FAILURE;
        if (dpiObject_release(elementObj) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }

    // cleanup
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(arrayObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiObjectType_release(elementObjType) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    for (i = 0; i < numAttrs; i++) {
        if (dpiObjectAttr_release(attrs[i]) < 0)
            return dpiTestCase_setFailedFromError(testCase);
    }
    if (dpiVar_release(numElementsVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiVar_release(arrayVar) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_4124()
//   Verify that binding CLOB IN works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4124(dpiTestCase *testCase, dpiTestParams *params)
{
    return dpiTest__bindLobIn(testCase, DPI_ORACLE_TYPE_CLOB, "CLOB",
            "In CLOB value (string)");
}


//-----------------------------------------------------------------------------
// dpiTest_4125()
//   Verify that binding CLOB IN/OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4125(dpiTestCase *testCase, dpiTestParams *params)
{
    return dpiTest__bindLobInOut(testCase, DPI_ORACLE_TYPE_CLOB, "CLOB",
            "IN/OUT CLOB value (string)");
}



//-----------------------------------------------------------------------------
// dpiTest_4126()
//   Verify that binding CLOB OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4126(dpiTestCase *testCase, dpiTestParams *params)
{
    return dpiTest__bindLobOut(testCase, DPI_ORACLE_TYPE_CLOB, "CLOB",
            "OUT CLOB");
}


//-----------------------------------------------------------------------------
// dpiTest_4127()
//   Verify that binding NCLOB IN works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4127(dpiTestCase *testCase, dpiTestParams *params)
{
    return dpiTest__bindLobIn(testCase, DPI_ORACLE_TYPE_NCLOB, "NCLOB",
            "In NCLOB value (national string)");
}


//-----------------------------------------------------------------------------
// dpiTest_4128()
//   Verify that binding NCLOB IN/OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4128(dpiTestCase *testCase, dpiTestParams *params)
{
    return dpiTest__bindLobInOut(testCase, DPI_ORACLE_TYPE_NCLOB, "NCLOB",
            "IN/OUT NCLOB value (national string)");
}



//-----------------------------------------------------------------------------
// dpiTest_4129()
//   Verify that binding NCLOB OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4129(dpiTestCase *testCase, dpiTestParams *params)
{
    return dpiTest__bindLobOut(testCase, DPI_ORACLE_TYPE_NCLOB, "NCLOB",
            "OUT NCLOB");
}


//-----------------------------------------------------------------------------
// dpiTest_4130()
//   Verify that binding BLOB IN works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4130(dpiTestCase *testCase, dpiTestParams *params)
{
    return dpiTest__bindLobIn(testCase, DPI_ORACLE_TYPE_BLOB, "BLOB",
            "In BLOB value (raw)");
}


//-----------------------------------------------------------------------------
// dpiTest_4131()
//   Verify that binding BLOB IN/OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4131(dpiTestCase *testCase, dpiTestParams *params)
{
    return dpiTest__bindLobInOut(testCase, DPI_ORACLE_TYPE_BLOB, "BLOB",
            "IN/OUT BLOB value (raw)");
}



//-----------------------------------------------------------------------------
// dpiTest_4132()
//   Verify that binding BLOB OUT works as expected (no error).
//-----------------------------------------------------------------------------
int dpiTest_4132(dpiTestCase *testCase, dpiTestParams *params)
{
    return dpiTest__bindLobOut(testCase, DPI_ORACLE_TYPE_BLOB, "BLOB",
            "OUT BLOB");
}


//-----------------------------------------------------------------------------
// main()
//-----------------------------------------------------------------------------
int main(int argc, char **argv)
{
    dpiTestSuite_initialize(4100);
    dpiTestSuite_addCase(dpiTest_4100,
            "test PL/SQL bind of unicode array (IN)");
    dpiTestSuite_addCase(dpiTest_4101,
            "test PL/SQL bind of unicode array (IN/OUT)");
    dpiTestSuite_addCase(dpiTest_4102,
            "test PL/SQL bind of unicode array (OUT)");
    dpiTestSuite_addCase(dpiTest_4103,
            "test PL/SQL bind of number array (IN)");
    dpiTestSuite_addCase(dpiTest_4104,
            "test PL/SQL bind of number array (IN/OUT)");
    dpiTestSuite_addCase(dpiTest_4105,
            "test PL/SQL bind of number array (OUT)");
    dpiTestSuite_addCase(dpiTest_4106,
            "test PL/SQL bind of date array (IN)");
    dpiTestSuite_addCase(dpiTest_4107,
            "test PL/SQL bind of date array (IN/OUT)");
    dpiTestSuite_addCase(dpiTest_4108,
            "test PL/SQL bind of date array (OUT)");
    dpiTestSuite_addCase(dpiTest_4109,
            "test PL/SQL bind of object array (IN)");
    dpiTestSuite_addCase(dpiTest_4110,
            "test PL/SQL bind of object array (IN/OUT)");
    dpiTestSuite_addCase(dpiTest_4111,
            "test PL/SQL bind of object array (OUT)");
    dpiTestSuite_addCase(dpiTest_4112,
            "test PL/SQL bind of number varray (IN)");
    dpiTestSuite_addCase(dpiTest_4113,
            "test PL/SQL bind of number varray (IN/OUT)");
    dpiTestSuite_addCase(dpiTest_4114,
            "test PL/SQL bind of number varray (OUT)");
    dpiTestSuite_addCase(dpiTest_4115,
            "test PL/SQL bind of string array (IN)");
    dpiTestSuite_addCase(dpiTest_4116,
            "test PL/SQL bind of string array (IN/OUT)");
    dpiTestSuite_addCase(dpiTest_4117,
            "test PL/SQL bind of string array (OUT)");
    dpiTestSuite_addCase(dpiTest_4118,
            "test PL/SQL bind of record (IN)");
    dpiTestSuite_addCase(dpiTest_4119,
            "test PL/SQL bind of record (IN/OUT)");
    dpiTestSuite_addCase(dpiTest_4120,
            "test PL/SQL bind of record (OUT)");
    dpiTestSuite_addCase(dpiTest_4121,
            "test PL/SQL bind of record array (IN)");
    dpiTestSuite_addCase(dpiTest_4122,
            "test PL/SQL bind of record array (IN/OUT)");
    dpiTestSuite_addCase(dpiTest_4123,
            "test PL/SQL bind of record array (IN/OUT)");
    dpiTestSuite_addCase(dpiTest_4124,
            "test PL/SQL bind of CLOBs (IN)");
    dpiTestSuite_addCase(dpiTest_4125,
            "test PL/SQL bind of CLOBs (IN/OUT)");
    dpiTestSuite_addCase(dpiTest_4126,
            "test PL/SQL bind of CLOBs (OUT)");
    dpiTestSuite_addCase(dpiTest_4127,
            "test PL/SQL bind of NCLOBs (IN)");
    dpiTestSuite_addCase(dpiTest_4128,
            "test PL/SQL bind of NCLOBs (IN/OUT)");
    dpiTestSuite_addCase(dpiTest_4129,
            "test PL/SQL bind of NCLOBs (OUT)");
    dpiTestSuite_addCase(dpiTest_4130,
            "test PL/SQL bind of BLOBs (IN)");
    dpiTestSuite_addCase(dpiTest_4131,
            "test PL/SQL bind of BLOBs (IN/OUT)");
    dpiTestSuite_addCase(dpiTest_4132,
            "test PL/SQL bind of BLOBs (OUT)");
    return dpiTestSuite_run();
}
