module firebird_transaction; private import ibase; private import firebirdCommon; private import firebird_database; private import firebird_status; class Transaction { private: STATUS status; int mRefCount; // Reference counter isc_tr_handle mHandle = null; // Transaction InterBase //static TRANSACTIONPARAMETERBLOCK tpb; Database* db = null; ubyte[] mBuffer; // Dynamically allocated TPB structure short mSize, mAlloc; // Its used size in bytes - Its allocated size void TPBNew() {mBuffer.length = 0; mSize = 0; mAlloc = 0; } void TPBGrow(int needed) { // Allocate or grow the mBuffer, so that 'needed' bytes can be written (at least) if(mBuffer.length == 0) ++needed; // Initial alloc will require one more byte if((mSize + needed) > mAlloc) { // We need to grow the buffer. We use increments of BUFFERINCR bytes. needed = (needed / BUFFERINCR + 1) * BUFFERINCR; ubyte[] newbuffer; newbuffer.length = mAlloc + needed; if(mBuffer.length == 0) { // Initial allocation, initialize the version tag newbuffer[0] = isc_dpb_version1; mSize = 1; } else { // Move the old buffer content to the new one mBuffer = newbuffer[0..mSize]; mBuffer.length = 0; } mBuffer = newbuffer; mAlloc += needed; } } // Insert a flag item void TPBInsert(char item) { TPBGrow(1); mBuffer[mSize++] = item; } // Insert a string(typically table name) void TPBInsert(ref char[] data) { int len = data.length; TPBGrow(len + 1); mBuffer[mSize++] = cast(char)(len); foreach(char c; data) mBuffer ~= cast(ubyte) c; mSize += len; } // Clears the TPB void TPBReset() { if(mSize != 0) { mBuffer.length = 0; mSize = 0; mAlloc = 0; } } public: isc_tr_handle *GetTransHandle() { return &mHandle; } void AttachDatabase(Database *dbi, TAM am, TIL il, TLR lr, TFF flags) { if(dbi is null) throw new Exception("Transaction::AttachDatabase Can't attach an unbound Database."); if(mHandle !is null) throw new Exception("Transaction::AttachDatabase Can't attach a Database if Transacton started."); if(dbi is null) throw new Exception("Transaction::AttachDatabase Can't attach a null Database."); db = dbi; TPBNew(); if(am == TAM.amRead) TPBInsert(isc_tpb_read); else TPBInsert(isc_tpb_write); switch(il) { case TIL.ilConsistency : TPBInsert(isc_tpb_consistency); break; case TIL.ilReadDirty : TPBInsert(isc_tpb_read_committed); TPBInsert(isc_tpb_rec_version); break; case TIL.ilReadCommitted :TPBInsert(isc_tpb_read_committed); TPBInsert(isc_tpb_no_rec_version); break; default : TPBInsert(isc_tpb_concurrency); break; } if(lr == TLR.lrNoWait) TPBInsert(isc_tpb_nowait); else TPBInsert(isc_tpb_wait); if(flags & TFF.tfIgnoreLimbo) TPBInsert(isc_tpb_ignore_limbo); if(flags & TFF.tfAutoCommit) TPBInsert(isc_tpb_autocommit); if(flags & TFF.tfNoAutoUndo) TPBInsert(isc_tpb_no_auto_undo); } this(Database *dbs, TAM am = TAM.amWrite, TIL il = TIL.ilConcurrency, TLR lr = TLR.lrWait, TFF flags = TFF.tfNone) { status = new STATUS; mRefCount = 0; AttachDatabase(dbs, am, il, lr, flags); } ~this() { try { // Rollback the transaction if it was Started if(Started()) Rollback(); } catch(Exception e) { } } /+ void AddReservation(Database db, ref char[] table, TTR tr) { if(mHandle !is null) throw new Exception("Transaction::AddReservation Can't add table reservation if Transaction started."); if(db.GetHandle() is null) throw new Exception("Transaction::AddReservation Can't add table reservation on an unbound Database."); // Find the TPB associated with this database int pos = DatabaseList.find(db); if(pos != -1) { TRANSACTIONPARAMETERBLOCK* tpb = TPBList.get(pos); // Now add the reservations to the TPB switch(tr) { case TTR.trSharedWrite : tpb.Insert(isc_tpb_lock_write); tpb.Insert(table); tpb.Insert(isc_tpb_shared); break; case TTR.trSharedRead : tpb.Insert(isc_tpb_lock_read); tpb.Insert(table); tpb.Insert(isc_tpb_shared); break; case TTR.trProtectedWrite : tpb.Insert(isc_tpb_lock_write); tpb.Insert(table); tpb.Insert(isc_tpb_protected); break; case TTR.trProtectedRead : tpb.Insert(isc_tpb_lock_read); tpb.Insert(table); tpb.Insert(isc_tpb_protected); break; default : throw new Exception("Transaction::AddReservation Illegal TTR value detected."); } } else throw new Exception("Transaction::AddReservation The database connection you specified is not attached to this transaction."); } +/ void Start() { if(mHandle !is null) return; // Already started anyway struct ISC_TEB { ISC_LONG* db_ptr; ISC_LONG tpb_len; char* tpb_ptr; } ISC_TEB *teb = new ISC_TEB; teb.db_ptr = cast(ISC_LONG*) *db.GetDbHandle; teb.tpb_len = mBuffer.length; teb.tpb_ptr = cast(char *) mBuffer.ptr; status.StatusReset(); isc_start_multiple(status.StatusHandle(), &mHandle, cast(short) 1, &teb); delete teb; if(status.StatusErrors()) { if(mHandle != null) // Should be, but better be sure... mHandle = null; throw new Exception("Transaction::Start " ~ status.toString); } } bool Started() { return mHandle is null ? false : true; } void Commit() { if(mHandle is null) throw new Exception("Transaction::Commit Transaction is not started."); status.StatusReset(); isc_commit_transaction(status.StatusHandle, &mHandle); if(status.StatusErrors()) throw new Exception("Transaction::Commit status"); mHandle = null; // Should be, better be sure } void Rollback() { if(mHandle is null) return; // Transaction not started anyway status.StatusReset(); isc_rollback_transaction(status.StatusHandle, &mHandle); if(status.StatusErrors()) throw new Exception("Transaction::Rollback status"); mHandle = null; // Should be, better be sure } void CommitRetain() { if(mHandle is null) throw new Exception("Transaction::CommitRetain Transaction is not started."); status.StatusReset(); isc_commit_retaining(status.StatusHandle, &mHandle); if(status.StatusErrors()) throw new Exception("Transaction::CommitRetain status"); } void RollbackRetain() { if(mHandle is null) throw new Exception("Transaction::RollbackRetain Transaction is not started."); status.StatusReset(); isc_rollback_retaining(status.StatusHandle, &mHandle); if(status.StatusErrors()) throw new Exception("Transaction::RollbackRetain status"); } }