// IBPP, Transaction class implementation
/*
  (C) Copyright 2000-2006 T.I.P. Group S.A. and the IBPP Team (www.ibpp.org)

  The contents of this file are subject to the IBPP License (the "License");
  you may not use this file except in compliance with the License.  You may
  obtain a copy of the License at http://www.ibpp.org or in the 'license.txt'
  file which must have been distributed along with this file.

  This software, distributed under the License, is distributed on an "AS IS"
  basis, WITHOUT WARRANTY OF ANY KIND, either express or implied.  See the
  License for the specific language governing rights and limitations
  under the License.
*/


#ifdef _MSC_VER
#pragma warning(disable: 4786 4996)
#ifndef _DEBUG
#pragma warning(disable: 4702)
#endif
#endif

#include "_ibpp.h"

#ifdef HAS_HDRSTOP
#pragma hdrstop
#endif

#include <algorithm>

using namespace ibpp_internals;

//  (((((((( OBJECT INTERFACE IMPLEMENTATION ))))))))

void TransactionImpl::AttachDatabase(IBPP::Database db,
    IBPP::TAM am, IBPP::TIL il, IBPP::TLR lr, IBPP::TFF flags)
{
    if (db.intf() == 0)
        throw LogicExceptionImpl("Transaction::AttachDatabase",
                _("Can't attach an unbound Database."));

    AttachDatabaseImpl(dynamic_cast<DatabaseImpl*>(db.intf()), am, il, lr, flags);
}

void TransactionImpl::DetachDatabase(IBPP::Database db)
{
    if (db.intf() == 0)
        throw LogicExceptionImpl("Transaction::DetachDatabase",
                _("Can't detach an unbound Database."));

    DetachDatabaseImpl(dynamic_cast<DatabaseImpl*>(db.intf()));
}

void TransactionImpl::AddReservation(IBPP::Database db,
    const std::string& table, IBPP::TTR tr)
{
    if (mHandle != 0)
        throw LogicExceptionImpl("Transaction::AddReservation",
                _("Can't add table reservation if Transaction started."));
    if (db.intf() == 0)
        throw LogicExceptionImpl("Transaction::AddReservation",
                _("Can't add table reservation on an unbound Database."));

    // Find the TPB associated with this database
    std::vector<DatabaseImpl*>::iterator pos =
        std::find(mDatabases.begin(), mDatabases.end(), dynamic_cast<DatabaseImpl*>(db.intf()));
    if (pos != mDatabases.end())
    {
        size_t index = pos - mDatabases.begin();
        TPB* tpb = mTPBs[index];

        // Now add the reservations to the TPB
        switch (tr)
        {
            case IBPP::trSharedWrite :
                    tpb->Insert(isc_tpb_lock_write);
                    tpb->Insert(table);
                    tpb->Insert(isc_tpb_shared);
                    break;
            case IBPP::trSharedRead :
                    tpb->Insert(isc_tpb_lock_read);
                    tpb->Insert(table);
                    tpb->Insert(isc_tpb_shared);
                    break;
            case IBPP::trProtectedWrite :
                    tpb->Insert(isc_tpb_lock_write);
                    tpb->Insert(table);
                    tpb->Insert(isc_tpb_protected);
                    break;
            case IBPP::trProtectedRead :
                    tpb->Insert(isc_tpb_lock_read);
                    tpb->Insert(table);
                    tpb->Insert(isc_tpb_protected);
                    break;
            default :
                    throw LogicExceptionImpl("Transaction::AddReservation",
                        _("Illegal TTR value detected."));
        }
    }
    else throw LogicExceptionImpl("Transaction::AddReservation",
            _("The database connection you specified is not attached to this transaction."));
}

void TransactionImpl::Start()
{
    if (mHandle != 0) return;   // Already started anyway

    if (mDatabases.empty())
        throw LogicExceptionImpl("Transaction::Start", _("No Database is attached."));

    struct ISC_TEB
    {
        ISC_LONG* db_ptr;
        ISC_LONG tpb_len;
        char* tpb_ptr;
    } * teb = new ISC_TEB[mDatabases.size()];

    unsigned i;
    for (i = 0; i < mDatabases.size(); i++)
    {
        if (mDatabases[i]->GetHandle() == 0)
        {
            // All Databases must be connected to Start the transaction !
            delete [] teb;
            throw LogicExceptionImpl("Transaction::Start",
                    _("All attached Database should have been connected."));
        }
        teb[i].db_ptr = (ISC_LONG*) mDatabases[i]->GetHandlePtr();
        teb[i].tpb_len = mTPBs[i]->Size();
        teb[i].tpb_ptr = mTPBs[i]->Self();
    }

    IBS status;
    (*getGDS().Call()->m_start_multiple)(status.Self(), &mHandle, (short)mDatabases.size(), teb);
    delete [] teb;
    if (status.Errors())
    {
        mHandle = 0;    // Should be, but better be sure...
        throw SQLExceptionImpl(status, "Transaction::Start");
    }
}

