--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/persistentstorage/dbms/pcdbms/utable/UT_TRANS.CPP Fri Jan 22 11:06:30 2010 +0200
@@ -0,0 +1,742 @@
+// Copyright (c) 1998-2009 Nokia Corporation and/or its subsidiary(-ies).
+// All rights reserved.
+// This component and the accompanying materials are made available
+// under the terms of "Eclipse Public License v1.0"
+// which accompanies this distribution, and is available
+// at the URL "http://www.eclipse.org/legal/epl-v10.html".
+//
+// Initial Contributors:
+// Nokia Corporation - initial contribution.
+//
+// Contributors:
+//
+// Description:
+//
+
+#include "UT_STD.H"
+
+// Class RDbTransaction::CNotifier
+
+NONSHARABLE_CLASS(RDbTransaction::CNotifier) : public CDbNotifier
+ {
+public:
+ inline CNotifier( RDbTransaction& aTransaction );
+ ~CNotifier();
+//
+// void Event( RDbNotifier::TEvent aEvent );
+private:
+// void Complete( TInt aStatus );
+// from CDbNotifier
+// void Notify( TType aEvent, TRequestStatus& aStatus );
+ void Cancel();
+private:
+ RDbTransaction* iTransaction;
+// TRequestStatus* iStatus;
+ TInt iPending;
+ };
+
+inline RDbTransaction::CNotifier::CNotifier( RDbTransaction& aTransaction )
+ : iTransaction( &aTransaction )
+ {}
+
+RDbTransaction::CNotifier::~CNotifier()
+//
+// Cancel any outstanding request and extract from the transaction
+//
+ {
+ Cancel();
+ if ( iTransaction )
+ {
+ __ASSERT( iTransaction->iNotifier == this );
+ iTransaction->iNotifier = 0;
+ }
+ }
+
+//void RDbTransaction::CNotifier::Complete( TInt aStatus )
+// {
+// if ( iStatus )
+// {
+// iPending = 0;
+// User::RequestComplete( iStatus, aStatus );
+// }
+// }
+
+//void RDbTransaction::CNotifier::Notify( CDbNotifier::TType aType, TRequestStatus& aStatus )
+////
+//// Request for future notification. If the database is closed complete immediately
+////
+// {
+// __ASSERT( !iStatus );
+// __ASSERT( iPending >= 0 );
+// iStatus = &aStatus;
+// if ( iPending > RDbNotifier::EUnlock )
+// Complete( iPending );
+// else if ( !iTransaction )
+// Complete( RDbNotifier::EClose );
+// else
+// {
+// iPending = aType;
+// aStatus = KRequestPending;
+// }
+// }
+
+void RDbTransaction::CNotifier::Cancel()
+ {
+// Complete( KErrCancel );
+ }
+
+//void RDbTransaction::CNotifier::Event( RDbNotifier::TEvent aEvent )
+// {
+// if ( aEvent == RDbNotifier::EClose )
+// iTransaction = 0;
+// if ( iStatus )
+// {
+// __ASSERT( iPending < 0 );
+// if (aEvent == RDbNotifier::EUnlock && iPending == CDbNotifier::EChange )
+// ; // not interested in unlock events
+// else
+// Complete( aEvent );
+// }
+// else
+// {
+// __ASSERT( iPending >= 0 );
+// if ( aEvent > iPending )
+// iPending = aEvent; // save the event
+// }
+// }
+
+
+// Class RDbTransaction
+
+#ifdef _ASSERTIONS
+
+//void RDbTransaction::_Invariant() const
+////
+//// Invariance test
+////
+// {
+// if ( iLockCount == 0 )
+// { // nothing must be happening in this state
+// __ASSERT( iLockState == EDbReadLock );
+// __ASSERT( iAction == EDbReadLock );
+// __ASSERT( iUpdaters == 0 );
+// return;
+// }
+// switch ( iLockState & EState )
+// {
+// default:
+// __ASSERT( 0 );
+// case EDbReadLock:
+// {
+// __ASSERT( iAction == EDbReadLock );
+// __ASSERT( iLockCount > 0 ); // someone must have a lock
+// __ASSERT( iLockCount <= iMaxLock );
+// __ASSERT( iUpdaters == 0 );
+// __ASSERT( iPrimary.iState != 0 );
+// for (TInt ii = iLockCount - 1; --ii >= 0; )
+// __ASSERT( iSharers[ii].iState != 0 );
+// }
+// break;
+// case EDbCompactLock: // not allowed in user-transactions
+// case EDbRecoveryLock:
+// __ASSERT( iAction == iLockState );
+// __ASSERT( iLockCount == 1 ); // exactly one lock allowed
+// __ASSERT( iUpdaters == 0 );
+// __ASSERT( iPrimary.iState == 0 );
+// break;
+// case EDbXReadLock: // intention to write. No updates but exclusive
+// __ASSERT( iLockCount == 1 ); // exactly one lock allowed
+// switch ( iAction )
+// {
+// default:
+// __ASSERT( 0 );
+// case EDbReadLock: // must be in a transaction: cannot commit a write/schema mod when releasing a read lock
+// __ASSERT( iUpdaters == 0 );
+// __ASSERT( iPrimary.iState & static_cast<TUint>( ETransactionLock ) );
+// break;
+// case EDbWriteLock:
+// __ASSERT( iUpdaters > 0 );
+// break;
+// }
+// break;
+// case EDbWriteLock:
+// case EDbSchemaLock:
+// __ASSERT( iLockCount == 1 ); // exactly one lock allowed
+// switch ( iAction )
+// {
+// default:
+// __ASSERT( 0 );
+// case EDbReadLock: // must be in a transaction: cannot commit a write/schema mod when releasing a read lock
+// __ASSERT( iUpdaters == 0 );
+// __ASSERT( iPrimary.iState & static_cast<TUint>( ETransactionLock ) );
+// break;
+// case EDbWriteLock:
+// __ASSERT( iUpdaters > 0 );
+// __ASSERT( ( iLockState & EState ) == EDbWriteLock || ( iPrimary.iState & static_cast<TUint>( ETransactionLock ) ) );
+// break;
+// case EDbSchemaLock:
+// __ASSERT( ( iLockState & EState ) == EDbSchemaLock );
+// __ASSERT( iUpdaters == 0 );
+// break;
+// }
+// break;
+// }
+// }
+
+//template <class T> struct _InvariantFunc
+// {
+// static void Invariant( TAny* aPtr ) { ( (const T*)aPtr )->_Invariant(); }
+// };
+//
+//template <class T> inline TCleanupOperation _InvariantFunction( T* )
+// { return _InvariantFunc<T>::Invariant; }
+//
+//struct _Invariant
+// {
+// inline _Invariant( TCleanupOperation aOp, TAny* aPtr )
+// : iOp( aOp ), iPtr( aPtr )
+// { aOp( aPtr ); }
+// inline ~_Invariant()
+// { iOp( iPtr ); }
+//private:
+// TCleanupOperation iOp;
+// TAny* iPtr;
+// };
+
+#ifndef __LEAVE_EQUALS_THROW
+//struct _InvariantL
+// {
+// inline _InvariantL( TCleanupOperation aOp, TAny* aPtr )
+// { aOp( aPtr ); CleanupStack::PushL( TCleanupItem( aOp, aPtr ) ); }
+// inline ~_InvariantL()
+// { CleanupStack::PopAndDestroy(); }
+// };
+#endif //__LEAVE_EQUALS_THROW__
+
+//#define __INVARIANT struct _Invariant _invariant( _InvariantFunction( this ), this );
+
+//#ifdef __LEAVE_EQUALS_THROW__
+// #define __INVARIANT_L __INVARIANT
+//#else
+// #define __INVARIANT_L struct _InvariantL _invariant( _InvariantFunction( this ), this );
+//#endif //__LEAVE_EQUALS_THROW__
+
+#define __INVARIANT ( (void)0 )
+#define __INVARIANT_L ( (void)0 )
+#else // _ASSERTIONS
+
+#define __INVARIANT ( (void)0 )
+#define __INVARIANT_L ( (void)0 )
+
+#endif // _ASSERTIONS
+
+inline TDbLockType RDbTransaction::LockState() const
+ { return TDbLockType( iLockState & EState ); }
+
+void RDbTransaction::Close()
+ {
+ __ASSERT( !IsLocked() );
+ User::Free( iSharers );
+// Event( RDbNotifier::EClose );
+ }
+
+void RDbTransaction::DoCommitL()
+//
+// Commit any changes
+//
+ {
+ __ASSERT_ALWAYS( ( iPrimary.iState & ~ETransactionLock ) == 0, Panic( EDbStreamsPendingOnCommit ) );
+ iLockState |= EFailed;
+ Database().FlushL( LockState() );
+ Database().SynchL( LockState() );
+ Unlock( RDbNotifier::ECommit );
+ }
+
+void RDbTransaction::DoRollback()
+//
+// Rollback any changes
+//
+ {
+ __ASSERT_ALWAYS( ( iPrimary.iState & ~ETransactionLock ) == 0, Panic( EDbStreamsPendingOnRollback ) );
+ Database().Revert( LockState() );
+ Database().Abandon( LockState() );
+ if ( LockState() >= EDbWriteLock )
+ ++iRollback;
+ Unlock( RDbNotifier::ERollback );
+ }
+
+// explicit transactions
+
+void RDbTransaction::BeginL( const CDbObject& aObject )
+//
+// begin a user transaction. This first gains a shared read-lock
+//
+ {
+ __INVARIANT_L;
+ __ASSERT_ALWAYS( GetLock( aObject ) == 0, Panic( EDbBeginNestedTransaction ) );
+ ReadyL();
+ PrepareSLockL( aObject, TUint( ETransactionLock ) );
+ __ASSERT( iAction == EDbReadLock );
+ __ASSERT( iLockState == EDbReadLock );
+ ++iLockCount;
+ }
+
+void RDbTransaction::CommitL( const CDbObject& aObject )
+//
+// Commit a user transaction and release the lock
+// All updates must be complete for a write-lock
+//
+ {
+ __INVARIANT_L;
+ __ASSERT_ALWAYS( InTransaction( aObject ), Panic( EDbNoCurrentTransaction ) );
+ ReadyL();
+ if ( iLockCount > 1 )
+ {
+ TLock* lock = GetLock( aObject );
+ __ASSERT( lock );
+ __ASSERT_ALWAYS( lock->iState == static_cast<TUint>( ETransactionLock ), Panic( EDbStreamsPendingOnCommit ) );
+ Unlock( *lock );
+ }
+ else
+ {
+ __ASSERT_ALWAYS( iAction == EDbReadLock, Panic( EDbUpdatesPendingOnCommit ) );
+ DoCommitL();
+ }
+ }
+
+void RDbTransaction::Rollback( const CDbObject& aObject )
+//
+// Rollback a user transaction and release the lock
+// All updates must be complete/aborted for a write-lock
+//
+ {
+ __INVARIANT;
+ __ASSERT_ALWAYS( InTransaction( aObject ), Panic( EDbNoCurrentTransaction ) );
+ if ( iLockCount > 1 )
+ {
+ TLock* lock = GetLock( aObject );
+ __ASSERT( lock );
+ __ASSERT_ALWAYS( lock->iState == static_cast<TUint>( ETransactionLock ), Panic( EDbStreamsPendingOnRollback ) );
+ Unlock( *lock );
+ }
+ else
+ {
+ __ASSERT_ALWAYS( iAction == EDbReadLock, Panic( EDbUpdatesPendingOnRollback ) );
+ DoRollback();
+ }
+ }
+
+void RDbTransaction::PrepareSLockL( const CDbObject& aObject, TUint aInitState )
+//
+// prepare to acquire a shared read lock
+// if any holder has an exclusive lock this fails
+//
+ {
+ __ASSERT( GetLock( aObject ) == 0 ); // cannot get a 2nd shared lock
+//
+ THolder h = aObject.Context();
+ if ( iLockCount == 0 )
+ {
+ iPrimary.iHolder = h; // first lock, no other checks required
+ iPrimary.iState = aInitState;
+ }
+ else if ( iLockState != EDbReadLock )
+ __LEAVE( KErrLocked );
+ else
+ { // allocate a Sharers-slot
+ TLock* share = iSharers;
+ if ( iLockCount == iMaxLock )
+ {
+ TInt newsize = iMaxLock + ELockListGranularity;
+ if ( newsize > EMaxLock )
+ {
+ __LEAVE( KErrLocked );
+ return;
+ }
+ iSharers = share = ( TLock* )User::ReAllocL( share, ( newsize - 1 ) * sizeof( TLock ) );
+ iMaxLock = TUint8( newsize );
+ }
+ share += iLockCount - 1;
+ share->iHolder = h;
+ share->iState = aInitState;
+ }
+ }
+
+void RDbTransaction::PrepareXLockL( const CDbObject& aObject )
+//
+// prepare to acquire an exclusive lock
+// if any other holder has a lock this fails
+//
+ {
+ THolder h = aObject.Context();
+ switch ( iLockCount )
+ {
+ case 0: // no other holders, acquire the lock
+ iPrimary.iHolder = h;
+ iPrimary.iState = 0; // this is not a transaction lock
+ break;
+ case 1: // check we are the single Lock holder
+ if (iPrimary.iHolder != h)
+ __LEAVE( KErrLocked );
+ break;
+ default: // cannot get XLock
+ __LEAVE( KErrLocked );
+ break;
+ }
+ }
+
+void RDbTransaction::Unlock( RDbNotifier::TEvent aEvent )
+//
+// Remove the last lock and signal an event to the Notifier
+//
+ {
+ __ASSERT( iLockCount == 1 );
+ __ASSERT( ( iPrimary.iState & ~ETransactionLock ) == 0 );
+ TDbLockType ls = LockState();
+ Event( ls == EDbReadLock || ls == EDbXReadLock ? RDbNotifier::EUnlock : aEvent );
+ iLockCount = 0;
+ iAction = iLockState = EDbReadLock;
+ iUpdaters = 0;
+ Database().CheckIdle();
+ }
+
+void RDbTransaction::Unlock( RDbTransaction::TLock& aLock )
+//
+// Remove a shared lock holder from the list
+//
+ {
+ __ASSERT( iLockCount > 1 );
+ __ASSERT( LockState() == EDbReadLock );
+ __ASSERT( ( aLock.iState & ~ETransactionLock ) == 0 );
+ aLock = iSharers[--iLockCount - 1];
+ }
+
+RDbTransaction::TLock* RDbTransaction::GetLock( const CDbObject& aObject )
+//
+// Test if aObject holds any lock, and return it
+//
+ {
+ const THolder h = aObject.Context();
+ TInt lc = iLockCount;
+ if ( --lc >= 0 )
+ {
+ if ( iPrimary.iHolder == h )
+ return &iPrimary;
+ if ( lc > 0 )
+ {
+ TLock* const base = iSharers;
+ TLock* l = base + lc;
+ do {
+ if ( ( --l )->iHolder == h )
+ return l;
+ } while ( l > base );
+ }
+ }
+ return 0;
+ }
+
+TBool RDbTransaction::InTransaction( const CDbObject& aObject )
+//
+// Test if aObject holds a non-auto transaction
+//
+ {
+ __INVARIANT;
+ TLock* lock = GetLock( aObject );
+ return lock ? lock->iState & static_cast<TUint>( ETransactionLock ) : 0;
+ }
+
+void RDbTransaction::ReadPrepareL( const CDbObject& aObject )
+//
+// Check that aObject can gain a shared read lock and allocate required resources
+//
+ {
+ __INVARIANT_L;
+ if ( GetLock( aObject ) == 0 )
+ PrepareSLockL( aObject, 0 ); // prepare a S-Lock for the read
+ else if ( iAction == EDbCompactLock ) // Cannot already hold a compaction lock
+ __LEAVE( KErrAccessDenied );
+ }
+
+void RDbTransaction::ReadBegin( const CDbObject& aObject )
+//
+// Take a read-lock: ReadPrepareL(aObject) _must_ already have been called
+//
+ {
+ __INVARIANT;
+ TLock* lock = GetLock( aObject );
+ if ( !lock )
+ {
+ ++iLockCount;
+ lock = GetLock( aObject );
+ __ASSERT( lock );
+ }
+ ++lock->iState;
+ }
+
+void RDbTransaction::ReadRelease( const CDbObject& aObject )
+ {
+ __INVARIANT;
+ TLock* lock = GetLock( aObject );
+ __ASSERT( lock );
+ __ASSERT( ( lock->iState & ~ETransactionLock ) > 0 );
+ if ( --lock->iState == 0 )
+ { // not transaction-lock
+ if ( iLockCount > 1 )
+ Unlock( *lock );
+ else if ( iAction == EDbReadLock ) // no other locks to this client
+ Unlock( RDbNotifier::EUnlock );
+ }
+ }
+
+void RDbTransaction::DMLCheckL()
+//
+// Check that we can open a new rowset
+//
+ {
+ __INVARIANT_L;
+ ReadyL();
+ if ( iAction > EDbCompactLock )
+ __LEAVE( KErrAccessDenied );
+ }
+
+void RDbTransaction::DMLPrepareL( const CDbObject& aObject )
+//
+// Check that we can do DML, this should be called immediately prior to DMLBegin
+//
+ {
+ __INVARIANT_L;
+ PrepareXLockL( aObject );
+ if ( iAction>EDbWriteLock )
+ __LEAVE( KErrAccessDenied );
+ }
+
+void RDbTransaction::DMLBegin()
+//
+// A Rowset begins an update
+//
+ {
+ __INVARIANT;
+ __ASSERT( iAction == EDbReadLock || iAction == EDbWriteLock );
+ __ASSERT( ( iLockState & EFailed ) == 0 );
+ __ASSERT( iLockCount <= 1 );
+ if ( iAction == EDbReadLock )
+ iAction = EDbWriteLock;
+ if (iLockState == EDbReadLock )
+ iLockState = EDbXReadLock; // escalate lock to exclusive as we are now writing
+ ++iUpdaters;
+ iLockCount = 1;
+ }
+
+void RDbTransaction::DMLTouch()
+//
+// This must be called prior to putting DML updates
+//
+ {
+ __ASSERT( iAction == EDbWriteLock );
+ __ASSERT( iUpdaters > 0 );
+ TInt ls = iLockState;
+ if ( ls == EDbXReadLock )
+ ls = EDbWriteLock | EFailed;
+ else
+ ls |= EFailed;
+ iLockState = TUint8( ls );
+ }
+
+void RDbTransaction::DMLBeginLC()
+ {
+ DMLBegin();
+ CleanupStack::PushL( TCleanupItem( DMLAbandon, this ) );
+ DMLTouch();
+ }
+
+void RDbTransaction::DMLCommitL()
+//
+// A rowset has completed an update
+//
+ {
+ __INVARIANT_L;
+ __ASSERT( iAction == EDbWriteLock && ( iLockState & EFailed ) );
+ TInt updaters = iUpdaters - 1;
+ if ( updaters == 0 )
+ {
+ if ( ( iPrimary.iState & static_cast<TUint>( ETransactionLock ) ) == 0 )
+ {
+ __ASSERT( iLockState == ( EDbWriteLock | EFailed ) );
+ DoCommitL(); // automatic write-commit, release auto-lock
+ return;
+ }
+ iAction = EDbReadLock;
+ }
+ iUpdaters = updaters;
+ iLockState &= ~EFailed;
+ }
+
+void RDbTransaction::DMLRollback()
+//
+// Rollback a DML operation
+//
+ {
+ __INVARIANT;
+ __ASSERT( iAction == EDbWriteLock );
+ TInt updates = iUpdaters - 1;
+ if ( updates == 0 )
+ {
+ if ( ( iPrimary.iState & static_cast<TUint>( ETransactionLock ) ) == 0 )
+ {
+ __ASSERT( LockState() == EDbWriteLock || LockState() == EDbXReadLock );
+ DoRollback(); // automatic rollback now (may panic)
+ return;
+ }
+ iAction = EDbReadLock;
+ }
+ iUpdaters = updates;
+ }
+
+void RDbTransaction::DMLAbandon( TAny* aPtr )
+ {
+ STATIC_CAST( RDbTransaction*, aPtr )->DMLRollback();
+ }
+
+void RDbTransaction::DDLPrepareL( const CDbObject& aObject )
+//
+// Check that we can use the database for ddl and flush out any tables
+// should be called before DDLBegin
+//
+ {
+ __INVARIANT_L;
+ ReadyL();
+ PrepareXLockL( aObject );
+ if ( iAction != EDbReadLock || ( IsLocked() && iPrimary.iState != static_cast<TUint>( ETransactionLock ) ) )
+ __LEAVE( KErrAccessDenied ); // Cannot take sole ownership of the database
+ TInt ls = iLockState;
+ if ( ls >= EDbWriteLock )
+ { // ensure all table data is flushed as they may be "released"
+ iLockState = TUint8( ls | EFailed );
+ Database().FlushL( EDbWriteLock );
+ iLockState = TUint8( ls );
+ }
+ }
+
+void RDbTransaction::DDLBegin()
+//
+// A DDL object is about to start ops
+//
+ {
+ __INVARIANT;
+ __ASSERT( iAction == EDbReadLock );
+ __ASSERT( ( iLockState & EFailed ) == 0 );
+ __ASSERT( iLockCount <= 1 );
+ iLockState = iAction = EDbSchemaLock;
+ iLockCount = 1;
+ }
+
+void RDbTransaction::DDLBeginLC()
+ {
+ DDLBegin();
+ CleanupStack::PushL( TCleanupItem( DDLAbandon, this ) );
+ }
+
+void RDbTransaction::DDLCommitL()
+//
+// A DDL incremental object has completed
+//
+ {
+ __INVARIANT_L;
+ __ASSERT( iAction == EDbSchemaLock );
+ if ( ( iPrimary.iState & static_cast<TUint>( ETransactionLock ) ) == 0 )
+ {
+ __ASSERT( iLockState == EDbSchemaLock );
+ DoCommitL(); // release auto-lock
+ }
+ else
+ iAction = EDbReadLock;
+ }
+
+void RDbTransaction::DDLRollback()
+//
+// Rollback a DDL operation
+//
+ {
+ __INVARIANT;
+ __ASSERT( iAction == EDbSchemaLock );
+ iLockState |= EFailed;
+ if ( ( iPrimary.iState & static_cast<TUint>( ETransactionLock ) ) == 0 )
+ {
+ __ASSERT( iLockState == ( EDbSchemaLock | EFailed ) );
+ DoRollback(); // release auto-lock
+ }
+ else
+ iAction = EDbReadLock;
+ }
+
+void RDbTransaction::DDLAbandon( TAny* aPtr )
+ {
+ STATIC_CAST( RDbTransaction*, aPtr )->DDLRollback();
+ }
+
+// recovery. Nothing else can be done at the same time as this
+
+void RDbTransaction::UtilityPrepareL( const CDbObject& aObject )
+//
+// Check that we are in a state to run a utility
+//
+ {
+ __INVARIANT_L;
+ ReadyL();
+ PrepareXLockL( aObject );
+ if ( IsLocked() ) // utilities not allowed in user transaction
+ __LEAVE( KErrAccessDenied );
+ }
+
+void RDbTransaction::UtilityBegin( CDbDatabase::TUtility aType )
+//
+// Database Recovery object is about to start
+//
+ {
+ __INVARIANT;
+ __ASSERT( !IsLocked() );
+ if ( aType == CDbDatabase::ERecover )
+ iLockState = iAction = EDbRecoveryLock;
+ else
+ iLockState = iAction = EDbCompactLock;
+ iLockCount = 1;
+ }
+
+void RDbTransaction::UtilityCommitL()
+//
+// Database Recovery has completed
+//
+ {
+ __INVARIANT_L;
+ Database().SynchL( LockState() );
+ Unlock( iAction == EDbRecoveryLock ? RDbNotifier::ERecover : RDbNotifier::EUnlock ); // release auto-lock
+ }
+
+void RDbTransaction::UtilityRollback()
+ {
+ __INVARIANT;
+ Database().Revert( LockState() );
+ Unlock( RDbNotifier::EUnlock ); // release auto-lock
+ }
+
+CDbNotifier* RDbTransaction::NotifierL()
+//
+// Only support a single notifier for the database (server multiplexes)
+//
+ {
+ if ( iNotifier )
+ __LEAVE( KErrNotSupported );
+ return iNotifier = new( ELeave ) CNotifier( *this );
+ }
+
+void RDbTransaction::Event( RDbNotifier::TEvent aEvent )
+//
+// Report an event to the Notifier
+// If the lock was less than a write lock, report unlock only: no commit or rollback
+//
+ {
+// if ( iNotifier )
+// iNotifier->Event( aEvent );
+ }
+
+