phonebookengines/contactsmodel/cntplsql/src/cpplcommaddrtable.cpp
changeset 0 e686773b3f54
child 9 0d28c1c5b6dd
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/phonebookengines/contactsmodel/cntplsql/src/cpplcommaddrtable.cpp	Tue Feb 02 10:12:17 2010 +0200
@@ -0,0 +1,1069 @@
+// Copyright (c) 2007-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 "pltables.h"
+#include "dbsqlconstants.h"
+#include "plplugins.h"
+#include <cntdb.h>
+#ifdef SYMBIAN_ENABLE_SPLIT_HEADERS
+#include <cntphonenumparser.h>
+#endif
+
+/**
+@param aDatabase A handle to the database.
+@param aProperties A contact properties object.
+
+@return A pointer to a new CPplCommAddrTable object.
+*/
+CPplCommAddrTable* CPplCommAddrTable::NewL(RSqlDatabase& aDatabase, CLplContactProperties& aProperties)
+	{
+	CPplCommAddrTable* self = CPplCommAddrTable::NewLC(aDatabase, aProperties);
+	CleanupStack::Pop(self);
+	return self;
+	}
+
+
+/**
+@param aDatabase A handle to the database.
+@param aProperties A contact properties object.
+
+@return A pointer to a new CPplCommAddrTable object.
+*/
+CPplCommAddrTable* CPplCommAddrTable::NewLC(RSqlDatabase& aDatabase, CLplContactProperties& aProperties)
+	{
+	CPplCommAddrTable* self = new (ELeave) CPplCommAddrTable(aDatabase, aProperties);
+	CleanupStack::PushL(self);
+	self->ConstructL();
+	return self;
+	}
+
+/**
+Set up the CCntSqlStatement objects held by the class.
+*/
+void CPplCommAddrTable::ConstructL()
+	{
+	// Statement types
+	TCntSqlStatementType insertType(EInsert, KSqlContactCommAddrTableName() );
+	TCntSqlStatementType selectType(ESelect, KSqlContactCommAddrTableName() );
+	TCntSqlStatementType updateType(EUpdate, KSqlContactCommAddrTableName() );
+	TCntSqlStatementType deleteType(EDelete, KSqlContactCommAddrTableName() );
+
+	// Where clauses
+
+	// sizes of the clauses
+	const TInt KWhereContactIdBufSize(KCommAddrContactId().Size() +
+		KWhereStringEqualsStringFormatText().Size() + KCommAddrContactIdParam().Size() );
+	const TInt KWhereCommAddrIdBufSize(KCommAddrId().Size() +
+		KWhereStringEqualsStringFormatText().Size() + KCommAddrIdParam().Size() );
+	const TInt KWhereAndClauseBufSize(KWhereCommAddrIdBufSize + KSqlAnd().Size() + KWhereCommAddrIdBufSize);
+
+	HBufC* whereContactIdClause = HBufC::NewLC(KWhereContactIdBufSize);
+	// for WHERE contact_id = [contact id value]
+	whereContactIdClause->Des().AppendFormat(KWhereStringEqualsStringFormatText,
+		&KCommAddrContactId, &KCommAddrContactIdParam );
+
+	// for WHERE comm_addr_id = [comm addr id value]
+	HBufC* whereCommAddrIdClause = HBufC::NewLC(KWhereCommAddrIdBufSize);
+	whereCommAddrIdClause->Des().AppendFormat(KWhereStringEqualsStringFormatText,
+		&KCommAddrId, &KCommAddrIdParam );
+
+	// for WHERE value = [value string] AND type = [type value]
+	HBufC* whereValueAndTypeClause = HBufC::NewLC(KWhereAndClauseBufSize);
+	TPtr whereValueAndTypeClausePtr = whereValueAndTypeClause->Des();
+	whereValueAndTypeClausePtr.AppendFormat(KWhereStringEqualsStringFormatText,
+		&KCommAddrValue, &KCommAddrValueParam );
+	whereValueAndTypeClausePtr.Append(KSqlAnd);
+	whereValueAndTypeClausePtr.AppendFormat(KWhereStringEqualsStringFormatText,
+		&KCommAddrType, &KCommAddrTypeParam );
+
+	// INSERT
+
+	// insert new comm addr record
+	// 	INSERT INTO comm_addr
+	//		(comm_addr_id, contact_id, type, value, extra_value)
+	//		VALUES (NULL, [contact id value], [type value],
+	//			[value string], [extra value string]);
+	//
+	iInsertStmnt = TSqlProvider::GetSqlStatementL(insertType);
+	iInsertStmnt->SetParamL(KCommAddrId(), KCommAddrIdParam() );
+	iInsertStmnt->SetParamL(KCommAddrContactId(), KCommAddrContactIdParam() );
+	iInsertStmnt->SetParamL(KCommAddrValue(), KCommAddrValueParam() );
+	iInsertStmnt->SetParamL(KCommAddrExtraValue(), KCommAddrExtraValueParam() );
+	iInsertStmnt->SetParamL(KCommAddrType(), KCommAddrTypeParam() );
+
+	// SELECT
+
+	// select all fields for all comm addrs for a particular item id
+	// For a statement in the following format:
+	// 	SELECT comm_addr_id, type, value, extra_value FROM comm_addr
+	//		WHERE contact_id = [contact id value];
+	//
+	iWholeSelectStmnt = TSqlProvider::GetSqlStatementL(selectType);
+	iWholeSelectStmnt->SetParamL(KCommAddrId(), KNullDesC() );
+	iWholeSelectStmnt->SetParamL(KCommAddrType(), KNullDesC() );
+	iWholeSelectStmnt->SetParamL(KCommAddrValue(), KNullDesC() );
+	iWholeSelectStmnt->SetParamL(KCommAddrExtraValue(), KNullDesC() );
+	iWholeSelectStmnt->SetConditionL(*whereContactIdClause);
+
+	// select fields for contacts that match phone, email or SIP lookup
+	// For a statement in the following format:
+	// 	SELECT contact_id, extra_value FROM comm_addr
+	//		WHERE value = [value string] AND type = [type value];
+	//
+	iMatchSelectStmnt = TSqlProvider::GetSqlStatementL(selectType);
+	iMatchSelectStmnt->SetParamL(KCommAddrContactId(), KNullDesC() );
+	iMatchSelectStmnt->SetParamL(KCommAddrExtraValue(), KNullDesC() );
+	iMatchSelectStmnt->SetConditionL(*whereValueAndTypeClause);
+
+	// UPDATE
+
+	// update comm addr record
+	// For a statement in the following format:
+	// 	UPDATE comm_addr SET
+	//		contact_id = [contact id value],
+	//		type = [type value],
+	//		value = [value string],
+	//		extra_value = [extra value string]
+	//		WHERE comm_addr_id = [comm addr id value];
+	//
+	iUpdateStmnt = TSqlProvider::GetSqlStatementL(updateType);
+	iUpdateStmnt->SetParamL(KCommAddrContactId(), KCommAddrContactIdParam() );
+	iUpdateStmnt->SetParamL(KCommAddrValue(), KCommAddrValueParam() );
+	iUpdateStmnt->SetParamL(KCommAddrExtraValue(), KCommAddrExtraValueParam() );
+	iUpdateStmnt->SetParamL(KCommAddrType(), KCommAddrTypeParam() );
+	iUpdateStmnt->SetConditionL(*whereCommAddrIdClause);
+
+	// DELETE
+
+	// delete single comm addr record
+	// For a statement in the following format:
+	// 	DELETE FROM comm_addr WHERE comm_addr_id = [comm addr id value];
+	//
+	iSingleDeleteStmnt = TSqlProvider::GetSqlStatementL(deleteType);
+	iSingleDeleteStmnt->SetConditionL(*whereCommAddrIdClause);
+
+	// delete all comm addrs for a particular contact item
+	// For a statement in the following format:
+	// 	DELETE FROM comm_addr WHERE contact_id = [contact id value];
+	//
+	iAllForItemDeleteStmnt = TSqlProvider::GetSqlStatementL(deleteType);
+	iAllForItemDeleteStmnt->SetConditionL(*whereContactIdClause);
+
+	CleanupStack::PopAndDestroy(3, whereContactIdClause); // and whereCommAddrIdClause, whereValueAndTypeClause
+	}
+
+
+/**
+Insert new communication addresses into the comm_addr table.
+@param aItem A contact item whose communication addresses are to be added to the contacts database.
+*/
+void CPplCommAddrTable::CreateInDbL(CContactItem& aItem)
+	{
+	// Check that the contact item is a card, own card or ICC entry.
+	const TUid KType = aItem.Type();
+	if (KType != KUidContactCard && KType != KUidContactOwnCard && KType != KUidContactICCEntry)
+		{
+		return;
+		}
+
+	// create lists for comm_addrs to keep track of what we have already seen so as to avoid duplicates
+	RArray<TMatch> newPhones;
+	RArray<TPtrC>  newEmails;
+	RArray<TPtrC>  newSips;
+	CleanupClosePushL(newPhones);
+	CleanupClosePushL(newEmails);
+	CleanupClosePushL(newSips);
+
+	for (TInt fieldNum = aItem.CardFields().Count() - 1; fieldNum >= 0; --fieldNum)
+		{
+		CContactItemField& currField = aItem.CardFields()[fieldNum];
+		const CContentType& contType = currField.ContentType();
+		TBool isPhone(contType.ContainsFieldType(KUidContactFieldPhoneNumber) ||
+					  contType.ContainsFieldType(KUidContactFieldFax)		  ||
+					  contType.ContainsFieldType(KUidContactFieldSms)		  );
+		TBool isEmail(contType.ContainsFieldType(KUidContactFieldEMail) );
+		TBool isSip(contType.ContainsFieldType(KUidContactFieldSIPID) );
+
+		// check it's a field we want and that it's not empty
+		// insert a new address only if we haven't already seen it -- no point storing the same one twice.
+		if ((isPhone || isEmail || isSip) && currField.StorageType() == KStorageTypeText
+				&& currField.TextStorage()->IsFull() )
+			{
+			const TContactItemId KItemId(aItem.Id());
+
+			// get phone numbers
+			if (isPhone)
+				{
+				TMatch phoneNumber;
+				phoneNumber = CreatePaddedPhoneDigitsL(currField.TextStorage()->Text(), KLowerSevenDigits,
+													   KMaxPhoneMatchLength - KLowerSevenDigits);
+				if (newPhones.Find(phoneNumber, TIdentityRelation<TMatch>(&TMatch::Equals) ) == KErrNotFound)
+					{
+					DoPhoneNumWriteOpL(phoneNumber, EInsert, KItemId);
+					newPhones.AppendL(phoneNumber);
+					}
+				}
+			// get email addresses
+			else if (isEmail && newEmails.Find(currField.TextStorage()->Text() ) == KErrNotFound)
+				{
+				DoNonPhoneWriteOpL(currField.TextStorage()->Text(), EInsert, KItemId, EEmailAddress);
+				newEmails.AppendL(currField.TextStorage()->Text() );
+				}
+			// get SIP addresses
+			else if (newSips.Find(currField.TextStorage()->Text() ) == KErrNotFound)
+				{
+				DoNonPhoneWriteOpL(currField.TextStorage()->Text(), EInsert, KItemId, ESipAddress);
+				newSips.AppendL(currField.TextStorage()->Text() );
+				}
+			}
+		}
+
+	CleanupStack::PopAndDestroy(3, &newPhones); // and newSips, newEmails
+	}
+
+
+/**
+Updates communication addresses in the database.
+
+If there are the same number of items to be updated as are already in the database, the
+existing records are overwritten using the update statement. However, if there is a
+different number of addresses to be updated, records in the database (if there are any)
+are deleted (using DeleteL() ) and the new data is inserted (using CreateInDbL() ).
+
+@param aItem A contact item whose communication addresses are to be updated in the contacts database.
+*/
+void CPplCommAddrTable::UpdateL(const CContactItem& aItem)
+	{
+	// Check that the contact item is a card, own card or ICC entry.
+	const TUid type(aItem.Type() );
+	if (type != KUidContactCard && type != KUidContactOwnCard && type != KUidContactICCEntry)
+		{
+		return;
+		}
+
+	const TContactItemId KItemId(aItem.Id() );
+
+	// create lists for comm_addrs and go through contact item to populate them with any new ones we find
+	RArray<TMatch> newPhones;
+	RArray<TPtrC>  newEmails;
+	RArray<TPtrC>  newSips;
+	CleanupClosePushL(newPhones);
+	CleanupClosePushL(newEmails);
+	CleanupClosePushL(newSips);
+
+	for (TInt fieldNum = aItem.CardFields().Count() - 1; fieldNum >= 0; --fieldNum)
+		{
+		CContactItemField& currField = aItem.CardFields()[fieldNum];
+		const CContentType& contType = currField.ContentType();
+		TBool isPhone(contType.ContainsFieldType(KUidContactFieldPhoneNumber) ||
+					  contType.ContainsFieldType(KUidContactFieldFax)		  ||
+					  contType.ContainsFieldType(KUidContactFieldSms)		  );
+		TBool isEmail(contType.ContainsFieldType(KUidContactFieldEMail) );
+		TBool isSip(contType.ContainsFieldType(KUidContactFieldSIPID) );
+
+		// check it's a field we want and that it's not empty
+		// store a new address if we haven't already seen it -- no point storing the same one twice.
+		if ((isPhone || isEmail || isSip) && currField.StorageType() == KStorageTypeText
+				&& currField.TextStorage()->IsFull() )
+			{
+			// get phone numbers
+			if (isPhone)
+				{
+				TMatch phoneNumber;
+				phoneNumber = CreatePaddedPhoneDigitsL(currField.TextStorage()->Text(), KLowerSevenDigits,
+													   KMaxPhoneMatchLength - KLowerSevenDigits);
+				if (newPhones.Find(phoneNumber, TIdentityRelation<TMatch>(&TMatch::Equals) ) == KErrNotFound)
+					{
+					newPhones.AppendL(phoneNumber);
+					}
+				}
+			// get email addresses
+			else if (isEmail && newEmails.Find(currField.TextStorage()->Text() ) == KErrNotFound)
+				{
+				newEmails.AppendL(currField.TextStorage()->Text() );
+				}
+			// get SIP addresses
+			else if (newSips.Find(currField.TextStorage()->Text() ) == KErrNotFound)
+				{
+				newSips.AppendL(currField.TextStorage()->Text() );
+				}
+			}
+		}
+
+	// if there are no comm addresses in the contact item, delete any from the database
+	if (!(newPhones.Count() + newEmails.Count() + newSips.Count() ) )
+		{
+		TBool lowDiskErr(EFalse);
+		DeleteL(aItem, lowDiskErr);
+		CleanupStack::PopAndDestroy(3, &newPhones); // and newSips, newEmails
+		if (lowDiskErr)
+			{
+			User::Leave(KErrDiskFull);
+			}
+		return;
+		}
+
+	// create from the database a list of comm_addr_ids that can be recycled as their
+	// comm_addrs are in the database but not in the new version of the contact item
+	RArray<TInt> freeCommAddrIds;
+	CleanupClosePushL(freeCommAddrIds);
+
+	// weed out addresses from the list that are already in the db but haven't changed
+	// and populate the freeCommAddrIds list
+	RemoveNonUpdatedAddrsL(newPhones, newEmails, newSips, freeCommAddrIds, KItemId);
+
+	// do the actual updating on an address-by-address basis
+	DoUpdateCommAddrsL(newPhones, newEmails, newSips, freeCommAddrIds, KItemId);
+
+	CleanupStack::PopAndDestroy(4, &newPhones); // and freeCommAddrIds, newSips, newEmails
+	}
+
+
+/**
+Removes comm addresses from the 3 lists that are already in the database and have been updated.
+It takes the 3 lists in as parameters and modifies them accordingly. It also populates the list
+of comm address ids that are free to be recycled during updating.
+*/
+void CPplCommAddrTable::RemoveNonUpdatedAddrsL(RArray<TMatch>& aNewPhones, RArray<TPtrC>& aNewEmails, RArray<TPtrC>& aNewSips,
+					  RArray<TInt>& aFreeCommAddrIds, const TInt aItemId)
+	{
+	// build the RSqlStatement
+	RSqlStatement stmnt;
+	CleanupClosePushL(stmnt);
+	stmnt.PrepareL(iDatabase, iWholeSelectStmnt->SqlStringL() );
+	const TInt KContactIdParamIndex(KFirstIndex); // first and only parameter in the query
+	User::LeaveIfError(stmnt.BindInt(KContactIdParamIndex, aItemId) ) ;
+
+	// fetch the results from the query and compare them with the new comm_addrs we have
+	TInt err(KErrNone);
+	while ((err = stmnt.Next() ) == KSqlAtRow)
+		{
+		const TInt KType(stmnt.ColumnInt(iWholeSelectStmnt->ParameterIndex(KCommAddrType() ) ) );
+		if (KType == EPhoneNumber)
+			{
+			TMatch phoneNumber;
+			TPtrC valString    = stmnt.ColumnTextL(iWholeSelectStmnt->ParameterIndex(KCommAddrValue() ) );
+			TPtrC extValString = stmnt.ColumnTextL(iWholeSelectStmnt->ParameterIndex(KCommAddrExtraValue() ) );
+			User::LeaveIfError(TLex(valString).Val(phoneNumber.iLowerSevenDigits) );
+			User::LeaveIfError(TLex(extValString).Val(phoneNumber.iUpperDigits) );
+
+			TInt matchIndex(aNewPhones.Find(phoneNumber, TIdentityRelation<TMatch>(&TMatch::Equals) ) );
+			// remove any phone numbers from the new list if we already
+			// have them in the db and they haven't changed...
+			if (matchIndex != KErrNotFound)
+				{
+				aNewPhones.Remove(matchIndex);
+				}
+			// ...and add any spare ids to the recycle list
+			else
+				{
+				aFreeCommAddrIds.AppendL(
+					stmnt.ColumnInt(iWholeSelectStmnt->ParameterIndex(KCommAddrId() ) ) );
+				}
+			}
+		else // is Email or SIP
+			{
+			TPtrC valString = stmnt.ColumnTextL(iWholeSelectStmnt->ParameterIndex(KCommAddrValue() ) );
+			TInt matchIndex(0);
+
+			// remove any email and sip addresses from the new list if
+			// we already have them in the db and they haven't changed...
+			if (KType == EEmailAddress)
+				{
+				matchIndex = aNewEmails.Find(valString);
+				if (matchIndex != KErrNotFound)
+					{
+					aNewEmails.Remove(matchIndex);
+					}
+				}
+			else // SIP
+				{
+				matchIndex = aNewSips.Find(valString);
+				if (matchIndex != KErrNotFound)
+					{
+					aNewSips.Remove(matchIndex);
+					}
+				}
+
+			// ...and add any spare ids to the recycle list
+			if (matchIndex == KErrNotFound)
+				{
+				aFreeCommAddrIds.AppendL(
+					stmnt.ColumnInt(iWholeSelectStmnt->ParameterIndex(KCommAddrId() ) ) );
+				}
+			}
+		}
+	// leave if we didn't complete going through the results properly
+	if(err != KSqlAtEnd)
+		{
+		User::Leave(err);
+		}
+	CleanupStack::PopAndDestroy(&stmnt);
+	}
+
+
+/**
+Does comm_addr updating on an address-by-address basis.
+Takes 3 lists of new addresses and a list of free comm_addr_ids.
+*/
+void CPplCommAddrTable::DoUpdateCommAddrsL(RArray<TMatch>& aNewPhones, RArray<TPtrC>& aNewEmails, RArray<TPtrC>& aNewSips,
+					  RArray<TInt>& aFreeCommAddrIds, const TInt aItemId)
+	{
+	// if we have free ids to recycle and new comm_addrs, insert them by UPDATE
+	const TInt KFirstElementId(0);
+	while (aFreeCommAddrIds.Count() && (aNewPhones.Count() || aNewEmails.Count() || aNewSips.Count() ) )
+		{
+		if(aNewPhones.Count() )
+			{
+			DoPhoneNumWriteOpL(aNewPhones[KFirstElementId], EUpdate, aItemId, aFreeCommAddrIds[KFirstElementId]);
+			aNewPhones.Remove(KFirstElementId);
+			aFreeCommAddrIds.Remove(KFirstElementId);
+			}
+		else if(aNewEmails.Count() )
+			{
+			DoNonPhoneWriteOpL(aNewEmails[KFirstElementId], EUpdate, aItemId, EEmailAddress,
+								aFreeCommAddrIds[KFirstElementId]);
+			aNewEmails.Remove(KFirstElementId);
+			aFreeCommAddrIds.Remove(KFirstElementId);
+			}
+		else if(aNewSips.Count() )
+			{
+			DoNonPhoneWriteOpL(aNewSips[KFirstElementId], EUpdate, aItemId, ESipAddress,
+								aFreeCommAddrIds[KFirstElementId]);
+			aNewSips.Remove(KFirstElementId);
+			aFreeCommAddrIds.Remove(KFirstElementId);
+			}
+		}
+
+	// if we still have free ids to recycle but no new comm_addrs,
+	// delete the existing comm_addrs for these ids
+	const TInt KNumFreeIds(aFreeCommAddrIds.Count() );
+	for (TInt i = 0; i < KNumFreeIds; ++i)
+		{
+		TBool lowDiskErr(EFalse);
+		DeleteSingleCommAddrL(aFreeCommAddrIds[i], lowDiskErr);
+		if (lowDiskErr)
+			{
+			User::Leave(KErrDiskFull);
+			}
+		}
+
+	// if we still have new comm_addrs but no free ids to recycle,
+	// put them in the database using INSERT
+	const TInt KNumNewPhones(aNewPhones.Count() );
+	const TInt KNumNewEmails(aNewEmails.Count() );
+	const TInt KNumNewSips(aNewSips.Count() );
+	for (TInt i = 0; i < KNumNewPhones; ++i)
+		{
+		DoPhoneNumWriteOpL(aNewPhones[i], EInsert, aItemId);
+		}
+	for (TInt i = 0; i < KNumNewEmails; ++i)
+		{
+		DoNonPhoneWriteOpL(aNewEmails[i], EInsert, aItemId, EEmailAddress);
+		}
+	for (TInt i = 0; i < KNumNewSips; ++i)
+		{
+		DoNonPhoneWriteOpL(aNewSips[i], EInsert, aItemId, ESipAddress);
+		}
+	}
+
+
+/**
+Deletes individual communication addresses from the database. In other words, deletes at
+the sub-contact item level rather than deleting everything with a specific contact item ID.
+*/
+void CPplCommAddrTable::DeleteSingleCommAddrL(TInt aCommAddrId, TBool& aLowDiskErrorOccurred)
+	{
+	RSqlStatement stmnt;
+	CleanupClosePushL(stmnt);
+	stmnt.PrepareL(iDatabase, iSingleDeleteStmnt->SqlStringL() );
+	const TInt KCommAddrIdParamIndex(KFirstIndex); // first and only parameter in the query
+	User::LeaveIfError(stmnt.BindInt(KCommAddrIdParamIndex, aCommAddrId ) );
+	TInt err = stmnt.Exec();
+	CleanupStack::PopAndDestroy(&stmnt);
+
+	if (err == KErrDiskFull)
+		{
+		aLowDiskErrorOccurred = ETrue;
+		}
+	else
+		{
+		User::LeaveIfError(err);
+		}
+	}
+
+/**
+Performs write operations for individual communication addresses of type "phone number".
+This version of the method does not require a CommAddrId argument. Therefore, this can only be used
+for insertion and not updating.
+*/
+void CPplCommAddrTable::DoPhoneNumWriteOpL(const CPplCommAddrTable::TMatch& aPhoneNum, TCntSqlStatement aType,
+											TInt aCntId)
+	{
+	// provide a commaddr of 0 as a default argument
+	DoPhoneNumWriteOpL(aPhoneNum, aType, aCntId, 0);
+	}
+
+
+/**
+Performs write (Insert/Update) operations for indiviual communication addresses of type "phone number".
+*/
+void CPplCommAddrTable::DoPhoneNumWriteOpL(const CPplCommAddrTable::TMatch& aPhoneNum, TCntSqlStatement aType,
+											TInt aCntId, TInt aCommAddrId)
+	{
+	// leave if the statement type is not insert or update.
+	// also, we can't update if aCommAddrId is 0 as we don't know the record's id
+	if ((aType != EUpdate && aType != EInsert) || (aType == EUpdate && aCommAddrId == 0) )
+		{
+		User::Leave(KErrArgument);
+		}
+
+	RSqlStatement stmnt;
+	CleanupClosePushL(stmnt);
+
+	// temporary reference to the CCntSqlStatements member variables to take advantage
+	// of the commonality between update and insert operations.
+	CCntSqlStatement* tempCntStmnt = iUpdateStmnt;
+	if (aType == EInsert)
+		{
+		tempCntStmnt = iInsertStmnt;
+		}
+
+	User::LeaveIfError(stmnt.Prepare(iDatabase, tempCntStmnt->SqlStringL() ) );
+	User::LeaveIfError(stmnt.BindInt(tempCntStmnt->ParameterIndex(KCommAddrContactId() ), aCntId) );
+	User::LeaveIfError(stmnt.BindInt(tempCntStmnt->ParameterIndex(KCommAddrExtraValue() ), aPhoneNum.iUpperDigits) );
+	User::LeaveIfError(stmnt.BindInt(tempCntStmnt->ParameterIndex(KCommAddrValue() ), aPhoneNum.iLowerSevenDigits) );
+	User::LeaveIfError(stmnt.BindInt(tempCntStmnt->ParameterIndex(KCommAddrType() ), EPhoneNumber) );
+
+	if (aType == EInsert)
+		{
+		User::LeaveIfError(stmnt.BindNull(tempCntStmnt->ParameterIndex(KCommAddrId() ) ) );
+		}
+	else
+		{
+		// it's the fifth parameter in the query and is in the WHERE
+		// clause so we can't get its index from the CCntSqlStatement
+		const TInt KCommAddrIdParamIndex(KFirstIndex + 4);
+		User::LeaveIfError(stmnt.BindInt(KCommAddrIdParamIndex, aCommAddrId) );
+		}
+
+	User::LeaveIfError(stmnt.Exec() );
+	CleanupStack::PopAndDestroy(&stmnt);
+	}
+
+
+/**
+Performs write operations for individual communication addresses of type "email" or "SIP" address.
+This version of the method does not require a CommAddrId argument. Therefore, this can only be used
+for insertion and not updating.
+*/
+void CPplCommAddrTable::DoNonPhoneWriteOpL(const TDesC& aAddress, const TCntSqlStatement aType,
+										 TInt aCntId, TCommAddrType aAddrType)
+	{
+	// provide a commaddr of 0 as a default argument
+	DoNonPhoneWriteOpL(aAddress, aType, aCntId, aAddrType, 0);
+	}
+
+
+/**
+Performs write (Insert/Update) operations for indiviual communication addresses of
+type "Email address" or "SIP Address".
+*/
+void CPplCommAddrTable::DoNonPhoneWriteOpL(const TDesC& aAddress, const TCntSqlStatement aType,
+										 TInt aCntId, TCommAddrType aAddrType, TInt aCommAddrId)
+	{
+	// leave if the statement type is not insert or update.
+	// also, we can't update if aCommAddrId is 0 as we don't know the record's id
+	if ((aType != EUpdate && aType != EInsert) || (aType == EUpdate && aCommAddrId == 0) )
+		{
+		User::Leave(KErrNotFound);
+		}
+
+	RSqlStatement stmnt;
+	CleanupClosePushL(stmnt);
+
+	// temporary reference to the CCntSqlStatements member variables to take advantage
+	// of the commonality between update and insert operations.
+	CCntSqlStatement* tempCntStmnt = iUpdateStmnt;
+	if (aType == EInsert)
+		{
+		tempCntStmnt = iInsertStmnt;
+		}
+
+	User::LeaveIfError(stmnt.Prepare(iDatabase, tempCntStmnt->SqlStringL() ) );
+	User::LeaveIfError(stmnt.BindInt(tempCntStmnt->ParameterIndex(KCommAddrContactId() ), aCntId) );
+	User::LeaveIfError(stmnt.BindText(tempCntStmnt->ParameterIndex(KCommAddrValue() ), aAddress) );
+	User::LeaveIfError(stmnt.BindNull(tempCntStmnt->ParameterIndex(KCommAddrExtraValue() ) ) );
+	User::LeaveIfError(stmnt.BindInt(tempCntStmnt->ParameterIndex(KCommAddrType() ), aAddrType) );
+
+	if (aType == EInsert)
+		{
+		User::LeaveIfError(stmnt.BindNull(tempCntStmnt->ParameterIndex(KCommAddrId() ) ) );
+		}
+	else
+		{
+		// it's the fifth parameter in the query and is in the WHERE
+		// clause so we can't get its index from the CCntSqlStatement
+		const TInt KCommAddrIdParamIndex(KFirstIndex + 4);
+		User::LeaveIfError(stmnt.BindInt(KCommAddrIdParamIndex, aCommAddrId) );
+		}
+
+	User::LeaveIfError(stmnt.Exec() );
+	CleanupStack::PopAndDestroy(&stmnt);
+	}
+
+
+/**
+Deletes all the communication addresses for a particular contact item. Should be used when
+deleting a contact item from the database altogether.
+
+@param aItem The contact item whose communcation addresses are to be deleted.
+*/
+void CPplCommAddrTable::DeleteL(const CContactItem& aItem, TBool& aLowDiskErrorOccurred)
+	{
+	const TUid KType = aItem.Type();
+	if (KType != KUidContactCard && KType != KUidContactOwnCard && KType != KUidContactICCEntry)
+		{
+		return;
+		}
+
+	RSqlStatement stmnt;
+	CleanupClosePushL(stmnt);
+	stmnt.PrepareL(iDatabase, iAllForItemDeleteStmnt->SqlStringL() );
+	const TInt KContactIdParamIndex(KFirstIndex); // first and only parameter in query
+	User::LeaveIfError(stmnt.BindInt(KContactIdParamIndex, aItem.Id() ) );
+	TInt err = stmnt.Exec();
+	CleanupStack::PopAndDestroy(&stmnt);
+
+	if (err == KErrDiskFull)
+		{
+		aLowDiskErrorOccurred = ETrue;
+		}
+	else
+		{
+		User::LeaveIfError(err);
+		}
+	}
+
+
+/**
+Creates the comm_addr table and its indexes in the database.
+*/
+void CPplCommAddrTable::CreateTableL()
+	{
+	User::LeaveIfError(iDatabase.Exec(KCommAddrCreateStmnt) );
+	}
+
+
+/**
+Returns an array of contact item IDs for all the contact items which may contain
+the specified telephone number in a telephone, fax or SMS type field.
+
+The comparison method used is not exact.  The number is compared starting from
+the right side of the number and the method returns an array of candidate
+matches.  Punctuation (e.g. spaces) and other alphabetic characters are ignored
+when comparing.
+
+Additionally, if the Contacts Model Phone Parser (CNTPHONE.DLL) is available,
+then any DTMF digits are also excluded from the comparision.
+
+Note that due to the way numbers are stored in the database, it is recommended
+that at least 7 match digits are specified even when matching a number
+containing fewer digits.  Failure to follow this guideline may (depending on the
+database contents) mean that the function will not return the expected Contact
+IDs.
+
+@param aNumber Phone number string.
+@param aMatchLengthFromRight Number of digits from the right of the phone number
+to use.  Up to 15 digits can be specified, and it is recommended that at least 7
+match digits are specified.
+@param aDatabase The database.
+
+@return Array of contact IDs which are candidate matches.
+*/
+CContactIdArray* CPplCommAddrTable::MatchPhoneNumberL(const TDesC& aNumber, const TInt aMatchLengthFromRight)
+	{
+	CContactIdArray* phoneMatchArray = CContactIdArray::NewLC();
+
+	TInt numLowerDigits = aMatchLengthFromRight;
+	TInt numUpperDigits = 0;
+
+	if(numLowerDigits > KLowerSevenDigits)
+		{
+		// New style matching.
+		numLowerDigits = KLowerSevenDigits;
+		numUpperDigits = aMatchLengthFromRight - KLowerSevenDigits;
+		}
+
+	TMatch phoneDigits = CreatePaddedPhoneDigitsL(aNumber, numLowerDigits, numUpperDigits);
+
+	if (phoneDigits.iNumLowerDigits + phoneDigits.iNumUpperDigits > 0)
+		{
+		// build statement
+		RSqlStatement stmnt;
+		CleanupClosePushL(stmnt);
+		stmnt.PrepareL(iDatabase, iMatchSelectStmnt->SqlStringL() );
+
+		const TInt KValueParamIndex(KFirstParam);			// first parameter in query...
+		const TInt KTypeParamIndex(KValueParamIndex + 1);	// ...and the second.
+
+    	User::LeaveIfError(stmnt.BindInt(KValueParamIndex, phoneDigits.iLowerSevenDigits ));
+    	User::LeaveIfError(stmnt.BindInt(KTypeParamIndex, EPhoneNumber ));
+
+		// fetch the list of any matching contact ids
+		TInt err(KErrNone);
+		const TInt KContactIdIdx(iMatchSelectStmnt->ParameterIndex(KCommAddrContactId() ) );
+		const TInt KExtraValueIdx(iMatchSelectStmnt->ParameterIndex(KCommAddrExtraValue() ) );
+		while ((err = stmnt.Next() ) == KSqlAtRow)
+			{
+			if (aMatchLengthFromRight <= KLowerSevenDigits)
+				{
+				// Matching 7 or less digits...we've already matched.
+				phoneMatchArray->AddL(stmnt.ColumnInt(KContactIdIdx) );
+				}
+			else
+				{
+				// Check the upper digits...
+				TInt32 storedUpperDigits(0);
+				TPtrC extValString = stmnt.ColumnTextL(KExtraValueIdx);
+				User::LeaveIfError(TLex(extValString).Val(storedUpperDigits) );
+
+				const TInt KDigitsToRemove = KMaxPhoneMatchLength - KLowerSevenDigits - phoneDigits.iNumUpperDigits;
+				for(TInt i = 0; i < KDigitsToRemove; ++i)
+					{
+					// repeatedly divide by 10 to lop off the appropriate number of digits from the right
+					storedUpperDigits /= 10;
+					}
+
+				storedUpperDigits = TMatch::PadOutPhoneMatchNumber(storedUpperDigits, KDigitsToRemove);
+
+				if (phoneDigits.iUpperDigits == storedUpperDigits)
+					{
+					phoneMatchArray->AddL(stmnt.ColumnInt(KContactIdIdx) );
+					}
+				}
+			}
+
+		// leave if we didn't complete going through the results properly
+		if(err != KSqlAtEnd)
+			{
+			User::Leave(err);
+			}
+		CleanupStack::PopAndDestroy(&stmnt);
+		}
+
+	CleanupStack::Pop(phoneMatchArray);
+	return phoneMatchArray;
+	}
+
+
+/**
+Searches the contacts database to find any contact items with an exact match on the email address supplied.
+
+@param aEmailAddr A descriptor containing the email address to be found in the database.
+@return An array of contact IDs which match the supplied email address.
+*/
+CContactIdArray* CPplCommAddrTable::MatchEmailAddressL(const TDesC& aEmailAddr)
+	{
+	return MatchNonPhoneAddrL(aEmailAddr, EEmailAddress);
+	}
+
+
+/**
+Searches the contacts database to find any contact items with an exact match on the SIP address supplied.
+
+@param aSipAddr A descriptor containing the SIP address to be found in the database.
+@return An array of contact IDs which match the supplied SIP address.
+*/
+CContactIdArray* CPplCommAddrTable::MatchSipAddressL(const TDesC& aSipAddr)
+	{
+	return MatchNonPhoneAddrL(aSipAddr, ESipAddress);
+	}
+
+
+/**
+Searches the contacts database to find any contact items with an exact match on the address supplied.
+
+@param aCommAddr A descriptor containing the address to be found in the database.
+@param aAddrType The type of addresses that is being sought.
+@return An array of contact IDs which match the supplied address.
+*/
+CContactIdArray* CPplCommAddrTable::MatchNonPhoneAddrL(const TDesC& aCommAddr, TCommAddrType aAddrType)
+	{
+
+	// build statement
+	RSqlStatement stmnt;
+	CleanupClosePushL(stmnt);
+	stmnt.PrepareL(iDatabase, iMatchSelectStmnt->SqlStringL() );
+
+	const TInt KValueParamIndex(KFirstParam);					// first parameter in query...
+	const TInt KTypeParamIndex(KValueParamIndex + 1);	// ...and the second.
+	User::LeaveIfError(stmnt.BindText(KValueParamIndex, aCommAddr) );
+	User::LeaveIfError(stmnt.BindInt(KTypeParamIndex, aAddrType) );
+
+	// fetch the list of any matching contact ids
+	CContactIdArray* idArray = CContactIdArray::NewLC();
+	TInt err(KErrNone);
+	const TInt KContactIdIdx(iMatchSelectStmnt->ParameterIndex(KCommAddrContactId() ) );
+	while ((err = stmnt.Next() ) == KSqlAtRow)
+		{
+		idArray->AddL(stmnt.ColumnInt(KContactIdIdx) );
+		}
+
+	// leave if we didn't complete going through the results properly
+	if(err != KSqlAtEnd)
+		{
+		User::Leave(err);
+		}
+
+	CleanupStack::Pop(idArray);
+	CleanupStack::PopAndDestroy(&stmnt);
+
+	return idArray;
+	}
+
+
+/**
+CPplCommAddrTable constructor
+*/
+CPplCommAddrTable::CPplCommAddrTable(RSqlDatabase& aDatabase, CLplContactProperties& aProperties)
+	: iProperties(aProperties),
+	iDatabase(aDatabase)
+	{
+	}
+
+
+/**
+Destructor
+
+Tidy up CCntSqlStatement objects
+*/
+CPplCommAddrTable::~CPplCommAddrTable()
+	{
+	delete iInsertStmnt;
+	delete iWholeSelectStmnt;
+	delete iMatchSelectStmnt;
+	delete iUpdateStmnt;
+	delete iSingleDeleteStmnt;
+	delete iAllForItemDeleteStmnt;
+	}
+
+/**
+Convert the supplied string to a matchable phone number.
+
+@param aText Descriptor containing phone number.
+@param aLowerMatchlength Number of least significant phone digits to use.
+@param aUpperMatchLength Number of most significant phone digits to use.
+
+@return The hash code(s) to use when matching the supplied phone number.
+*/
+CPplCommAddrTable::TMatch CPplCommAddrTable::CreatePaddedPhoneDigitsL(const TDesC& aNumber, const TInt aNumLowerDigits, const TInt aNumUpperDigits)
+	{
+	CPplCommAddrTable::TMatch phoneNumber = CreatePhoneMatchNumberL(aNumber, aNumLowerDigits, aNumUpperDigits);
+
+	if (phoneNumber.iNumLowerDigits + phoneNumber.iUpperDigits == 0)
+		{
+		// No digits, do nothing
+		}
+	else if(phoneNumber.iNumLowerDigits < KLowerSevenDigits)
+		{
+		// Only the lower-digits hash is used, pad out the number to
+		// KLowerSevenDigits.
+		TInt pad = KLowerSevenDigits - phoneNumber.iNumLowerDigits;
+		phoneNumber.iLowerSevenDigits = TMatch::PadOutPhoneMatchNumber(phoneNumber.iLowerSevenDigits,pad);
+		}
+	else if(phoneNumber.iNumUpperDigits < (KMaxPhoneMatchLength - KLowerSevenDigits) )
+		{
+		// The lower-digits hash is full, pad out the upper hash if less than 15
+		// digits total.
+		TInt pad = KMaxPhoneMatchLength - KLowerSevenDigits - phoneNumber.iNumUpperDigits;
+		phoneNumber.iUpperDigits = TMatch::PadOutPhoneMatchNumber(phoneNumber.iUpperDigits,pad);
+		}
+
+	return phoneNumber;
+	}
+
+
+/**
+CPplCommAddrTable::TMatch constructor.
+*/
+CPplCommAddrTable::TMatch::TMatch()
+	:
+	iLowerSevenDigits(0),
+	iUpperDigits(0),
+	iNumLowerDigits(0),
+	iNumUpperDigits(0)
+	{
+	}
+
+
+TBool CPplCommAddrTable::TMatch::operator==(const TMatch& aSecondMatch)const
+	{
+	return (iLowerSevenDigits == aSecondMatch.iLowerSevenDigits)&& (iUpperDigits == aSecondMatch.iUpperDigits);
+	}
+
+
+TBool CPplCommAddrTable::TMatch::Equals(const TMatch& aRMatch, const TMatch& aLMatch)
+	{
+	return aRMatch == aLMatch;
+	}
+
+/**
+Pad out the supplied phone digits with zeroes.
+
+@param aPhoneNumber The number to pad.
+@param aPadOutLength The number of digits to pad out.
+
+@return The padded number.
+*/
+TInt32 CPplCommAddrTable::TMatch::PadOutPhoneMatchNumber(TInt32& aPhoneNumber, TInt aPadOutLength)
+	{
+	TInt32 result(aPhoneNumber);
+	const TInt KMult(10);
+	for(TInt i = 0; i < aPadOutLength; ++i)
+		{
+		result *= KMult;
+		}
+	aPhoneNumber = result;
+	return result;
+	}
+
+
+/**
+Returns the hash code(s) to use when matching the supplied phone number.  If the
+number supplied has more actual phone digits (i.e. not including spaces) than
+KLowerSevenDigits, a second hash is generated to hold the remaining most
+significant phone digits.  Extracts DTMF digits from the phone number if a
+parser is available, otherwise just removes the non-digit characters.
+
+@param aText Descriptor containing contacts phone number field.
+@param aLowerMatchlength Number of least significant phone digits to use.
+@param aUpperMatchLength Number of most significant phone digits to use.
+
+@return The hash code(s) to use when matching the supplied phone number.
+*/
+CPplCommAddrTable::TMatch CPplCommAddrTable::CreatePhoneMatchNumberL(const TDesC& aText, TInt aLowerMatchLength, TInt aUpperMatchLength)
+	{
+	__ASSERT_DEBUG( ((aLowerMatchLength == KLowerSevenDigits) && (aUpperMatchLength > 0) )		// upper 8 digits
+					|| ((aLowerMatchLength <= KLowerSevenDigits) && (aUpperMatchLength == 0) ),	// lower 7 digits
+					User::Leave(KErrNotSupported) );
+
+	const TInt KBufLength = KCntMaxTextFieldLength+1;
+	TBuf<KBufLength> buf;
+
+	CContactPhoneNumberParser* parser = iProperties.ContactPhoneParserL().Parser();
+
+	if (parser)
+		{
+		parser->ExtractRawNumber(aText.Left(KCntMaxTextFieldLength),buf);
+		}
+	else
+		{
+		if(aText.Length() <= KBufLength)
+			{
+			buf = aText;
+			}
+		else
+			{
+			buf = aText.Right(KBufLength);
+			}
+		TMatch::StripOutNonDigitChars(buf);
+		}
+
+	TMatch phoneNumber;
+
+	if (buf.Length() == 0)
+		{
+		return phoneNumber;
+		}
+
+	// Generate a hash for the upper digits only if the phone number string is
+	// large enough and more than 7 digits are to be matched.
+	TInt phoneNumberlength = buf.Length();
+	if ( (phoneNumberlength > KLowerSevenDigits) && (aUpperMatchLength > 0) )
+		{
+		TPtrC upperPart = buf.Left(phoneNumberlength - KLowerSevenDigits);
+		phoneNumber.iUpperDigits = TMatch::CreateHashL(upperPart,
+			aUpperMatchLength, phoneNumber.iNumUpperDigits);
+		}
+
+	// Generate a hash of the lower digits.
+	phoneNumber.iLowerSevenDigits = TMatch::CreateHashL(buf, aLowerMatchLength, phoneNumber.iNumLowerDigits);
+
+	return phoneNumber;
+	}
+
+
+/**
+Strip out any non-digit characters before we convert the phone number to an
+integer.
+
+@param aText Phone number which on return will have any non-digit characters
+removed.
+*/
+void CPplCommAddrTable::TMatch::StripOutNonDigitChars(TDes& aText)
+	{
+	for(TInt chrPos = 0; chrPos < aText.Length(); ++chrPos)
+		{
+		TChar chr = aText[chrPos];
+		if (!chr.IsDigit() )
+			{
+			aText.Delete(chrPos, 1);
+			--chrPos;
+			}
+		}
+	}
+
+
+/**
+Generates a hash value by reversing the aMatchLength least significant digits,
+ignoring non-digits and zeroes at the end of the number.  Asserts if no phone
+digits are supplied.
+
+@param aPhoneNumberString A descriptor containing a phone number.
+@param aMatchLength The number of digits from the right of the phone number to use.
+@param aNumPhoneDigits The number of digits found in the phone number string.
+
+@return An integer representation of the phone number string in reverse.
+*/
+TInt32 CPplCommAddrTable::TMatch::CreateHashL(const TDesC& aPhoneNumberString, TInt aMatchLength, TInt& aNumPhoneDigits)
+	{
+	TInt phoneNumberLength = aPhoneNumberString.Length();
+	__ASSERT_DEBUG(phoneNumberLength > 0, User::Leave(KErrNotSupported) );
+
+	TInt startIndex = 0;
+
+	if (phoneNumberLength > aMatchLength)
+		{
+		startIndex = phoneNumberLength - aMatchLength;
+		}
+
+	aNumPhoneDigits = 0;
+	TUint reversedDigits = 0;
+	TInt mult = 1;
+
+	for (TInt chrIndex = startIndex; (aNumPhoneDigits < aMatchLength) && (chrIndex < phoneNumberLength); chrIndex++)
+		{
+		TChar chr = aPhoneNumberString[chrIndex];
+		if (chr.IsDigit() )
+			{
+			reversedDigits += (chr.GetNumericValue() ) * mult;
+			mult = mult * 10;
+			++aNumPhoneDigits;
+			}
+		}
+
+	return reversedDigits ;
+	}