void TransactionImpl::Commit()
{
    if (mHandle == 0)
        throw LogicExceptionImpl("Transaction::Commit", _("Transaction is not started."));

    IBS status;

    (*getGDS().Call()->m_commit_transaction)(status.Self(), &mHandle);
    if (status.Errors())
        throw SQLExceptionImpl(status, "Transaction::Commit");
    mHandle = 0;    // Should be, better be sure

    /*
    size_t i;
    for (i = mStatements.size(); i != 0; i--)
        mStatements[i-1]->CursorFree();
    */
}

void TransactionImpl::CommitRetain()
{
    if (mHandle == 0)
        throw LogicExceptionImpl("Transaction::CommitRetain", _("Transaction is not started."));

    IBS status;

    (*getGDS().Call()->m_commit_retaining)(status.Self(), &mHandle);
    if (status.Errors())
        throw SQLExceptionImpl(status, "Transaction::CommitRetain");
}

void TransactionImpl::Rollback()
{
    if (mHandle == 0) return;   // Transaction not started anyway

    IBS status;

    (*getGDS().Call()->m_rollback_transaction)(status.Self(), &mHandle);
    if (status.Errors())
        throw SQLExceptionImpl(status, "Transaction::Rollback");
    mHandle = 0;    // Should be, better be sure

    /*
    size_t i;
    for (i = mStatements.size(); i != 0; i--)
        mStatements[i-1]->CursorFree();
    */
}

void TransactionImpl::RollbackRetain()
{
    if (mHandle == 0)
        throw LogicExceptionImpl("Transaction::RollbackRetain", _("Transaction is not started."));

    IBS status;

    (*getGDS().Call()->m_rollback_retaining)(status.Self(), &mHandle);
    if (status.Errors())
        throw SQLExceptionImpl(status, "Transaction::RollbackRetain");
}

IBPP::ITransaction* TransactionImpl::AddRef()
{
    ASSERTION(mRefCount >= 0);
    ++mRefCount;
    return this;
}

void TransactionImpl::Release()
{
    // Release cannot throw, except in DEBUG builds on assertion
    ASSERTION(mRefCount >= 0);
    --mRefCount;
    try { if (mRefCount <= 0) delete this; }
        catch (...) { }
}

//  (((((((( OBJECT INTERNAL METHODS ))))))))

void TransactionImpl::Init()
{
    mHandle = 0;
    mDatabases.clear();
    mTPBs.clear();
    mStatements.clear();
    mBlobs.clear();
    mArrays.clear();
}

void TransactionImpl::AttachStatementImpl(StatementImpl* st)
{
    if (st == 0)
        throw LogicExceptionImpl("Transaction::AttachStatement",
                    _("Can't attach a 0 Statement object."));

    mStatements.push_back(st);
}

void TransactionImpl::DetachStatementImpl(StatementImpl* st)
{
    if (st == 0)
        throw LogicExceptionImpl("Transaction::DetachStatement",
                _("Can't detach a 0 Statement object."));

    mStatements.erase(std::find(mStatements.begin(), mStatements.end(), st));
}

void TransactionImpl::AttachBlobImpl(BlobImpl* bb)
{
    if (bb == 0)
        throw LogicExceptionImpl("Transaction::AttachBlob",
                    _("Can't attach a 0 BlobImpl object."));

    mBlobs.push_back(bb);
}

void TransactionImpl::DetachBlobImpl(BlobImpl* bb)
{
    if (bb == 0)
        throw LogicExceptionImpl("Transaction::DetachBlob",
                _("Can't detach a 0 BlobImpl object."));

    mBlobs.erase(std::find(mBlobs.begin(), mBlobs.end(), bb));
}

void TransactionImpl::AttachArrayImpl(ArrayImpl* ar)
{
    if (ar == 0)
        throw LogicExceptionImpl("Transaction::AttachArray",
                    _("Can't attach a 0 ArrayImpl object."));

    mArrays.push_back(ar);
}

void TransactionImpl::DetachArrayImpl(ArrayImpl* ar)
{
    if (ar == 0)
        throw LogicExceptionImpl("Transaction::DetachArray",
                _("Can't detach a 0 ArrayImpl object."));

    mArrays.erase(std::find(mArrays.begin(), mArrays.end(), ar));
}

