diff -r 000000000000 -r 8e480a14352b messagingfw/msgsrvnstore/server/src/msvmessagedbadapter.cpp --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/messagingfw/msgsrvnstore/server/src/msvmessagedbadapter.cpp Mon Jan 18 20:36:02 2010 +0200 @@ -0,0 +1,1457 @@ +// Copyright (c) 2001-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: Neeraj Nayan +// +// Description: The class provides interface to read/write header and +// body entry from the database. +// + + +/** + * HEADER FILES + */ + +#include "msvpreferreddrivelist.h" // Preferred drive list. +#include "msvmessagedbadapter.h" // Header file for this class. +#include "msvindexadapter.h" // For GetDriveId() +#include "msvdbadapter.h" // For BindL() and handle to CMsvDBAdapter while constructing + + +/** + * LITERAL DEFINITION + */ +const TInt KMaxQueryLength = 1000; + +// Macros to wrap DB error. KErrGeneral is thrown for +// all DB error. Make sure you declare 'err' before +// using these macros. +#define MSG_WRAP_DB_LEAVE(x) TRAP(err, x); \ + if(err<=KSqlErrGeneral) User::Leave(KErrGeneral); \ + User::LeaveIfError(err); + +#define MSG_WRAP_DB_ERROR(errTxt) err = errTxt; \ + if(err<=KSqlErrGeneral) User::Leave(KErrGeneral); \ + else User::LeaveIfError(err); + + + +/** + * MEMBER FUNCTIONS OF CMsvMessageDBAdapter + */ + + + +/** + * CMsvMessageDBAdapter() + * The default Constructor. + */ +CMsvMessageDBAdapter::CMsvMessageDBAdapter(CMsvDBAdapter* aDBAdapter): + iDBAdapter(aDBAdapter), iDatabaseRef(aDBAdapter->iDatabase) + { + } + + + + +/** + * ~CMsvMessageDBAdapter() + * The default Destructor. + */ +CMsvMessageDBAdapter::~CMsvMessageDBAdapter() + { + iHeaderDataList.ResetAndDestroy(); + } + + + + +/** + * NewL() + * + * @param CMsvDBAdapter* : A reference to database adapter class. + * @return CMsvMessageDBAdapter* : A reference to CMsvMessageDBAdapter + * class. + * + * Allocates and constructs a new CMsvMessageDBAdapter object. + */ +CMsvMessageDBAdapter* CMsvMessageDBAdapter::NewL(CMsvDBAdapter* aDBAdapter,TBool aDbInCurrentDrive) + { + //Create the DBAdapter object. + CMsvMessageDBAdapter* self = new(ELeave) CMsvMessageDBAdapter(aDBAdapter); + CleanupStack::PushL(self); + + self->ConstructL(aDbInCurrentDrive); + + CleanupStack::Pop(); + return self; + } + + +/* + * ConstructL() + * + * The function populates metadata structure. A metadata structure + * contains information about metadata table which is later used to + * create and update header entries. The structure is updated whenever + * a new header table is created in the database. + */ +void CMsvMessageDBAdapter::ConstructL(TBool aDbInCurrentDrive) + { + // check if the operation are required for only single drive + // This is required for message store converter as it works only on 1 drive. + + if(aDbInCurrentDrive) + { + iNonAttachedDrive = ETrue; + UpdateMetadataStructureL(KCurrentDriveId); + } + else + { + iNonAttachedDrive = EFalse; + for(TInt index=1; indexiDatabasePresent))[index]) + { + UpdateMetadataStructureL(index); + } + } + } + } + + + + + +void CMsvMessageDBAdapter::UpdateMetadataStructureL(TUint aDriveId) + { + TInt err; + _LIT(KCheckHeaderTableQuery0, "SELECT tbl_name FROM DB"); + _LIT(KCheckHeaderTableQuery1, ".sqlite_master WHERE type = 'table' AND tbl_name LIKE 'Header_%';"); + + // 1. Get all the header table names from the SQLITE_MASTER table. + RBuf headerTableQuery; + CleanupClosePushL(headerTableQuery); + headerTableQuery.CreateL(KMaxQueryLength); + + // Create the query string. + if(!iNonAttachedDrive) + { + headerTableQuery.Append(KCheckHeaderTableQuery0); + headerTableQuery.AppendNum(aDriveId); + headerTableQuery.Append(KCheckHeaderTableQuery1); + } + else + { + _LIT(KCheckHeaderTableQuery2, "SELECT tbl_name FROM sqlite_master WHERE type = 'table' AND tbl_name LIKE 'Header_%';"); + headerTableQuery.Append(KCheckHeaderTableQuery2); + } + + //Prepare a statement for the query. + RSqlStatement sqlStmt; + RPointerArray headerTableNames; + CleanupClosePushL(sqlStmt); + CleanupClosePushL(headerTableNames); + + MSG_WRAP_DB_ERROR(sqlStmt.Prepare(iDatabaseRef, headerTableQuery)); + // Prepare a list of header table names. + HBufC* tableName = NULL; + while((err = sqlStmt.Next()) == KSqlAtRow) + { + MSG_WRAP_DB_LEAVE(tableName = ColumnTextL(sqlStmt, 0)); + CleanupStack::PushL(tableName); + + headerTableNames.AppendL(tableName); + CleanupStack::Pop(); + } + MSG_WRAP_DB_ERROR(err); + + // 2. For each tablename read the table structure from the DB. + _LIT(KGetHeaderSchema1, "SELECT * FROM DB"); + _LIT(KGetHeaderSchema2, "."); + _LIT(KGetHeaderSchema3, ";"); + _LIT(KGetHeaderSchema4, "SELECT * FROM "); + + while(headerTableNames.Count()) + { + HBufC* tableName = headerTableNames[0]; + + TLex mtmId(tableName->Mid(tableName->Locate('_')+1)); + TUid uid; + mtmId.Val(uid.iUid); + + // Don't insert meta-data if we already have it for this mtmId. + if(GetMetadataEntryIndex(uid) != KErrNotFound) + { + delete tableName; + headerTableNames.Remove(0); + continue; + } + + headerTableQuery.Zero(); + sqlStmt.Close(); + + if(iNonAttachedDrive) // this is for msg converter. it refers to a single drive + { + headerTableQuery.Append(KGetHeaderSchema4); + } + else + { + // This creates following string... + // "SELECT * FROM DB.HEADER_;" + + headerTableQuery.Append(KGetHeaderSchema1); + headerTableQuery.AppendNum(aDriveId); + headerTableQuery.Append(KGetHeaderSchema2); + } + + headerTableQuery.Append(tableName->Des()); + headerTableQuery.Append(KGetHeaderSchema3); + + MSG_WRAP_DB_ERROR(sqlStmt.Prepare(iDatabaseRef, headerTableQuery)); + + CHeaderMetaData* headerMetaData = new(ELeave) CHeaderMetaData(); + CleanupStack::PushL(headerMetaData); + + TInt metadataIndex = 0; + headerMetaData->iMtmId.iUid = uid.iUid; + + TInt colCnt = sqlStmt.ColumnCount(); + // Record the header structure, ignore first two fields (Id, UID). + TRAPD(err, + for(TInt index=2; indexiType)); + TPtrC fieldName; + User::LeaveIfError(sqlStmt.ColumnName(index, fieldName)); + fieldObj->iName = fieldName.AllocL(); + headerMetaData->iFieldList.AppendL(fieldObj); + CheckStdColumnsAdded(fieldObj->iName, headerMetaData->iStdColumnStatus); + + CleanupStack::Pop(fieldObj); + } + ); + if(err) + { + MSG_WRAP_DB_ERROR(err); + } + iHeaderDataList.InsertL(headerMetaData, metadataIndex); + CleanupStack::Pop(); // headerMetaData + + delete tableName; + headerTableNames.Remove(0); + } // while(headerTableNames.Count()) + + CleanupStack::PopAndDestroy(3); // headerTableQuery, sqlStmt, headerTableNames + } + + + + +/* + * IsHeaderTableExistsL() + * + * @param aMtmId: MTM ID of the header table. + * @return TBool: ETrue/EFalse + * + * The function returns ETrue if the header table for + * passed MTM ID already exists in the database. The + * function works for only message server current drive. + */ +TBool CMsvMessageDBAdapter::IsHeaderTableExistsL(const TUid& aMtmId) + { + _LIT16(KCheckHeaderTableQuery1, "SELECT COUNT(*) FROM DB"); + _LIT16(KCheckHeaderTableQuery2, ".SQLITE_MASTER WHERE NAME LIKE 'Header_"); + _LIT16(KCheckHeaderTableQuery3, "';"); + + RBuf headerTableQuery; + CleanupClosePushL(headerTableQuery); + headerTableQuery.CreateL(KMaxQueryLength); + + // Create the query string. + // Ex: SELECT COUNT(*) FROM DB.SQLITE_MASTER WHERE + // NAME LIKE 'Header_'; + headerTableQuery.Append(KCheckHeaderTableQuery1); + headerTableQuery.AppendNum(KCurrentDriveId); + headerTableQuery.Append(KCheckHeaderTableQuery2); + headerTableQuery.AppendNum(aMtmId.iUid); + headerTableQuery.Append(KCheckHeaderTableQuery3); + + TInt count = 0; + TSqlScalarFullSelectQuery query(iDatabaseRef); + TRAPD(err, count = query.SelectIntL(headerTableQuery)); + CleanupStack::PopAndDestroy(); // headerTableQuery + + // If the table exists, count will have value 1. + if(err == KErrNone && count == 1) + { + return ETrue; + } + else + { + return EFalse; + } + } + + + + + + +/* + * CreateHeaderTableL() + * + * @param aMtmId: MTM ID of the table. + * @param aFieldDetails: Table structure details. + * @return aErrorMessage: Error message text, in case + * header table creation fails. + * + * The function creates separate header table for each MTM type. + * If the table already exist, it simply exits without returning an error. + */ +void CMsvMessageDBAdapter::CreateHeaderTableL(const TUid& aMtmId, const RPointerArray& aFieldDetails, TPtrC& aErrorMessage) + { + RBuf headerTableQuery; + CleanupClosePushL(headerTableQuery); + headerTableQuery.CreateL(KMaxQueryLength); + + CHeaderMetaData* headerMetaData = new(ELeave) CHeaderMetaData(); + CleanupStack::PushL(headerMetaData); + + TInt metadataIndex = GetMetadataEntryIndex(aMtmId); + // Insert a new metadata entry if it doesn't already exist. + if(metadataIndex == KErrNotFound) + { + iHeaderDataList.InsertL(headerMetaData, 0); + } + headerMetaData->iMtmId = aMtmId; + + if(iNonAttachedDrive) + { + _LIT16(KCreateHeaderTableQuery1, "CREATE TABLE IF NOT EXISTS "); + _LIT16(KCreateHeaderTableQuery2, "Header_"); + + // This creates following string... + // "CREATE TABLE IF NOT EXISTS DB.Header_ ( id INT, uid INT," + headerTableQuery.Append(KCreateHeaderTableQuery1); + headerTableQuery.Append(KCreateHeaderTableQuery2); + } + else + { + _LIT16(KCreateHeaderTableQuery1, "CREATE TABLE IF NOT EXISTS DB"); + _LIT16(KCreateHeaderTableQuery2, ".Header_"); + // "CREATE TABLE IF NOT EXISTS DB.Header_ ( id INT, uid INT," + headerTableQuery.Append(KCreateHeaderTableQuery1); + headerTableQuery.AppendNum(KCurrentDriveId); + headerTableQuery.Append(KCreateHeaderTableQuery2); + } + _LIT16(KCreateHeaderTableQuery3, " (id INT, uid INT, "); + _LIT16(KCreateHeaderTableQuery4, "Details TEXT, PRIMARY KEY (id, uid));"); + aErrorMessage.Set(_L("")); + headerTableQuery.AppendNum(aMtmId.iUid); + headerTableQuery.Append(KCreateHeaderTableQuery3); + + // Add the column details to the query. + TRAPD(err, DoCreateHeaderTableCreationQueryL(aFieldDetails, headerTableQuery, headerMetaData)); + if(err) + { + CleanupStack::Pop(headerMetaData); + delete headerMetaData; + iHeaderDataList.Remove(0); + User::Leave(err); + } + headerTableQuery.Append(KCreateHeaderTableQuery4); + + // Execute the query. + // Ignore error if table already exist. + err = iDatabaseRef.Exec(headerTableQuery); + if(err < 0) + { + aErrorMessage.Set(iDatabaseRef.LastErrorMessage()); + + CleanupStack::Pop(headerMetaData); + delete headerMetaData; + iHeaderDataList.Remove(0); + MSG_WRAP_DB_ERROR(err); + } + + CleanupStack::Pop(headerMetaData); // headerMetaData + if(metadataIndex != KErrNotFound) + delete headerMetaData; + CleanupStack::PopAndDestroy(); // headerTableQuery + } + + + + + + + +/* + * DoCreateHeaderTableCreationQueryL() + * + * @param aFieldDetails: Table structure details. + * @return aHeaderTableQuery: Header-table creation query string. + * @return aHeaderMetaData: Header metadata structure. + * + * The function appends the column details to the header + * creation query string. It also updates the metadata structure + * for the header entry. + */ +void CMsvMessageDBAdapter::DoCreateHeaderTableCreationQueryL(const RPointerArray& aFieldDetails, RBuf& aHeaderTableQuery, CHeaderMetaData*& aHeaderMetaData) + { + _LIT16(KIntField, " INT, "); + _LIT16(KTextField, " TEXT, "); + _LIT16(KDateField, " INT64, "); + _LIT16(KQuote, "\""); + + CFieldClass *fieldObj = NULL; + // Browse through each column names in the field-list. + TInt fieldDetailsCount = aFieldDetails.Count(); + for(TInt fieldIndex=0; fieldIndexiFieldName) + { + User::Leave(KErrArgument); + } + + // Create a metadata entry. + fieldObj = new(ELeave) CFieldClass; + CleanupStack::PushL(fieldObj); + fieldObj->iName = aFieldDetails[fieldIndex]->iFieldName->Des().AllocL(); + + // Append column name under quotes. + aHeaderTableQuery.Append(KQuote); + aHeaderTableQuery.Append(aFieldDetails[fieldIndex]->iFieldName->Des()); + aHeaderTableQuery.Append(KQuote); + + // Append appropriate column type. + switch(aFieldDetails[fieldIndex]->iFieldType) + { + case EIntegerField: + aHeaderTableQuery.Append(KIntField); + fieldObj->iType = ESqlInt; + break; + case ETextField: + aHeaderTableQuery.Append(KTextField); + fieldObj->iType = ESqlText; + break; + case EDateField: + aHeaderTableQuery.Append(KDateField); + fieldObj->iType = ESqlInt64; + break; + default: + User::Leave(KErrArgument); + break; + } + + aHeaderMetaData->iFieldList.AppendL(fieldObj); + CleanupStack::Pop(); // fieldObj + + // Update the status of standard header columns + // in the metadata structure. + CheckStdColumnsAdded(aFieldDetails[fieldIndex]->iFieldName, aHeaderMetaData->iStdColumnStatus); + } // for(TInt fieldIndex=0; fieldIndexiType = ESqlText; + fieldObj->iName = KDetails().AllocL(); + aHeaderMetaData->iFieldList.AppendL(fieldObj); // Added for Details column. + + CleanupStack::Pop(); // fieldObj + } + + + + + + +/* + * CreateHeaderEntryL() + * + * @param aMtmId: MtmId to identify the header table. + * @param aEntryId: Metadata Entry Id. + * @param aFieldPairList: Header details. + * + * The function creates a new header entry in the header + * table. The header details passed to the function in + * aFieldPairList should either have values for all the + * columns in the header table or should have value for + * only one column (The default column "Details"). + * + * The function can accept multiple header portion (each + * associated with a unique UID. These header portion will + * be stored in a separate row in the header table. + */ + +void CMsvMessageDBAdapter::CreateHeaderEntryL(const TUid& aMtmId, TMsvId aEntryId, const RPointerArray& aFieldPairList) + { + TInt err = KErrNone; + + // Get the metadata entry index for the MTM. + TInt metadataEntryIndex = GetMetadataEntryIndex(aMtmId); + User::LeaveIfError(metadataEntryIndex); + TBool isLocalTransaction = EFalse; + + // Open the DB transaction, if it is not already open. + if(!iDBAdapter->isTransactionOpen && !iNonAttachedDrive) + { + MSG_WRAP_DB_LEAVE(iDBAdapter->BeginTransactionL()); + isLocalTransaction = ETrue; + } + + // For each entry in header details, create a separate + // row in the header table. + TInt fieldPairListCount = aFieldPairList.Count(); + TRAP(err, + for(TInt index=0; indexRollbackTransactionL(); + } + MSG_WRAP_DB_ERROR(err); + } + else if (!iNonAttachedDrive) + { + if(isLocalTransaction) + { + MSG_WRAP_DB_LEAVE(iDBAdapter->CommitTransactionL()); + } + } + } + + + + + + + +/* + * DoInsertHeaderEntryL() + * + * @param aMtmId: MtmId to identify the header table. + * @param aEntryId: Metadata Entry Id. + * @param aHeaderFields: Header entry details. + * @param aMetadataEntryIndex: Header table metadata. + * + * The function creates a new header entry in the header + * table. The header details passed to the function in + * aHeaderFields should either have values for all the + * columns in the header table or should have value for + * only one column (The default column "Details"). + */ +void CMsvMessageDBAdapter::DoInsertHeaderEntryL(const TUid& aMtmId, TMsvId aEntryId, CHeaderFields* aHeaderFields, TInt aMetadataEntryIndex) + { + TInt err = KErrNone; + _LIT16(KGetHeaderSchema5, "INSERT INTO HEADER_"); + _LIT16(KGetHeaderSchema1, "INSERT INTO DB"); + _LIT16(KGetHeaderSchema2, ".HEADER_"); + _LIT16(KGetHeaderSchema3, " VALUES (:id, :uid"); + _LIT16(KGetHeaderSchema4, " (id, uid, Details) VALUES (:id, :uid, :details);"); + _LIT16(KComma, ", :"); + _LIT16(KDelimiter, ");"); + + TInt KDefaultColumnSize = 1; + RBuf headerTableQuery; + CleanupClosePushL(headerTableQuery); + + // Create header entry creation query + // Ex: INSERT INTO DB.HEADER_ + headerTableQuery.CreateL(KMaxQueryLength); + if(!iNonAttachedDrive) + { + headerTableQuery.Append(KGetHeaderSchema1); + headerTableQuery.AppendNum(GetDriveId(aEntryId)); + headerTableQuery.Append(KGetHeaderSchema2); + } + else + { + headerTableQuery.Append(KGetHeaderSchema5); + } + headerTableQuery.AppendNum(aMtmId.iUid); + + // Append column names to the query string. + // Column names are stored in the header metadata structure. + CHeaderMetaData* headerMetadata = iHeaderDataList[aMetadataEntryIndex]; + TInt fieldPairListCount = aHeaderFields->iFieldPairList.Count(); + + // If header entry has more than one field... + if(KDefaultColumnSize < fieldPairListCount) + { + headerTableQuery.Append(KGetHeaderSchema3); + + // Check if number of values passed for the header entry + // matches with the number of columns in the header table. + RPointerArray& fieldList = headerMetadata->iFieldList; + if(fieldList.Count() != fieldPairListCount) + { + User::Leave(KErrArgument); + } + // Append column names to the query string. + TInt fieldListCount = fieldList.Count(); + for(TInt fieldCount=0; fieldCountiName->Des()); + } + headerTableQuery.Append(KDelimiter); + } // if(KDefaultColumnSize < aHeaderFields->iFieldPairList.Count()) + else + { + // If the header entry has just one field + // It must be the value for default column (Details). + headerTableQuery.Append(KGetHeaderSchema4); + } + + // Prepare the SQL statement for execution. + RSqlStatement sqlStmt; + CleanupClosePushL(sqlStmt); + MSG_WRAP_DB_ERROR(sqlStmt.Prepare(iDatabaseRef, headerTableQuery)); + + // Bind values against column names. + TInt fieldIndex = 0; + MSG_WRAP_DB_LEAVE(BindIntL(sqlStmt, fieldIndex++, UnmaskTMsvId(aEntryId))); + MSG_WRAP_DB_LEAVE(BindIntL(sqlStmt, fieldIndex++, aHeaderFields->iUid.iUid)); + + TInt index = 0; + for(; index < fieldPairListCount-1; index++, fieldIndex++) + { + switch(headerMetadata->iFieldList[index]->iType) + { + case ESqlInt: + BindIntL(sqlStmt, fieldIndex, aHeaderFields->iFieldPairList[index]->iFieldNumValue); + break; + case ESqlInt64: + BindInt64L(sqlStmt, fieldIndex, aHeaderFields->iFieldPairList[index]->iFieldNumValue); + break; + case ESqlText: + if(!aHeaderFields->iFieldPairList[index]->iFieldTextValue) + { + User::Leave(KErrArgument); + } + BindTextL(sqlStmt, fieldIndex, aHeaderFields->iFieldPairList[index]->iFieldTextValue->Des()); + break; + } + } + if(aHeaderFields->iFieldPairList[index]->iFieldTextValue) + { + BindTextL(sqlStmt, fieldIndex, aHeaderFields->iFieldPairList[index]->iFieldTextValue->Des()); + } + else + { + User::Leave(KErrArgument); + } + + // Execute the query. Return KErrGeneral, + // if entry is not inserted. + err = sqlStmt.Exec(); + if(!err) + { + User::Leave(KErrGeneral); + } + if(err == KSqlErrConstraint) + { + User::Leave(KErrAlreadyExists); + } + MSG_WRAP_DB_ERROR(err); + CleanupStack::PopAndDestroy(2); //sqlStmt, headerTableQuery + } + + + + + + + + +/* + * LoadHeaderEntryL() + * + * @param aMtmId: MtmId to identify the header table. + * @param aEntryId: Metadata Entry Id. + * @return aFieldPairList: Header entry details. + * + * The function loads the header entry from the header + * table. If the header entry has multiple row, all of + * them will be loaded and stored as a separate element + * of CHeaderFields. + */ +void CMsvMessageDBAdapter::LoadHeaderEntryL(const TUid& aMtmId, TMsvId aEntryId, RPointerArray& aFieldPairList) + { + TInt err = KErrNone; + _LIT16(KGetHeaderSchema1, "SELECT uid"); + _LIT16(KGetHeaderSchema2, " FROM DB"); + _LIT16(KGetHeaderSchema3, ".HEADER_"); + _LIT16(KGetHeaderSchema4, " WHERE id = :id;"); + _LIT16(KComma, ", \""); + _LIT16(KQuote, "\""); + + aFieldPairList.Reset(); + // Get the metadata entry index for the MTM. + TInt metadataEntryIndex = GetMetadataEntryIndex(aMtmId); + User::LeaveIfError(metadataEntryIndex); + RPointerArray& fieldList = iHeaderDataList[metadataEntryIndex]->iFieldList; + + // Create header query string + // Ex: SELECT uid, + RBuf headerTableQuery; + CleanupClosePushL(headerTableQuery); + headerTableQuery.CreateL(KMaxQueryLength); + headerTableQuery.Append(KGetHeaderSchema1); + + // Append header column name to the query. + TInt fieldListCount = fieldList.Count(); + for(TInt index=0; indexiName->Des()); + headerTableQuery.Append(KQuote); + } + + // Append "FROM DB.HEADER_ WHERE ID = :ID; + headerTableQuery.Append(KGetHeaderSchema2); + headerTableQuery.AppendNum(GetDriveId(aEntryId)); + headerTableQuery.Append(KGetHeaderSchema3); + headerTableQuery.AppendNum(aMtmId.iUid); + headerTableQuery.Append(KGetHeaderSchema4); + + //Prepare the SQL statement. + RSqlStatement sqlStmt; + CleanupClosePushL(sqlStmt); + + MSG_WRAP_DB_ERROR(sqlStmt.Prepare(iDatabaseRef, headerTableQuery)); + MSG_WRAP_DB_LEAVE(BindIntL(sqlStmt, 0, UnmaskTMsvId(aEntryId))); + + // Load the header entry into header structure. + TRAP(err, DoLoadHeaderEntryL(sqlStmt, fieldList, aFieldPairList)); + + // Handle errors if any. + if(err<0) + { + for(TInt index=0; index& aMetadataList, RPointerArray& aFieldPairList) + { + TInt err = KErrNone; + // Fetch each header-table row in the loop. + while(ETrue) + { + err = aSqlStmt.Next(); + if(KSqlAtRow != err) + { + if(KSqlAtEnd == err) + { + break; + } + else + { + MSG_WRAP_DB_ERROR(err); + } + } + + // Create header structure to store a single row. + CHeaderFields* headerRow = new(ELeave) CHeaderFields(); + CleanupStack::PushL(headerRow); + + TBool isPrimaryRow = EFalse; + TInt count = aMetadataList.Count(); + headerRow->iUid.iUid = ColumnInt(aSqlStmt, 0); + // Read individual fields and populate the header structure. + for(TInt index=0; indexiFieldName = aMetadataList[index]->iName->Des().AllocL(); + switch(aMetadataList[index]->iType) + { + case ESqlInt: + fieldObj->iFieldNumValue = ColumnInt(aSqlStmt, index+1); + if(!isPrimaryRow && fieldObj->iFieldNumValue != NULL && index != count-1) + { + isPrimaryRow = ETrue; + } + break; + case ESqlInt64: + fieldObj->iFieldNumValue = ColumnInt64(aSqlStmt, index+1); + if(!isPrimaryRow && fieldObj->iFieldNumValue != NULL && index != count-1) + { + isPrimaryRow = ETrue; + } + break; + case ESqlText: + TRAP(err, fieldObj->iFieldTextValue = ColumnTextL(aSqlStmt, index+1)); + if(err < 0) + { + User::Leave(KErrGeneral); + } + if(!isPrimaryRow && (index != count-1) && fieldObj->iFieldTextValue->Des().Compare(_L("")) ) + { + isPrimaryRow = ETrue; + } + break; + } // switch(fieldList[index]->iType) + + headerRow->iFieldPairList.AppendL(fieldObj); + CleanupStack::Pop(fieldObj); + } // for(TInt index=0; indexiFieldPairList[0]; + headerRow->iFieldPairList.Remove(0); + } + } + aFieldPairList.AppendL(headerRow); + CleanupStack::Pop(headerRow); + } + } + + + + + + + +/* + * UpdateHeaderEntryL() + * + * @param aMtmId: MtmId to identify the header table. + * @param aEntryId: Entry Id. + * @param aFieldPairList: Header entry details. + * + * The function updates the header entry in the header table with the + * passed header structure. A header entry is essentially a list of + * UID-String combination which is stored as a separate element in the + * passed header structure. Each such header portion is stored in separate + * rows in the header table. + + * If the number of rows in the passed header structure is more than what + * is present in the DB, the function inserts the new rows in the header + * table. And if the few rows are missing from the passed header structure + * the function deletes the extra row from the header table. + */ +void CMsvMessageDBAdapter::UpdateHeaderEntryL(const TUid& aMtmId, TMsvId aEntryId, const RPointerArray& aFieldPairList) + { + TInt err = KErrNone; + _LIT16(KGetHeaderSchema1, "SELECT uid FROM DB"); + _LIT16(KGetHeaderSchema2, ".HEADER_"); + _LIT16(KGetHeaderSchema3, " WHERE id = :id;"); + + // First find out the number of rows present in the DB + // against this entry, and store their UID in a array. + RBuf headerTableQuery; + CleanupClosePushL(headerTableQuery); + headerTableQuery.CreateL(KMaxQueryLength); + + // This creates following string... + // Ex: SELECT uid FROM DB.HEADER_ WHERE id = :id; + headerTableQuery.Append(KGetHeaderSchema1); + headerTableQuery.AppendNum(GetDriveId(aEntryId)); + headerTableQuery.Append(KGetHeaderSchema2); + headerTableQuery.AppendNum(aMtmId.iUid); + headerTableQuery.Append(KGetHeaderSchema3); + + //Prepare a statement for the query. + RSqlStatement sqlStmt; + CleanupClosePushL(sqlStmt); + + MSG_WRAP_DB_ERROR(sqlStmt.Prepare(iDatabaseRef, headerTableQuery)); + MSG_WRAP_DB_LEAVE(BindIntL(sqlStmt, 0, UnmaskTMsvId(aEntryId))); + + // Execute the query and form the UID list. + RArray uidList; + CleanupClosePushL(uidList); + do + { + err = sqlStmt.Next(); + if(err == KSqlAtRow) + { + uidList.AppendL(ColumnInt(sqlStmt, 0)); + } + } + while(err == KSqlAtRow); + + // If no entry is found in DB, return KErrNotFound. + MSG_WRAP_DB_ERROR(err); + if(!uidList.Count()) + { + User::Leave(KErrNotFound); + } + + // Open a DB transaction if it is not already open. + TBool isLocalTransaction = EFalse; + if(!iDBAdapter->isTransactionOpen) + { + MSG_WRAP_DB_LEAVE(iDBAdapter->BeginTransactionL()); + isLocalTransaction = ETrue; + } + + // Process all the header portion one by one. + TRAP(err, DoProcessHeaderEntryL(aMtmId, aEntryId, aFieldPairList, uidList)); + + // Close the transaction and handle error. + if(err) + { + if(isLocalTransaction) + { + iDBAdapter->RollbackTransactionL(); + } + MSG_WRAP_DB_ERROR(err); + } + else + { + if(isLocalTransaction) + { + MSG_WRAP_DB_LEAVE(iDBAdapter->CommitTransactionL()); + } + } + + CleanupStack::PopAndDestroy(3); //uidList, sqlStmt, headerTableQuery + } + + + + + + +/* + * DoProcessHeaderEntryL() + * + * @param aMtmId: MtmId to identify the header table. + * @param aEntryId: Entry Id. + * @param aFieldPairList: Header entry details. + * @param aUidList: UID list of the header entry present in DB. + * + * The function is called by UpdateHeaderEntryL() only, and it updates + * the header entry in the header table. For details, see the comment + * of UpdateHeaderEntryL(). + */ +void CMsvMessageDBAdapter::DoProcessHeaderEntryL(const TUid& aMtmId, TMsvId aEntryId, const RPointerArray& aFieldPairList, RArray& aUidList) + { + TInt metadataEntryIndex = GetMetadataEntryIndex(aMtmId); + User::LeaveIfError(metadataEntryIndex); + + // Processing each header row. + TInt fieldPairListCount = aFieldPairList.Count(); + for(TInt index=0; index < fieldPairListCount; index++) + { + // Search the entry UID in the UID list. + CHeaderFields* headerRow = aFieldPairList[index]; + TInt rowIndex = aUidList.Find(headerRow->iUid.iUid); + // If the entry is not present in DB, create it. + if(KErrNotFound == rowIndex) + { + // Insert the entry + DoInsertHeaderEntryL(aMtmId, aEntryId, headerRow, metadataEntryIndex); + } + else + { + // else, update the entry + DoUpdateHeaderEntryL(aMtmId, aEntryId, headerRow, metadataEntryIndex); + aUidList.Remove(rowIndex); + } + } + + // If there is extra entries in the DB, + // delete them. + if(aUidList.Count()) + { + _LIT16(KGetHeaderSchema1, "DELETE FROM DB"); + _LIT16(KGetHeaderSchema2, ".HEADER_"); + _LIT16(KGetHeaderSchema3, " WHERE id = :id and uid in ("); + _LIT16(KGetHeaderSchema4, ");"); + _LIT16(KComma, ", "); + + // Create the delete query string. + RBuf headerTableQuery; + CleanupClosePushL(headerTableQuery); + headerTableQuery.CreateL(KMaxQueryLength); + + // This creates following string... + headerTableQuery.Append(KGetHeaderSchema1); + headerTableQuery.AppendNum(GetDriveId(aEntryId)); + headerTableQuery.Append(KGetHeaderSchema2); + headerTableQuery.AppendNum(aMtmId.iUid); + headerTableQuery.Append(KGetHeaderSchema3); + headerTableQuery.AppendNum(aUidList[0]); + TInt uidListCount = aUidList.Count(); + for(TInt index=1; index < uidListCount; index++) + { + headerTableQuery.Append(KComma); + headerTableQuery.AppendNum(aUidList[index]); + } + headerTableQuery.Append(KGetHeaderSchema4); + + //Prepare a statement for the query. + RSqlStatement sqlStmt; + CleanupClosePushL(sqlStmt); + User::LeaveIfError(sqlStmt.Prepare(iDatabaseRef, headerTableQuery)); + BindIntL(sqlStmt, 0, UnmaskTMsvId(aEntryId)); + + // Execute the query and handle error. + TInt err = sqlStmt.Exec(); + if(!err) + { + User::Leave(KErrNotFound); + } + + User::LeaveIfError(err); + CleanupStack::PopAndDestroy(2); //sqlStmt, headerTableQuery + } + } + + + + + + +/* + * DoUpdateHeaderEntryL() + * + * @param aMtmId: MtmId to identify the header table. + * @param aEntryId: Entry Id. + * @param aHeaderFields: Header entry details. + * @param aMetadataEntryIndex: UID list of the header entry present in DB. + * + * The function is called by UpdateHeaderEntryL() only, and it updates + * the header entry in the header table. For details, see the comment + * of UpdateHeaderEntryL(). + */ +void CMsvMessageDBAdapter::DoUpdateHeaderEntryL(const TUid& aMtmId, TMsvId aEntryId, CHeaderFields* aHeaderFields, TInt aMetadataEntryIndex) + { + _LIT16(KGetHeaderSchema1, "UPDATE DB"); + _LIT16(KGetHeaderSchema2, ".HEADER_"); + _LIT16(KGetHeaderSchema3, " SET "); + _LIT16(KGetHeaderSchema4, " WHERE id = :id and uid = :uid;"); + _LIT16(KGetHeaderSchema5, " details = :details WHERE id = :id and uid = :uid;"); + _LIT16(KComma, ", "); + _LIT16(KColon, "\" = :"); + _LIT16(KQuote, "\""); + + RBuf headerTableQuery; + CleanupClosePushL(headerTableQuery); + + // Create the query string: + // Ex: UPDATE DB.HEADER_ SET + headerTableQuery.CreateL(KMaxQueryLength); + headerTableQuery.Append(KGetHeaderSchema1); + headerTableQuery.AppendNum(GetDriveId(aEntryId)); + headerTableQuery.Append(KGetHeaderSchema2); + headerTableQuery.AppendNum(aMtmId.iUid); + headerTableQuery.Append(KGetHeaderSchema3); + + TInt columnIndex = -1; + // Append column information to the query + CHeaderMetaData* headerMetadata = iHeaderDataList[aMetadataEntryIndex]; + TInt fieldPairListCount = aHeaderFields->iFieldPairList.Count(); + if(fieldPairListCount > 1) + { + // Check if all the field values are passed. + if(fieldPairListCount != headerMetadata->iFieldList.Count()) + { + User::Leave(KErrArgument); + } + for(TInt index=0; index < fieldPairListCount; index++) + { + if(index) + { + headerTableQuery.Append(KComma); + } + headerTableQuery.Append(KQuote); + headerTableQuery.Append(headerMetadata->iFieldList[index]->iName->Des()); + headerTableQuery.Append(KColon); + headerTableQuery.Append(headerMetadata->iFieldList[index]->iName->Des()); + columnIndex++; + } + headerTableQuery.Append(KGetHeaderSchema4); + } + else + { + headerTableQuery.Append(KGetHeaderSchema5); + columnIndex++; + } + + // Prepare the query and Bind the values. + RSqlStatement sqlStmt; + CleanupClosePushL(sqlStmt); + User::LeaveIfError(sqlStmt.Prepare(iDatabaseRef, headerTableQuery)); + + TInt fieldIndex = 0; + for(; fieldIndexiFieldList[fieldIndex]->iType) + { + case ESqlInt: + BindIntL(sqlStmt, fieldIndex, aHeaderFields->iFieldPairList[fieldIndex]->iFieldNumValue); + break; + case ESqlInt64: + BindInt64L(sqlStmt, fieldIndex, aHeaderFields->iFieldPairList[fieldIndex]->iFieldNumValue); + break; + case ESqlText: + if(!aHeaderFields->iFieldPairList[fieldIndex]->iFieldTextValue) + { + User::Leave(KErrArgument); + } + BindTextL(sqlStmt, fieldIndex, aHeaderFields->iFieldPairList[fieldIndex]->iFieldTextValue->Des()); + break; + } + } + + if(aHeaderFields->iFieldPairList[fieldIndex]->iFieldTextValue) + { + BindTextL(sqlStmt, columnIndex++, aHeaderFields->iFieldPairList[fieldIndex]->iFieldTextValue->Des()); + } + else + { + User::Leave(KErrArgument); + } + + BindIntL(sqlStmt, columnIndex++, UnmaskTMsvId(aEntryId)); + BindIntL(sqlStmt, columnIndex, aHeaderFields->iUid.iUid); + + // Execute the query and process the error. + TInt err = sqlStmt.Exec(); + if(!err) + { + User::Leave(KErrGeneral); + } + + User::LeaveIfError(err); + CleanupStack::PopAndDestroy(2); //sqlStmt, headerTableQuery + } + + + + + + + + +/* + * CheckStdColumnsAdded() + * + * @param aFieldName: Column name + * @return aColStatus: Column status flag. + * + * The function checks the existence of standard header fields + * as defined in CMsvHeaderStore::TCommonHeaderField in the + * header table and sets the appropriate flag n aColStatus. + */ +void CMsvMessageDBAdapter::CheckStdColumnsAdded(HBufC* aFieldName, TInt& aColStatus) + { + // If it is a standard FORM field... + if(!aFieldName->Des().Compare(_L("From"))) + { + aColStatus |= CMsvHeaderStore::EFrom; + } + else + { + if(!aFieldName->Des().Compare(_L("To"))) + { + aColStatus |= CMsvHeaderStore::ETo; + } + else + { + if(!aFieldName->Des().Compare(_L("CC"))) + { + aColStatus |= CMsvHeaderStore::ECC; + } + else + { + if(!aFieldName->Des().Compare(_L("BCC"))) + { + aColStatus |= CMsvHeaderStore::EBCC; + } + else + { + if(!aFieldName->Des().Compare(_L("Subject"))) + { + aColStatus |= CMsvHeaderStore::ESubject; + } + } // BCC + } // CC + } // To + } // From + } + + + + + + + + +/* + * DeleteHeaderEntryL() + * + * @param aMtmId: MtmId to identify the header table. + * @param aEntryId: Entry Id. + * + * The function deletes the header entry from the corresponding + * header table. If multiple rows are associated with the header + * entry, all such rows are deleted from the header table. + */ +void CMsvMessageDBAdapter::DeleteHeaderEntryL(const TUid& aMtmId, TMsvId aEntryId) + { + TInt err = KErrNone; + _LIT16(KGetHeaderSchema1, "DELETE FROM DB"); + _LIT16(KGetHeaderSchema2, ".HEADER_"); + _LIT16(KGetHeaderSchema3, " WHERE id = :id;"); + + // Leave with KErrNotFound, if the header table does not exist. + User::LeaveIfError(GetMetadataEntryIndex(aMtmId)); + + RBuf headerTableQuery; + CleanupClosePushL(headerTableQuery); + headerTableQuery.CreateL(KMaxQueryLength); + + // This creates following string... + // Ex: DELETE FROM DB.HEADER_ WHERE id = :id; + headerTableQuery.Append(KGetHeaderSchema1); + headerTableQuery.AppendNum(GetDriveId(aEntryId)); + headerTableQuery.Append(KGetHeaderSchema2); + headerTableQuery.AppendNum(aMtmId.iUid); + headerTableQuery.Append(KGetHeaderSchema3); + + //Prepare a statement for the query. + RSqlStatement sqlStmt; + CleanupClosePushL(sqlStmt); + + MSG_WRAP_DB_ERROR(sqlStmt.Prepare(iDatabaseRef, headerTableQuery)); + MSG_WRAP_DB_LEAVE(BindIntL(sqlStmt, 0, UnmaskTMsvId(aEntryId))); + + // Execute the query and handle the error. + err = sqlStmt.Exec(); + if(!err) + { + User::Leave(KErrNotFound); + } + + MSG_WRAP_DB_ERROR(err); + CleanupStack::PopAndDestroy(2); //sqlStmt, headerTableQuery + } + + + + +/* + * CopyHeaderEntryL() + * + * @param aMtmId The MTM UId to identify the header table. + * @param aSrcEntryId The source entry's TMsvId. + * @param aDestEntryId The destination source entry's TMsvId. + * @return None. + * @leave KErrNoMemory while creating the buffer for the query. + * + * + * The function copies header entry(s) of an entry from the corresponding + * header table. If multiple rows are associated with the entry's TMsvId, + * then copies of all such rows are made. + * This is + */ +void CMsvMessageDBAdapter::CopyHeaderEntryL(const TUid& aMtmId, const TMsvId& aSrcEntryId, const TMsvId& aDestEntryId) + { + _LIT(KComma, ", "); + _LIT(KDelimiter, ";"); + _LIT(KQuote, "\""); + _LIT(KCopyHeader1, "INSERT INTO DB"); // INSERT INTO DB1 + _LIT(KCopyHeader2, ".HEADER_"); // .HEADER_100 + _LIT(KCopyHeader3, " (id, uid"); // (id, uid, field3, ... + _LIT(KCopyHeader4, ") SELECT "); // ) SELECT aDestEntryId + _LIT(KCopyHeader5, ",uid"); // , uid, field3, ... + _LIT(KCopyHeader6, " FROM HEADER_"); // FROM HEADER_100 + _LIT(KCopyHeader7, " WHERE id = "); // WHERE id = aSrcEntryId; + + // Get metadata for the appropriate header + TInt driveId = GetDriveId(aSrcEntryId); + TInt metadataEntryIndex = GetMetadataEntryIndex(aMtmId); + User::LeaveIfError(metadataEntryIndex); + + /* + We're looking to construct a query that looks like: + INSERT INTO DB.HEADER_ (id, uid, field3...) + SELECT aDestEntryId, uid, field3... + WHERE id = aSrcEntryId; + */ + + // Start query construction. + RBuf headerTableQuery; + CleanupClosePushL(headerTableQuery); + headerTableQuery.CreateL(KMaxQueryLength); + headerTableQuery.Append(KCopyHeader1); + headerTableQuery.AppendNum(driveId); + headerTableQuery.Append(KCopyHeader2); + headerTableQuery.AppendNum(aMtmId.iUid); + headerTableQuery.Append(KCopyHeader3); + + + // Append column names to the query string. + // Column names are stored in the header metadata structure. + // We wrap the column names in quotes in case the column names are SQLite keywords. Eg: "From". + CHeaderMetaData* headerMetadata = iHeaderDataList[metadataEntryIndex]; + RPointerArray& fieldList = headerMetadata->iFieldList; + TInt fieldListCount = fieldList.Count(); + TInt index = 0; + for(; index < fieldListCount; ++index) + { + headerTableQuery.Append(KComma); + headerTableQuery.Append(KQuote); + headerTableQuery.Append(fieldList[index]->iName->Des()); + headerTableQuery.Append(KQuote); + } + + headerTableQuery.Append(KCopyHeader4); + headerTableQuery.AppendNum(UnmaskTMsvId(aDestEntryId)); + headerTableQuery.Append(KCopyHeader5); + + for(index = 0; index < fieldListCount; ++index) + { + headerTableQuery.Append(KComma); + headerTableQuery.Append(KQuote); + headerTableQuery.Append(fieldList[index]->iName->Des()); + headerTableQuery.Append(KQuote); + } + + headerTableQuery.Append(KCopyHeader6); + headerTableQuery.AppendNum(aMtmId.iUid); + headerTableQuery.Append(KCopyHeader7); + headerTableQuery.AppendNum(UnmaskTMsvId(aSrcEntryId)); + + headerTableQuery.Append(KDelimiter); + + TInt err = iDatabaseRef.Exec(headerTableQuery); + if(!err) + { // 0 returned by Exec is due to SELECT part of the query not returning any result to the INSERT part. + User::Leave(KErrNotFound); + } + + if(KSqlErrConstraint == err) + { + User::Leave(KErrAlreadyExists); + } + + MSG_WRAP_DB_ERROR(err); + CleanupStack::PopAndDestroy(); //headerTableQuery + } + + + +/* + * CheckHeaderTableAndStdColumnFields() + * + * @param aMtmId: MtmId for related Header table + * @param aHeaderFilelds: Header field specified in search query + * + * Check HeaderTable And StdColumn are exist. + */ +TBool CMsvMessageDBAdapter::CheckHeaderTableAndStdColumnFields(const TUid& aMtmId, TInt aHeaderFields) + { + TBool flag = EFalse; + + TInt index = GetMetadataEntryIndex(aMtmId); + if(index != KErrNotFound) + { + // needs to check all standard columns given in a query are avilble in header table + if( (iHeaderDataList[index]->iStdColumnStatus & aHeaderFields) == aHeaderFields) + { + flag = ETrue; + } + } + return flag; + } + + + + +TBool CMsvMessageDBAdapter::DoesAnyStoreExistsL(TMsvId aId, TUid aMtmId) + { + _LIT16(KCheckHeaderTableQuery1, "SELECT COUNT(*) FROM DB"); + _LIT16(KCheckHeaderTableQuery2, ".HEADER_"); + _LIT16(KCheckHeaderTableQuery3, " WHERE id = "); + _LIT16(KSemiColon, ";"); + + RBuf headerTableQuery; + CleanupClosePushL(headerTableQuery); + headerTableQuery.CreateL(KMaxQueryLength); + + headerTableQuery.Append(KCheckHeaderTableQuery1); + headerTableQuery.AppendNum(GetDriveId(aId)); + headerTableQuery.Append(KCheckHeaderTableQuery2); + headerTableQuery.AppendNum(aMtmId.iUid); + headerTableQuery.Append(KCheckHeaderTableQuery3); + headerTableQuery.AppendNum(UnmaskTMsvId(aId)); + headerTableQuery.Append(KSemiColon); + + TInt count = 0; + TSqlScalarFullSelectQuery query(iDatabaseRef); + TRAPD(err, count = query.SelectIntL(headerTableQuery)); + CleanupStack::PopAndDestroy(); // headerTableQuery + + if(err == KErrNone && count > 0) + { + return ETrue; + } + return EFalse; + }