persistentstorage/dbms/utable/UT_TABLE.CPP
changeset 0 08ec8eefde2f
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/persistentstorage/dbms/utable/UT_TABLE.CPP	Fri Jan 22 11:06:30 2010 +0200
@@ -0,0 +1,672 @@
+// 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"
+
+#define UNUSED_VAR(a) a = a
+
+const TUint KTableExpiry=0x100000;	// ~1.0s
+
+// Class Blob cleanup
+
+NONSHARABLE_CLASS(CDbBlobCleanup) : public CArrayFixFlat<TDbBlobId>
+	{
+public:
+	static CDbBlobCleanup* NewLC(CDbBlobSpace& aBlobSpace);
+	~CDbBlobCleanup();
+private:
+	inline CDbBlobCleanup(CDbBlobSpace& aBlobSpace);
+private:
+	CDbBlobSpace& iBlobSpace;
+	};
+
+inline CDbBlobCleanup::CDbBlobCleanup(CDbBlobSpace& aBlobSpace)
+	: CArrayFixFlat<TDbBlobId>(8),iBlobSpace(aBlobSpace)
+	{}
+
+CDbBlobCleanup* CDbBlobCleanup::NewLC(CDbBlobSpace& aBlobSpace)
+	{
+	CDbBlobCleanup* self=new(ELeave) CDbBlobCleanup(aBlobSpace);
+	CleanupStack::PushL(self);
+	return self;
+	}
+
+CDbBlobCleanup::~CDbBlobCleanup()
+	{
+	TInt count=Count();
+	if (count)
+		{
+		const TDbBlobId* blob=&(*this)[0];
+		const TDbBlobId* const end=blob+count;
+		for (;blob<end;++blob)
+			iBlobSpace.Delete(*blob);
+		}
+	}
+
+// Class CDbTable
+
+
+EXPORT_C CDbTable::CDbTable(CDbTableDatabase& aDatabase,const CDbTableDef& aDef)
+	: iDatabase(&aDatabase),iDef(&aDef)
+	{
+	aDatabase.Open();
+	aDatabase.AddTable(*this);		// we reference database
+	}
+
+EXPORT_C CDbTable::~CDbTable()
+//
+// Destroy components
+//
+	{
+	__ASSERT(!InUse());		// cannot be directly deleted
+	if (IsActive())
+		Disconnect();
+	}
+
+void CDbTable::Disconnect()
+//
+// Disconnect the table from the database collection.
+//
+	{
+	__ASSERT(IsActive());
+	Database().RemoveTable(*this);
+	TRAPD(errCode, ApplyToComponentsL(CDbRecordBase::DoDelete));
+    UNUSED_VAR(errCode);
+	}
+
+void CDbTable::Open()
+	{
+	__ASSERT(IsActive());
+	TInt r=++iRef;
+	if (r<=0)
+		{	// were idle or cached
+		if (r<0)
+			{
+			Cache().Release(*this);	// was cached
+			iRef=0;
+			}
+		Database().Open();
+		}
+	}
+
+void CDbTable::Close()
+//
+// We may destroy this object when the last reference goes away
+//
+	{
+	__ASSERT(InUse());
+	if (--iRef<0)
+		{
+		if (!IsActive())
+			delete this;		// disconnected table
+		else
+			{
+			CDbTableDatabase& db=Database();
+			if (!db.Transaction().IsLocked())
+				Idle();			// no transaction, idle now
+			db.Close();			// this must be done last to avoid early self destruction
+			}
+		}
+	}
+
+void CDbTable::Idle()
+//
+// Called when idle, change to cached state
+//
+	{
+	__ASSERT(IsIdle());
+	__ASSERT(IsActive());
+//
+	iRef=ECached;
+	Cache().Hold(this,KTableExpiry);	// may delete this
+	}
+
+void CDbTable::FlushL()
+//
+// Ensure all records objects are flushed
+//
+	{
+	__ASSERT(IsActive());
+	if (iRef!=ECached)
+		ApplyToComponentsL(CDbRecordBase::DoFlushL);
+	}
+
+void CDbTable::Abandon()
+//
+// Discard all components
+//
+	{
+	__ASSERT(IsActive());
+	TRAPD(errCode, ApplyToComponentsL(CDbRecordBase::DoAbandon));
+    UNUSED_VAR(errCode);
+	iIndexesEnd=NULL;	// flags indexes as abandoned
+	++iGeneration;
+	}
+
+void CDbTable::Release()
+//
+// Release the table and all its cursors as DDL is about to begin
+//
+	{
+	__ASSERT(IsActive());
+	switch (iRef)
+		{
+	case ECached:
+		Cache().Release(*this);
+		// fall throught to Idle
+	case EIdle:
+		delete this;
+		break;
+	default:
+		__ASSERT(InUse());
+		Database().Close();
+		Disconnect();
+		iDatabase=0;		// this marks us as released
+		iDef=0;
+		break;
+		}
+	}
+
+void CDbTable::ApplyToBlobsL(RDbRow& aRow,TBlobFuncL aFuncL,CDbBlobCleanup* aCleanup)
+	{
+	__ASSERT(Def().Columns().HasLongColumns());
+	CDbBlobSpace* blobs=BlobsL();
+	__ASSERT(blobs);
+	TDbColNo col=1;
+	HDbColumnSet::TIteratorC iter=Def().Columns().Begin();
+	const HDbColumnSet::TIteratorC end=Def().Columns().End();
+	do
+		{
+		if (!TDbCol::IsLong(iter->Type()))
+			continue;
+		const TDbColumnC column(aRow,col);
+		if (column.IsNull())
+			continue;
+		aFuncL(*blobs,CONST_CAST(TDbBlob&,column.Blob()),iter->Type(),aCleanup);
+		} while (++col,++iter<end);
+	}
+
+LOCAL_C void DuplicateBlobL(CDbBlobSpace& aBlobStore,TDbBlob& aBlob,TDbColType aType,CDbBlobCleanup* aCleanup)
+	{
+	__ASSERT(aCleanup);
+	if (aBlob.IsInline())
+		return;
+// need to duplicate blob
+	RReadStream old(aBlobStore.ReadLC(aBlob.Id(),aType));
+	TDbBlobId& newId=aCleanup->ExtendL();
+	newId=KDbNullBlobId;
+	RWriteStream dup(aBlobStore.CreateL(newId,aType));
+	dup.PushL();
+	dup.WriteL(old,aBlob.Size());
+	dup.CommitL();
+	CleanupStack::PopAndDestroy(2);		// old and dup streams
+	aBlob.SetId(newId);		// row is writable
+	}
+
+void CDbTable::DuplicateBlobsL(RDbRow& aRow)
+//
+// duplicate any blobs
+//
+	{
+	if (!Def().Columns().HasLongColumns())
+		return;
+
+	CDbBlobCleanup* cleaner=CDbBlobCleanup::NewLC(*BlobsL());
+	ApplyToBlobsL(aRow,DuplicateBlobL,cleaner);
+	cleaner->Reset();
+	CleanupStack::PopAndDestroy();
+	}
+
+TBool CDbTable::ExistsL(TDbRecordId aRecordId)
+//
+// Check that aRecordId is good for this table
+//
+	{
+	__ASSERT(IsActive() && InUse());
+	return RecordsL().ExistsL(aRecordId);
+	}
+
+void CDbTable::NewRowL(RDbRow& aRow)
+//
+// Initialise any auto-increment columns in the row
+//
+	{
+	const HDbColumnSet& columns=Def().Columns();
+	if (!columns.HasAutoIncrement())
+		return;
+
+	TUint value=RecordsL().AutoIncrementL();
+	TDbColNo col=1;
+	HDbColumnSet::TIteratorC iter=columns.Begin();
+	const HDbColumnSet::TIteratorC end=columns.End();
+	do
+		{
+		if (iter->iAttributes&TDbCol::EAutoIncrement)
+			{
+			// auto-increment only for integral types <=32 bits wide
+			__ASSERT(iter->iType<=EDbColUint32);
+			TDbColumn column(aRow,col);
+			column.SetL(TUint32(value));
+			}
+		} while (++col,++iter<end);
+	}
+
+void CDbTable::ValidateL(const RDbRow& aRow)
+//
+// Ensure that the column data conforms to type size/flags etc
+//
+	{
+	HDbColumnSet::TIteratorC iter=Def().Columns().Begin();
+	const HDbColumnSet::TIteratorC end=Def().Columns().End();
+	const TDbCell* const last=aRow.Last();
+	for (const TDbCell* column=aRow.First();column<last;++iter,column=column->Next())
+		{
+		TInt size=column->Length();
+		if (size==0)
+			{	// check for Null
+			if (iter->iAttributes&TDbCol::ENotNull)
+				{
+				__LEAVE(KErrNotFound);
+				return;
+				}
+			continue;
+			}
+		const TUint32* data=(const TUint32*)column->Data();
+		switch (iter->iType)
+			{
+		case EDbColBit:
+			if (*data>1)
+				__LEAVE(KErrOverflow);
+			break;
+		case EDbColInt8:
+			{
+			TInt val=*data;
+			if (TInt8(val)!=val)
+				__LEAVE(KErrOverflow);
+			}
+			break;
+		case EDbColInt16:
+			{
+			TInt val=*data;
+			if (TInt16(val)!=val)
+				__LEAVE(KErrOverflow);
+			}
+			break;
+		case EDbColUint8:
+			{
+			TUint val=*data;
+			if (TUint8(val)!=val)
+				__LEAVE(KErrOverflow);
+			}
+			break;
+		case EDbColUint16:
+			{
+			TUint val=*data;
+			if (TUint16(val)!=val)
+				__LEAVE(KErrOverflow);
+			}
+			break;
+		case EDbColText16:
+			size>>=1;
+		case EDbColBinary:
+		case EDbColText8:
+			if (iter->iMaxLength==KDbUndefinedLength)
+				break;
+			if (size>iter->iMaxLength)
+				__LEAVE(KErrOverflow);
+			break;
+		case EDbColLongBinary:
+		case EDbColLongText8:
+		case EDbColLongText16:
+			if (iter->iMaxLength==KDbUndefinedLength)
+				break;
+			size=((TDbBlob*)data)->Size();
+			if (size==KDbUndefinedLength)
+				break;
+			if (iter->iType==EDbColText16)
+				size>>=1;
+			if (size>iter->iMaxLength)
+				__LEAVE(KErrOverflow);
+			break;
+		default:
+			break;
+			}
+		}
+	for (;iter<end;++iter)
+		{	// check for Null
+		if (iter->iAttributes&TDbCol::ENotNull)
+			{
+			__LEAVE(KErrNotFound);
+			return;
+			}
+		}
+	}
+
+void CDbTable::ReadRowL(RDbRow& aRow,TDbRecordId aRecordId)
+//
+// Read a record from the table
+//
+	{
+	CopyToRowL(aRow,RecordsL().ReadL(aRecordId));
+	}
+
+void CDbTable::PrepareAppendL(const RDbTableRow& aRow)
+//
+// Validate a new record for appending
+//
+	{
+	EnsureIndexesL();
+	ValidateL(aRow);
+	CDbRecordIndex** end=iIndexesEnd;
+	for (CDbRecordIndex** pix=iIndexes;pix<end;++pix)
+		{
+		CDbRecordIndex& ix=**pix;
+		if (ix.IsBroken())
+			continue;
+		if (ix.FindL(KDbNullRecordId,aRow)==CDbRecordIndex::EKeyMatch)
+			__LEAVE(KErrAlreadyExists);	// duplicate found
+		}
+	}
+
+TDbRecordId CDbTable::AppendRowL(const RDbTableRow& aRow)
+//
+// Validate and add a new record to the table and any open indexes
+//
+	{
+	CDbRecordSpace& records=RecordsL();
+	CopyFromRow(records.NewL(RecordLength(aRow)),aRow);
+	TDbRecordId id=records.AppendL();
+	CDbRecordIndex** end=iIndexesEnd;
+	for (CDbRecordIndex** pix=iIndexes;pix<end;++pix)
+		{
+		CDbRecordIndex& ix=**pix;
+		if (ix.IsBroken())
+			continue;
+		__DEBUG(TInt dbgchk=) ix.InsertL(id,aRow);
+		__ASSERT(dbgchk);
+		}
+	++iGeneration;
+	return id;
+	}
+
+void CDbTable::PrepareReplaceL(const RDbTableRow& aRow,TDbRecordId aRecordId)
+//
+// Validate a record for replacement
+//
+	{
+	EnsureIndexesL();
+	ValidateL(aRow);
+	TUint32 update=0;
+	CDbRecordIndex** end=iIndexes;
+	for (CDbRecordIndex** pix=iIndexesEnd;--pix>=end;)
+		{
+		update<<=1;
+		CDbRecordIndex& ix=**pix;
+		if (ix.IsBroken())
+			continue;
+		switch (ix.FindL(aRecordId,aRow))
+			{
+		case CDbRecordIndex::ENoMatch:		// key has changed in index
+			update|=1;
+			break;
+		case CDbRecordIndex::EKeyMatch:	// duplicate found
+			__LEAVE(KErrAlreadyExists);
+		case CDbRecordIndex::EEntryMatch:	// no change in index
+			break;
+			}
+		}
+	iUpdateMap=update;
+	}
+
+void CDbTable::DoReplaceRowL(const RDbRow& aRow,TDbRecordId aRecordId)
+	{
+	CopyFromRow(RecordsL().ReplaceL(aRecordId,RecordLength(aRow)),aRow);
+	++iGeneration;
+	}
+
+void CDbTable::ReplaceRowL(RDbTableRow& aRow,TDbRecordId aRecordId)
+//
+// Replace a record in the table
+//
+	{
+	if (Def().Columns().HasLongColumns())
+		CheckInliningL(aRow);
+	TUint32 update=iUpdateMap;
+	if (update==0)
+		{
+		DoReplaceRowL(aRow,aRecordId);
+		return;
+		}
+	RDbTableRow oldRow;		// temporary row buffer for old row values
+	oldRow.Open(this);
+	oldRow.PushL();			// cleanup buffer if there is trouble
+	ReadRowL(oldRow,aRecordId);
+	DoReplaceRowL(aRow,aRecordId);
+	for (CDbRecordIndex** pix=iIndexes;update;++pix,update>>=1)
+		{
+		if (update&1)
+			{
+			CDbRecordIndex& index=**pix;
+			index.DeleteL(aRecordId,oldRow);
+			__DEBUG(TInt dbgchk=) index.InsertL(aRecordId,aRow);
+			__ASSERT(dbgchk);
+			}
+		}
+	CleanupStack::PopAndDestroy();	// temp row buffer
+	}
+
+LOCAL_C void CheckInlineL(CDbBlobSpace& aBlobStore,TDbBlob& aBlob,TDbColType aType,CDbBlobCleanup*)
+	{
+	if (!aBlob.IsInline())
+		return;
+	if (aBlob.Size()>aBlobStore.InlineLimit())
+		aBlob.SetId(aBlobStore.CreateL(aType,aBlob.Data(),aBlob.Size()));
+	}
+
+void CDbTable::CheckInliningL(RDbRow& aRow)
+//
+// Ensure that all Blobs are within the current inline limit
+//
+	{
+	ApplyToBlobsL(aRow,CheckInlineL);
+	}
+
+LOCAL_C void DiscardBlobL(CDbBlobSpace& aBlobStore,TDbBlob& aBlob,TDbColType,CDbBlobCleanup*)
+	{
+	if (!aBlob.IsInline())
+		aBlobStore.DeleteL(aBlob.Id());
+	}
+
+EXPORT_C void CDbTable::DiscardBlobsL(RDbRow& aRow)
+//
+// Default implemtation xlates the record and then walks the row buffer
+//
+	{
+	ApplyToBlobsL(aRow,DiscardBlobL);
+	}
+
+void CDbTable::DeleteRowL(RDbTableRow& aRow,TDbRecordId aRecordId)
+//
+// Delete the record from the file and unlock it.
+//
+	{
+	EnsureIndexesL();
+
+	if (Def().Columns().HasLongColumns())
+		{
+		// Read data from the stream but do not delete the stream yet.
+		aRow.ReadL(aRecordId);
+		}
+
+	CDbRecordIndex** end=iIndexes;
+	CDbRecordIndex** pix=iIndexesEnd;
+	if (pix!=end)
+		aRow.ReadL(aRecordId);
+	RecordsL().EraseL(aRecordId);
+	while (--pix>=end)
+		{
+		CDbRecordIndex& ix=**pix;
+		if (!ix.IsBroken())
+			ix.DeleteL(aRecordId,aRow);
+		}
+
+	if (Def().Columns().HasLongColumns())
+		{
+		// Now delete the stream.
+		DiscardBlobsL(aRow);
+		}
+
+	++iGeneration;
+	}
+
+EXPORT_C CDbRecordSpace& CDbTable::RecordsL()
+	{
+	__ASSERT(IsActive() && InUse());
+	CDbRecordSpace* rec=iRecords;
+	if (rec==NULL)
+		iRecords=rec=RecordSpaceL();
+	if (rec->OpenL())
+		__LEAVE(KErrCorrupt);
+	return *rec;
+	}
+
+EXPORT_C CDbBlobSpace* CDbTable::BlobsL()
+	{
+	__ASSERT(IsActive() && InUse());
+	CDbBlobSpace* blob=iBlobs;
+	if (blob==NULL)
+		iBlobs=blob=BlobSpaceL();
+	if (blob->OpenL())
+		__LEAVE(KErrCorrupt);
+	return blob;
+	}
+
+EXPORT_C CDbRecordIndex& CDbTable::IndexL(const CDbTableIndexDef& aIndex)
+//
+// Load the index associated with the index definition and ensure it is operational
+//
+	{
+	__ASSERT(IsActive() && InUse());
+	// find the matching slot in the indexes array
+	CDbRecordIndex** slot=&iIndexes[0];
+	for (TSglQueIterC<CDbTableIndexDef> iter(Def().Indexes().AsQue());iter++!=&aIndex;)
+		++slot;
+	__ASSERT(iIndexesEnd==NULL||(slot>=iIndexes&&slot<iIndexesEnd));
+	// load (if required) and open the index
+	CDbRecordIndex* index=*slot;
+	if (index==0)
+		*slot=index=RecordIndexL(aIndex);
+	if (index->OpenL())
+		__LEAVE(KErrCorrupt);
+	return *index;
+	}
+
+void CDbTable::EnsureIndexesL()
+//
+// Ensure that all indexes are open
+//
+	{
+	__ASSERT(IsActive() && InUse());
+	if (iIndexesEnd==NULL)
+		{
+		CDbRecordIndex** pp=iIndexes;
+		TSglQueIterC<CDbTableIndexDef> iter(Def().Indexes().AsQue());
+		for (const CDbTableIndexDef* xDef;(xDef=iter++)!=NULL;++pp)
+			{
+			CDbRecordIndex* ix=*pp;
+			if (ix==NULL)
+				*pp=ix=RecordIndexL(*xDef);
+			ix->OpenL();	// ignore broken-ness
+			}
+		iIndexesEnd=pp;
+		}
+	}
+
+CDbRecordIter* CDbTable::IteratorL()
+	{
+	return RecordsL().IteratorL();
+	}
+
+CDbRecordIter* CDbTable::IteratorL(const CDbTableIndexDef& aIndex,TUint aInclusion,const TDbLookupKey* aLowerBound,const TDbLookupKey* aUpperBound)
+//
+// create an interator for the index parameter
+//
+	{
+	return IndexL(aIndex).IteratorL(aInclusion,aLowerBound,aUpperBound);
+	}
+
+EXPORT_C TInt CDbTable::IndexSpanL(const CDbTableIndexDef&,TUint,const TDbLookupKey*,const TDbLookupKey*)
+//
+// Default implementation: no statistics are available
+//
+	{
+	return EUnavailableSpan;
+	}
+
+CDbRecordIter* CDbTable::IteratorL(const TDesC& aIndex)
+//
+// create an interator for the index named
+//
+	{
+	return IteratorL(Def().Indexes().FindL(aIndex));
+	}
+
+void CDbTable::ApplyToComponentsL(void (*anOperationL)(CDbRecordBase*))
+//
+// Invoke anOperation on all components of the table
+//
+	{
+	if (iRecords)
+		anOperationL(iRecords);
+	if (iBlobs)
+		anOperationL(iBlobs);
+	CDbRecordIndex** const ixs=iIndexes;
+	CDbRecordIndex** pix=iIndexesEnd;
+	if (pix==NULL)
+		pix=&iIndexes[KDbTableMaxIndexes];
+	while (--pix>=ixs)
+		if (*pix)
+			anOperationL(*pix);
+	}
+
+// Class CDbtable::TValid
+
+// this class is used by the cursor to check that it is still operational
+
+CDbTable::TValid::TValid(CDbTable& aTable)
+	:iTable(aTable)
+	{
+	__ASSERT(aTable.IsActive());
+	iRollback.Construct(aTable.Database().Transaction().RollbackGeneration());
+	}
+
+TBool CDbTable::TValid::Reset()
+	{
+	TBool b=Table().IsActive();
+	if (b)
+		iRollback.Mark();
+	return b;
+	}
+
+void CDbTable::TValid::CheckL() const
+	{
+	CDbTableDatabase* d=Table().iDatabase;
+	if (!d)
+		__LEAVE(KErrDisconnected);
+	else
+		d->Transaction().ReadyL();
+	if (iRollback.Changed())
+		__LEAVE(KErrNotReady);
+	}