void TransactionImpl::AttachDatabaseImpl(DatabaseImpl* dbi,
    IBPP::TAM am, IBPP::TIL il, IBPP::TLR lr, IBPP::TFF flags)
{
    if (mHandle != 0)
        throw LogicExceptionImpl("Transaction::AttachDatabase",
                _("Can't attach a Database if Transaction started."));
    if (dbi == 0)
        throw LogicExceptionImpl("Transaction::AttachDatabase",
                _("Can't attach a null Database."));

    mDatabases.push_back(dbi);

    // Prepare a new TPB
    TPB* tpb = new TPB;
    if (am == IBPP::amRead) tpb->Insert(isc_tpb_read);
    else tpb->Insert(isc_tpb_write);

    switch (il)
    {
        case IBPP::ilConsistency :      tpb->Insert(isc_tpb_consistency); break;
        case IBPP::ilReadDirty :        tpb->Insert(isc_tpb_read_committed);
                                        tpb->Insert(isc_tpb_rec_version); break;
        case IBPP::ilReadCommitted :    tpb->Insert(isc_tpb_read_committed);
                                        tpb->Insert(isc_tpb_no_rec_version); break;
        default :                       tpb->Insert(isc_tpb_concurrency); break;
    }

    if (lr == IBPP::lrNoWait) tpb->Insert(isc_tpb_nowait);
    else tpb->Insert(isc_tpb_wait);

    if (flags & IBPP::tfIgnoreLimbo)    tpb->Insert(isc_tpb_ignore_limbo);
    if (flags & IBPP::tfAutoCommit)     tpb->Insert(isc_tpb_autocommit);
    if (flags & IBPP::tfNoAutoUndo)     tpb->Insert(isc_tpb_no_auto_undo);

    mTPBs.push_back(tpb);

    // Signals the Database object that it has been attached to the Transaction
    dbi->AttachTransactionImpl(this);
}

void TransactionImpl::DetachDatabaseImpl(DatabaseImpl* dbi)
{
    if (mHandle != 0)
        throw LogicExceptionImpl("Transaction::DetachDatabase",
                _("Can't detach a Database if Transaction started."));
    if (dbi == 0)
        throw LogicExceptionImpl("Transaction::DetachDatabase",
                _("Can't detach a null Database."));

    std::vector<DatabaseImpl*>::iterator pos =
        std::find(mDatabases.begin(), mDatabases.end(), dbi);
    if (pos != mDatabases.end())
    {
        size_t index = pos - mDatabases.begin();
        TPB* tpb = mTPBs[index];
        mDatabases.erase(pos);
        mTPBs.erase(mTPBs.begin()+index);
        delete tpb;
    }

    // Signals the Database object that it has been detached from the Transaction
    dbi->DetachTransactionImpl(this);
}

TransactionImpl::TransactionImpl(DatabaseImpl* db,
    IBPP::TAM am, IBPP::TIL il, IBPP::TLR lr, IBPP::TFF flags)
    : mRefCount(0)
{
    Init();
    AttachDatabaseImpl(db, am, il, lr, flags);
}

TransactionImpl::~TransactionImpl()
{
    // Rollback the transaction if it was Started
    try { if (Started()) Rollback(); }
        catch (...) { }

    // Let's detach cleanly all Blobs from this Transaction.
    // No Blob object can still maintain pointers to this
    // Transaction which is disappearing.
    //
    // We use a reverse traversal of the array to avoid loops.
    // The array shrinks on each loop (mBbCount decreases).
    // And during the deletion, there is a packing of the array through a
    // copy of elements from the end to the beginning of the array.
    try {
        while (mBlobs.size() > 0)
            mBlobs.back()->DetachTransactionImpl();
    } catch (...) { }

    // Let's detach cleanly all Arrays from this Transaction.
    // No Array object can still maintain pointers to this
    // Transaction which is disappearing.
    try {
        while (mArrays.size() > 0)
            mArrays.back()->DetachTransactionImpl();
    } catch (...) { }

    // Let's detach cleanly all Statements from this Transaction.
    // No Statement object can still maintain pointers to this
    // Transaction which is disappearing.
    try {
        while (mStatements.size() > 0)
            mStatements.back()->DetachTransactionImpl();
    } catch (...) { }

    // Very important : let's detach cleanly all Databases from this
    // Transaction. No Database object can still maintain pointers to this
    // Transaction which is disappearing.
    try {
        while (mDatabases.size() > 0)
        {
            size_t i = mDatabases.size()-1;
            DetachDatabaseImpl(mDatabases[i]);  // <-- remove link to database from mTPBs
                                            // array and destroy TPB object
                                            // Fixed : Maxim Abrashkin on 12 Jun 2002
            //mDatabases.back()->DetachTransaction(this);
        }
    } catch (...) { }
}
