plugins/contacts/symbian/contactsmodel/cntplsql/src/cpplpredictivesearchtable.cpp
/*
* Copyright (c) 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 "cpcskeymap.h"
#include "cntitem.h"
#include <QStringList>
// This macro suppresses log writes
//#define NO_PRED_SEARCH_LOGS
#include "predictivesearchlog.h"
// How many characters from the beginning of the first name and last name are
// stored. This only affects how precisely the results are put in alphabetic order.
const TInt KCharactersFromName = 16;
/**
Destructor
*/
CPplPredictiveSearchTableBase::~CPplPredictiveSearchTableBase()
{
PRINT(_L("CPplPredictiveSearchTableBase dtor"));
delete iInsertStmnt;
delete iDeleteStmnt;
delete iKeyMap;
PRINT(_L("CPplPredictiveSearchTableBase dtor ends"));
}
/**
@param aItem A contact item whose data are added to the table.
*/
void CPplPredictiveSearchTableBase::CreateInDbL(CContactItem& aItem)
{
PRINT(_L("CPplPredictiveSearchTableBase::CreateInDbL"));
WriteToDbL(aItem);
PRINT(_L("CPplPredictiveSearchTableBase::CreateInDbL ends"));
}
/**
Update is done in two steps: delete contact from all predictive search tables,
then insert it into relevant tables.
@param aItem A contact item whose data is updated in the database.
*/
void CPplPredictiveSearchTableBase::UpdateL(const CContactItem& aItem)
{
PRINT(_L("CPplPredictiveSearchTableBase::UpdateL"));
TBool lowDiskErrorOccurred(EFalse);
DeleteFromAllTablesL(aItem.Id(), lowDiskErrorOccurred);
if (lowDiskErrorOccurred)
{
User::Leave(KErrGeneral);
}
WriteToDbL(aItem);
PRINT(_L("CPplPredictiveSearchTableBase::UpdateL ends"));
}
/**
Deletes a contact item from predictive search tables.
@param aItem The contact item to be deleted. It contains contact id, but not
first name or last name fields.
*/
void CPplPredictiveSearchTableBase::DeleteL(const CContactItem& aItem,
TBool& aLowDiskErrorOccurred)
{
PRINT(_L("CPplPredictiveSearchTableBase::DeleteL"));
DeleteFromAllTablesL(aItem.Id(), aLowDiskErrorOccurred);
PRINT(_L("CPplPredictiveSearchTableBase::DeleteL ends"));
}
/**
Default implementation returns empty list.
*/
QStringList CPplPredictiveSearchTableBase::GetTableSpecificFields(
const CContactItem& /*aItem*/) const
{
QStringList emptyList;
return emptyList;
}
const CPcsKeyMap* CPplPredictiveSearchTableBase::KeyMap() const
{
return iKeyMap;
}
HBufC* CPplPredictiveSearchTableBase::GetNextTableNameL(QList<QChar>& aTables) const
{
HBufC* tableName(NULL);
if (aTables.count() > 0)
{
tableName = TableNameL(aTables[0]);
aTables.removeFirst();
// PRINT1(_L("CPplPredictiveSearchTableBase::GetNextTableNameL '%S'"), tableName);
}
return tableName;
}
/**
Set up the CCntSqlStatement objects held by the class.
*/
void CPplPredictiveSearchTableBase::ConstructL()
{
PRINT(_L("CPplPredictiveSearchTableBase::ConstructL"));
// Using dummy table names here
TCntSqlStatementType insertType(EInsert, KSqlContactPredSearchTable0);
TCntSqlStatementType deleteType(EDelete, KSqlContactPredSearchTable0);
iInsertStmnt = TSqlProvider::GetSqlStatementL(insertType);
// Details of INSERT are done in subclass
const TInt KWhereContactIdBufSize(
KWhereStringEqualsStringFormatText().Size() +
KPredSearchContactId().Size() +
KPredSearchContactIdParam().Size() );
HBufC* whereContactIdClause = HBufC::NewLC(KWhereContactIdBufSize);
// for WHERE contact_id = [contact id value]
whereContactIdClause->Des().AppendFormat(KWhereStringEqualsStringFormatText,
&KPredSearchContactId, &KPredSearchContactIdParam);
// Delete information of a particular contact item
// DELETE FROM predictivesearchX (X=0..11)
// WHERE contact_id = [contact id value];
iDeleteStmnt = TSqlProvider::GetSqlStatementL(deleteType);
iDeleteStmnt->SetConditionL(*whereContactIdClause);
CleanupStack::PopAndDestroy(whereContactIdClause);
PRINT(_L("CPplPredictiveSearchTableBase::ConstructL ends"));
}
/**
Constructor
*/
CPplPredictiveSearchTableBase::CPplPredictiveSearchTableBase(
RSqlDatabase& aDatabase, TInt aMaxTokens, TInt aMaxTokenLength) :
iDatabase(aDatabase),
iMaxTokens(aMaxTokens),
iMaxTokenLength(aMaxTokenLength)
{
}
QList<QChar> CPplPredictiveSearchTableBase::DetermineTables(QStringList aTokens) const
{
QList<QChar> tables;
for (TInt i = aTokens.count() - 1; i >= 0; --i)
{
QChar ch = aTokens[i][0];
__ASSERT_ALWAYS(IsValidChar(ch),
User::Panic(_L("DetermineTables"), KErrArgument));
if (!tables.contains(ch))
{
tables.append(ch);
}
}
return tables;
}
// Insert a contact to predictive search tables.
// Write contact's all tokens to each associate pred.search table.
// E.g. if FN="11 22" LN="2 333", write "11","22","2" and "333" to tables 1, 2 and 3.
//
// Store also contacts that have a mail address beginning by an unknown character
// as the contact can still be searched by first name or last name.
void CPplPredictiveSearchTableBase::WriteToDbL(const CContactItem& aItem)
{
PRINT(_L("CPplPredictiveSearchTableBase::WriteToDbL"));
HBufC* firstNameAsNbr(NULL); // owned
HBufC* lastNameAsNbr(NULL); // owned
HBufC* firstName(NULL); // owned
HBufC* lastName(NULL); // owned
GetFieldsLC(aItem, &firstNameAsNbr, &lastNameAsNbr, &firstName, &lastName);
QStringList tokens;
QList<QChar> tables;
QT_TRYCATCH_LEAVING({
QStringList tableSpecificFields = GetTableSpecificFields(aItem);
tokens = GetTokens(tableSpecificFields, firstNameAsNbr, lastNameAsNbr);
tables = DetermineTables(tokens);
});
HBufC* tableName(NULL);
while ((tableName = GetNextTableNameL(tables)) != NULL)
{
// Takes ownership. Clears also earlier SQL statement.
iInsertStmnt->SetTableName(tableName);
RSqlStatement stmnt;
CleanupClosePushL( stmnt );
PRINT1(_L("CPplPredictiveSearchTableBase::WriteToDbL SQL='%S'"),
&iInsertStmnt->SqlStringL());
stmnt.PrepareL(iDatabase, iInsertStmnt->SqlStringL());
// TODO: while this works, it is inefficient, since the BIGINT values are
// computed in every iteration of the while-loop, even though the data is
// always the same.
FillKeyboardSpecificFieldsL(stmnt, tokens);
User::LeaveIfError(stmnt.BindInt(
User::LeaveIfError(stmnt.ParameterIndex(KPredSearchContactIdParam)),
aItem.Id()));
if (firstName)
{
User::LeaveIfError(stmnt.BindText(
User::LeaveIfError(stmnt.ParameterIndex(KPredSearchFirstNameParam)),
*firstName));
}
if (lastName)
{
User::LeaveIfError(stmnt.BindText(
User::LeaveIfError(stmnt.ParameterIndex(KPredSearchLastNameParam)),
*lastName));
}
// PRINT(_L("CPplPredictiveSearchTableBase::WriteToDbL execute SQL statement"));
// Execute the SQL statement
User::LeaveIfError(stmnt.Exec());
CleanupStack::PopAndDestroy(&stmnt);
}
CleanupStack::PopAndDestroy(lastNameAsNbr);
CleanupStack::PopAndDestroy(lastName);
CleanupStack::PopAndDestroy(firstNameAsNbr);
CleanupStack::PopAndDestroy(firstName);
PRINT(_L("CPplPredictiveSearchTableBase::WriteToDbL ends"));
}
void CPplPredictiveSearchTableBase::GetFieldsLC(const CContactItem& aItem,
HBufC** aFirstNameAsNbr,
HBufC** aLastNameAsNbr,
HBufC** aFirstName,
HBufC** aLastName) const
{
PRINT(_L("CPplPredictiveSearchTableBase::GetFieldsLC"));
__ASSERT_ALWAYS(aFirstNameAsNbr != NULL && *aFirstNameAsNbr == NULL,
User::Leave(KErrArgument));
__ASSERT_ALWAYS(aLastNameAsNbr != NULL && *aLastNameAsNbr == NULL,
User::Leave(KErrArgument));
__ASSERT_ALWAYS(aFirstName != NULL && *aFirstName == NULL,
User::Leave(KErrArgument));
__ASSERT_ALWAYS(aLastName != NULL && *aLastName == NULL,
User::Leave(KErrArgument));
CContactItemFieldSet& fieldset = aItem.CardFields();
TInt pos = fieldset.Find(KUidContactFieldGivenName);
if (pos != KErrNotFound)
{
CContactTextField* textfield = fieldset[pos].TextStorage();
if (textfield)
{
TPtrC firstName = textfield->Text();
*aFirstName = firstName.Left(KCharactersFromName).AllocLC();
*aFirstNameAsNbr = iKeyMap->GetMappedStringL(firstName);
}
}
// If aFirstName was not pushed to cleanupstack above, do it now
if (*aFirstName == NULL)
{
CleanupStack::PushL(*aFirstName);
}
CleanupStack::PushL(*aFirstNameAsNbr);
pos = fieldset.Find(KUidContactFieldFamilyName);
if (pos != KErrNotFound)
{
CContactTextField* textfield = fieldset[pos].TextStorage();
if (textfield)
{
TPtrC lastName = textfield->Text();
*aLastName = lastName.Left(KCharactersFromName).AllocLC();
*aLastNameAsNbr = iKeyMap->GetMappedStringL(lastName);
}
}
// If aLastName was not pushed to cleanupstack above, do it now
if (*aLastName == NULL)
{
CleanupStack::PushL(*aLastName);
}
CleanupStack::PushL(*aLastNameAsNbr);
PRINT5(_L("CPplPredictiveSearchTableBase::GetFieldsLC id=%d FNnbr='%S' LNnbr='%S' FN='%S' LN='%S'"),
aItem.Id(),
*aFirstNameAsNbr ? *aFirstNameAsNbr : &KNullDesC,
*aLastNameAsNbr ? *aLastNameAsNbr : &KNullDesC,
*aFirstName ? *aFirstName : &KNullDesC,
*aLastName ? *aLastName: &KNullDesC);
}
// 1. get first token of LN
// 2. get first token of FN
// 3. get second token of LN
// 4. get second token of FN
// :
// :
// If LN or FN runs out of tokens before maximum amount of tokens have been found,
// keep getting tokens from the other field.
QStringList
CPplPredictiveSearchTableBase::GetTokens(QStringList aNonTokenizedFields,
HBufC* aFirstName,
HBufC* aLastName) const
{
PRINT2(_L("CPplPredictiveSearchTableBase::GetTokens FN='%S',LN='%S'"),
aFirstName ? aFirstName : &KNullDesC,
aLastName ? aLastName : &KNullDesC);
QStringList tokens;
while (tokens.count() < iMaxTokens && !aNonTokenizedFields.isEmpty())
{
GetNextToken(aNonTokenizedFields, tokens);
}
QStringList firstNameTokens;
QStringList lastNameTokens;
AddTokens(aFirstName, firstNameTokens);
AddTokens(aLastName, lastNameTokens);
while (tokens.count() < iMaxTokens &&
(!firstNameTokens.isEmpty() || !lastNameTokens.isEmpty()))
{
GetNextToken(lastNameTokens, tokens);
GetNextToken(firstNameTokens, tokens);
}
PRINT1(_L("CPplPredictiveSearchTableBase::GetTokens found %d tokens"),
tokens.count());
return tokens;
}
// Ignore tokens beginning with invalid (unknown) character.
// Keep duplicate tokens to support e.g. search "202" when both FN and LN are "23".
void
CPplPredictiveSearchTableBase::AddTokens(HBufC* aString, QStringList& aTokens) const
{
if (aString)
{
QString s((QChar*)aString->Ptr(), aString->Length());
QStringList tokens = s.split(iKeyMap->Separator(), QString::SkipEmptyParts);
// Select tokens in the same order they are in original aString
for (TInt i = 0; i < tokens.count(); ++i)
{
if (IsValidChar(tokens[i][0]))
{
aTokens.append(tokens[i]);
}
}
}
}
void CPplPredictiveSearchTableBase::GetNextToken(QStringList& aSource,
QStringList& aDestination) const
{
if (!aSource.isEmpty() && aDestination.count() < iMaxTokens)
{
QString padded = aSource[0].left(iMaxTokenLength);
aDestination.append(padded);
aSource.removeFirst();
}
}
void
CPplPredictiveSearchTableBase::DeleteFromAllTablesL(TContactItemId aContactId,
TBool& aLowDiskErrorOccurred) const
{
QList<QChar> tables;
QT_TRYCATCH_LEAVING(tables = FillAllTables());
HBufC* tableName(NULL);
while ((tableName = GetNextTableNameL(tables)) != NULL)
{
iDeleteStmnt->SetTableName(tableName); // Clears also earlier SQL statement
RSqlStatement stmnt;
CleanupClosePushL(stmnt);
PRINT1(_L("CPplPredictiveSearchTableBase::DeleteFromAllTablesL SQL='%S'"),
&iDeleteStmnt->SqlStringL());
stmnt.PrepareL(iDatabase, iDeleteStmnt->SqlStringL());
// Contact id was not added with iDeleteStmnt->SetParamL() so it can not be
// accessed with iDeleteStmnt->ParameterIndex().
// It is the first and only parameter in query
const TInt KContactIdParamIndex(KFirstIndex);
User::LeaveIfError(stmnt.BindInt(KContactIdParamIndex, aContactId));
// Returns the amount of affected rows. As contact is not present each
// table, some operations return 0, it is not an error.
TInt status = stmnt.Exec();
#if defined(WRITE_PRED_SEARCH_LOGS)
if (status != 0)
{
PRINT1(_L(" rows deleted=%d"), status);
}
#endif
CleanupStack::PopAndDestroy(&stmnt);
if (status == KErrDiskFull)
{
PRINT(_L("CPplPredictiveSearchTableBase::DeleteFromAllTablesL disk full"));
aLowDiskErrorOccurred = ETrue;
}
else
{
User::LeaveIfError(status);
}
}
}