//-----------------------------------------------------------------------------
// Copyright (c) 2017, 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_1700_transactions.c
//   Test suite for testing Transactions.
//-----------------------------------------------------------------------------
#include "TestLib.h"

#define FORMAT_ID                  100
#define TRANSACTION_ID             "txn-odpic"
#define BRANCH_QUALIFIER           "bqual-odpic"

//-----------------------------------------------------------------------------
// dpiTest__insertRowsInTable() [INTERNAL]
//   Inserts rows into the test table.
//-----------------------------------------------------------------------------
int dpiTest__insertRowsInTable(dpiTestCase *testCase, dpiConn *conn)
{
    const char *sql = "insert into TestTempTable values (:1, :2)";
    dpiData intColValue, stringColValue;
    char *strValue = "String 1";
    dpiStmt *stmt;

    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiData_setInt64(&intColValue, 1);
    dpiData_setBytes(&stringColValue, strValue, strlen(strValue));
    if (dpiStmt_bindValueByPos(stmt, 1, DPI_NATIVE_TYPE_INT64,
            &intColValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_bindValueByPos(stmt, 2, DPI_NATIVE_TYPE_BYTES,
            &stringColValue) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest__populateXid() [INTERNAL]
//   Populate the XID structure.
//-----------------------------------------------------------------------------
void dpiTest__populateXid(dpiXid *xid)
{
    xid->formatId = FORMAT_ID;
    xid->globalTransactionId = TRANSACTION_ID;
    xid->globalTransactionIdLength = strlen(TRANSACTION_ID);
    xid->branchQualifier = BRANCH_QUALIFIER;
    xid->branchQualifierLength = strlen(BRANCH_QUALIFIER);
}


//-----------------------------------------------------------------------------
// dpiTest__truncateTable() [INTERNAL]
//   Truncate the rows in the test table.
//-----------------------------------------------------------------------------
int dpiTest__truncateTable(dpiTestCase *testCase, dpiConn *conn)
{
    const char *sql = "truncate table TestTempTable";
    dpiStmt *stmt;

    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest__verifyData() [INTERNAL]
//   Verify that the table contains the expected number of rows. A new
// connection is established to ensure the transaction was truly committed or
// rolled back.
//-----------------------------------------------------------------------------
int dpiTest__verifyData(dpiTestCase *testCase, int64_t expectedNumRows)
{
    const char *sql = "select count(*) from TestTempTable";
    dpiNativeTypeNum nativeTypeNum;
    uint32_t bufferRowIndex;
    dpiData *data;
    dpiConn *conn;
    dpiStmt *stmt;
    int found;

    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;
    if (dpiConn_prepareStmt(conn, 0, sql, strlen(sql), NULL, 0, &stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_execute(stmt, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_defineValue(stmt, 1, DPI_ORACLE_TYPE_NUMBER,
            DPI_NATIVE_TYPE_INT64, 0, 0, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_fetch(stmt, &found, &bufferRowIndex) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiStmt_getQueryValue(stmt, 1, &nativeTypeNum, &data) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectIntEqual(testCase, dpiData_getInt64(data),
            expectedNumRows) < 0)
        return DPI_FAILURE;
    if (dpiStmt_release(stmt) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_1700()
//   Call dpiConn_tpcBegin() with parameters globalTransactionIdLength and
// branchQualifierLength <= 64 (no error).
//-----------------------------------------------------------------------------
int dpiTest_1700(dpiTestCase *testCase, dpiTestParams *params)
{
    dpiConn *conn;
    dpiXid xid;

    dpiTest__populateXid(&xid);
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;
    if (dpiConn_tpcBegin(conn, &xid, DPI_TPC_BEGIN_NEW, 0) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_1701()
//   Call dpiConn_tpcBegin() with parameter globalTransactionIdLength > 64
// (error DPI-1035).
//-----------------------------------------------------------------------------
int dpiTest_1701(dpiTestCase *testCase, dpiTestParams *params)
{
    dpiConn *conn;
    dpiXid xid;

    dpiTest__populateXid(&xid);
    xid.globalTransactionIdLength = 65;
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;
    dpiConn_tpcBegin(conn, &xid, DPI_TPC_BEGIN_NEW, 0);
    return dpiTestCase_expectError(testCase, "DPI-1035:");
}


//-----------------------------------------------------------------------------
// dpiTest_1702()
//   Call dpiConn_tpcBegin() with parameter branchQualifierLength > 64
// (error DPI-1036).
//-----------------------------------------------------------------------------
int dpiTest_1702(dpiTestCase *testCase, dpiTestParams *params)
{
    dpiConn *conn;
    dpiXid xid;

    dpiTest__populateXid(&xid);
    xid.branchQualifierLength = 65;
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;
    dpiConn_tpcBegin(conn, &xid, DPI_TPC_BEGIN_NEW, 0);
    return dpiTestCase_expectError(testCase, "DPI-1036:");
}


//-----------------------------------------------------------------------------
// dpiTest_1703()
//   Call dpiConn_tpcBegin(), then call dpiConn_tpcPrepare() and verify that
// commitNeeded has the value 0 (no error).
//-----------------------------------------------------------------------------
int dpiTest_1703(dpiTestCase *testCase, dpiTestParams *params)
{
    int commitNeeded;
    dpiConn *conn;
    dpiXid xid;

    dpiTest__populateXid(&xid);
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;
    if (dpiConn_tpcBegin(conn, &xid, DPI_TPC_BEGIN_NEW, 0) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_tpcPrepare(conn, NULL, &commitNeeded) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectUintEqual(testCase, commitNeeded, 0) < 0)
        return DPI_FAILURE;

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_1704()
//   Call dpiConn_tpcBegin(), then call dpiConn_tpcPrepare(), then call
// dpiConn_tpcCommit() (error ORA-24756).
//-----------------------------------------------------------------------------
int dpiTest_1704(dpiTestCase *testCase, dpiTestParams *params)
{
    int commitNeeded;
    dpiConn *conn;
    dpiXid xid;

    dpiTest__populateXid(&xid);
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;
    if (dpiConn_tpcBegin(conn, &xid, DPI_TPC_BEGIN_NEW, 0) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_tpcPrepare(conn, NULL, &commitNeeded) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    dpiConn_tpcCommit(conn, NULL, 1);
    return dpiTestCase_expectError(testCase, "ORA-24756:");
}


//-----------------------------------------------------------------------------
// dpiTest_1705()
//   Call dpiConn_tpcBegin(), then execute some DML, then call
// dpiConn_tpcPrepare() and verify that commitNeeded has the value 1;
// call dpiConn_tpcCommit() and create a new connection using the common
// connection creation method and verify that the changes have been committed
// to the database (no error).
//-----------------------------------------------------------------------------
int dpiTest_1705(dpiTestCase *testCase, dpiTestParams *params)
{
    int commitNeeded;
    dpiConn *conn;
    dpiXid xid;

    // setup table
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;
    if (dpiTest__truncateTable(testCase, conn) < 0)
        return DPI_FAILURE;

    // perform transaction
    dpiTest__populateXid(&xid);
    if (dpiConn_tpcBegin(conn, &xid, DPI_TPC_BEGIN_NEW, 0) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTest__insertRowsInTable(testCase, conn) < 0)
        return DPI_FAILURE;
    if (dpiConn_tpcPrepare(conn, NULL, &commitNeeded) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectUintEqual(testCase, commitNeeded, 1) < 0)
        return DPI_FAILURE;
    if (dpiConn_tpcCommit(conn, NULL, 1) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_release(conn) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // verify commit succeeded
    if (dpiTest__verifyData(testCase, 1) < 0)
        return DPI_FAILURE;

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_1706()
//   Call dpiConn_tpcBegin(), then execute some DML, then call
// dpiConn_tpcPrepare(); call dpiConn_tpcRollback() and create a new
// connection using the common connection creation method and verify that the
// changes have been rolled back (no error).
//-----------------------------------------------------------------------------
int dpiTest_1706(dpiTestCase *testCase, dpiTestParams *params)
{
    int commitNeeded;
    dpiConn *conn;
    dpiXid xid;

    // setup table
    if (dpiTestCase_getConnection(testCase, &conn) < 0)
        return DPI_FAILURE;
    if (dpiTest__truncateTable(testCase, conn) < 0)
        return DPI_FAILURE;

    // perform transaction
    dpiTest__populateXid(&xid);
    if (dpiConn_tpcBegin(conn, &xid, DPI_TPC_BEGIN_NEW, 0) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTest__insertRowsInTable(testCase, conn) < 0)
        return DPI_FAILURE;
    if (dpiConn_tpcPrepare(conn, NULL, &commitNeeded) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiTestCase_expectUintEqual(testCase, commitNeeded, 1) < 0)
        return DPI_FAILURE;
    if (dpiConn_tpcRollback(conn, NULL) < 0)
        return dpiTestCase_setFailedFromError(testCase);
    if (dpiConn_release(conn) < 0)
        return dpiTestCase_setFailedFromError(testCase);

    // verify rollback succeeded
    if (dpiTest__verifyData(testCase, 0) < 0)
        return DPI_FAILURE;

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_1707()
//   Call TPC functions with NULL connection (error DPI-1002).
//-----------------------------------------------------------------------------
int dpiTest_1707(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *expectedError = "DPI-1002:";

    dpiConn_tpcBegin(NULL, NULL, 0, 0);
    if (dpiTestCase_expectError(testCase, expectedError) < 0)
        return DPI_FAILURE;
    dpiConn_tpcCommit(NULL, NULL, 0);
    if (dpiTestCase_expectError(testCase, expectedError) < 0)
        return DPI_FAILURE;
    dpiConn_tpcEnd(NULL, NULL, 0);
    if (dpiTestCase_expectError(testCase, expectedError) < 0)
        return DPI_FAILURE;
    dpiConn_tpcForget(NULL, NULL);
    if (dpiTestCase_expectError(testCase, expectedError) < 0)
        return DPI_FAILURE;
    dpiConn_tpcPrepare(NULL, NULL, NULL);
    if (dpiTestCase_expectError(testCase, expectedError) < 0)
        return DPI_FAILURE;
    dpiConn_tpcRollback(NULL, NULL);
    if (dpiTestCase_expectError(testCase, expectedError) < 0)
        return DPI_FAILURE;

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// dpiTest_1708()
//   Call TPC functions that expect a valid XID with a null value (error
// DPI-1046).
//-----------------------------------------------------------------------------
int dpiTest_1708(dpiTestCase *testCase, dpiTestParams *params)
{
    const char *expectedError = "DPI-1046:";
    dpiConn *conn;

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

    dpiConn_tpcBegin(conn, NULL, 0, 0);
    if (dpiTestCase_expectError(testCase, expectedError) < 0)
        return DPI_FAILURE;
    dpiConn_tpcForget(conn, NULL);
    if (dpiTestCase_expectError(testCase, expectedError) < 0)
        return DPI_FAILURE;
    dpiConn_tpcPrepare(conn, NULL, NULL);
    if (dpiTestCase_expectError(testCase, expectedError) < 0)
        return DPI_FAILURE;

    return DPI_SUCCESS;
}


//-----------------------------------------------------------------------------
// main()
//-----------------------------------------------------------------------------
int main(int argc, char **argv)
{
    dpiTestSuite_initialize(1700);
    dpiTestSuite_addCase(dpiTest_1700,
            "dpiConn_tpcBegin() with valid parameters");
    dpiTestSuite_addCase(dpiTest_1701,
            "dpiConn_tpcBegin() with transactionIdLength > 64");
    dpiTestSuite_addCase(dpiTest_1702,
            "dpiConn_tpcBegin() with branchQualifierLength > 64");
    dpiTestSuite_addCase(dpiTest_1703,
            "dpiConn_tpcPrepare() with no transaction");
    dpiTestSuite_addCase(dpiTest_1704,
            "dpiConn_tpcCommit() of transaction with no DML");
    dpiTestSuite_addCase(dpiTest_1705,
            "dpiConn_tpcCommit() of transaction with DML");
    dpiTestSuite_addCase(dpiTest_1706,
            "dpiConn_tpcRollback() of transaction with DML");
    dpiTestSuite_addCase(dpiTest_1707,
            "verify tpc functions with NULL connection");
    dpiTestSuite_addCase(dpiTest_1708,
            "verify tpc functions with NULL XID");
    return dpiTestSuite_run();
}
