--- /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);
+ }