plugins/contacts/symbian/contactsmodel/cntplsql/src/cntsqlsearch.cpp
author Dremov Kirill (Nokia-D-MSW/Tampere) <kirill.dremov@nokia.com>
Mon, 04 Oct 2010 01:37:06 +0300
changeset 5 603d3f8b6302
parent 0 876b1a06bc25
permissions -rw-r--r--
Revision: 201037 Kit: 201039

/*
* Copyright (c) 2010 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: Retrieves the character map for each of the numeric keys.
*/
#include <QStringList>

#include "cntsqlsearch.h"
#include "cqwertykeymap.h"
#include "c12keykeymap.h"
#include <QHash>
#include <QLocale>

const char KLimitLength = 15;
const int KTwoTokens = 2;
const int KOneToken = 1;
const char KLowerLimitPadding = '0';
const char KUpperLimitPadding = 'F';
const int KMinimumSearchPatternLength = 1;
const int KHexadecimalBase = 16;


#define ORDER_BY_FIRSTNAME_LASTNAME " ORDER BY first_name, last_name ASC;"
#define SELECT_CONTACT_ID			"SELECT contact_id FROM "

// TODO: Since the column names are repeated several times, replace them with
// shorter names like w, x, y & z. Also replace contact_id by id etc.

// Predictive search table's columns
const QString KColumn1 = "nbr";
const QString KColumn2 = "nbr2";
const QString KColumn3 = "nbr3";
const QString KColumn4 = "nbr4";

// Predictive search qwert table's columns
const QString KQm1 = "n";
const QString KQm2 = "n2";
const QString KQm3 = "n3";
const QString KQm4 = "n4";
const QString KQm5 = "n5";
const QString KQm6 = "n6";
const QString KQm7 = "n7";

// Special handling for characters that originate from * and # keys
const QChar KStarChar('*');
const QChar KPlusChar('+');
const QChar KPChar('p');
const QChar KWChar('w');
const QChar KHashChar('#');

//Predictive search table
const QString QwertyTableName = "qm";

CntSqlSearch::CntSqlSearch( const CPcsKeyMap& twelveKeyKeyMap,
                            const CPcsKeyMap& qertyKeyMap ) 
        
    : mkeyKeyMap( static_cast<const C12keyKeyMap*>(&twelveKeyKeyMap) ),
      mQertyKeyMap( static_cast<const CQwertyKeyMap*>(&qertyKeyMap) )
    {
    }

CntSqlSearch::~CntSqlSearch()
    {
    }

// Basic cases:
// 1: "0", "5"
// Just one digit. Select all contact ids from the table. No need to compare
// values.
//
// 2: "123", "01", "10", "00"
// No zeros which have non-zeros in their both sides
// One or zero tokens, when pattern is split using '0'.
//
// 3: "101", "1001"
// Same digit of both sides of the zero
// Two tokens, each with length of 1 and tokens are the same.
// The queries of case 3 could also be handled with case 4 query, but that
// would yield a longer SQL statement.
//
// 4: "102", "1002"
// One or more zeros in the middle, just one digit on either side of the zero(s)
// and those digits are not the same.
// Two tokens, each with length of 1 and tokens are different.
//
// 5: "1023", "1010", "00100200", "10203", "01020304050"
// Two tokens, at least one of them has length > 1.
// If tokens are identical, handle as case 3, otherwise handle as case 4.
// ("10203" -> tokens "1" and "203"
//  "0010023004560" -> tokens "001" and "23004560")
//
// 6: "10", "1000"
// One token, ends with zero.
// In this case, query should look-up first toke and number ("10", "1000").
//
// 7: "0102"
// Same case 5, but first zero is remover in order to get more matches. 
// e.g. 0102 is 01 AND 2 or 1 AND 2.
//
QString CntSqlSearch::CreatePredictiveSearch(const QString &pattern)
	{
	int len = pattern.length();
	QString newPattern;
	if (isQwerty(pattern))
	    {
        return CreateQwertyQuery(pattern);
	    }
	else
	    {
        newPattern = ChangeStringPadings(pattern);
        // For best performance, handle 1 digit case first
        if (len == KMinimumSearchPatternLength)
            {
            // Case 1
            return SELECT_CONTACT_ID + SelectTable(newPattern) + ORDER_BY_FIRSTNAME_LASTNAME;
            }
        if (len <= KLimitLength && len > KMinimumSearchPatternLength)
            {
            return CreateQuery(newPattern);
            }
        return QString(""); // Invalid pattern
        }
	}
QString CntSqlSearch::selectQweryTable(const QString &pattern) const
    {
    QString tableNumber; 
    if(pattern.length() > 0)
        {
        return QwertyTableName + tableNumber.setNum(mQertyKeyMap->MapKeyNameToValue(pattern[0]));
        }
    else
        {
        return QString("");
        }
    }
QString CntSqlSearch::SelectTable(const QString &pattern) const
	{
        QString predictivesearch;
        QStringList tokens = GetTokens(pattern);
        bool ok;
        if (pattern.length() == 0)
                {
                return "";
                }
        QString firstNumber(pattern.at(0));
        uint hex = firstNumber.toUInt(&ok, 16);
        if (!ok)
            {
            // TODO: handle error (=invalid characters in pattern)
            }
        switch (hex)
            {
            case 0:
                {
                predictivesearch = QString("predictivesearch0");
                }
            break;
            case 1:
                {
                predictivesearch = QString("predictivesearch1");
                }
            break;
            case 2:
                {
                predictivesearch = QString("predictivesearch2");
                }
            break;
            case 3:
                {
                predictivesearch = QString("predictivesearch3");
                }
            break;
            case 4:
                {
                predictivesearch = QString("predictivesearch4");
                }
            break;
            case 5:
                {
                predictivesearch = QString("predictivesearch5");
                }
            break;
            case 6:
                {
                predictivesearch = QString("predictivesearch6");
                }
            break;
            case 7:
                {
                predictivesearch = QString("predictivesearch7");
                }
            break;
            case 8:
                {
                predictivesearch = QString("predictivesearch8");
                }
            break;
            case 9:
                {
                predictivesearch = QString("predictivesearch9");
                }
             break;
            case 10:
                {
                predictivesearch = QString("predictivesearch10");
                }
            break;
            case 11:
                {
                predictivesearch = QString("predictivesearch11");
                }
            break;
                    default: // error
                            predictivesearch = "";
                            break;
            }
            return predictivesearch;
	}

// Even if there are over 2 tokens, make 2 tokens.
// If there are two or more tokens, include the leading zeros in the first
// token the and trailing zeros in the second token.
// E.g. "0010230" results tokens "001" and "230" and
// "001230045067800900" tokens "00123" and "45067800900".
QStringList CntSqlSearch::GetTokens(const QString& pattern) const
    {
    const QChar KZero('0');
    QStringList tokens = pattern.split(KZero, QString::SkipEmptyParts);
    if (tokens.count() < KTwoTokens)
        {
        return tokens;
        }

    QStringList twoTokens;
    int i(0);
    while (pattern[i] == KZero) // Skip leading zeros
        {
        ++i;
        }
    while (pattern[i] != KZero) // Skip non-zeros to find where first token ends
        {
        ++i;
        }
    twoTokens.append(pattern.left(i));
    
    while (pattern[i] == KZero) // Skip zeros to find where second token begins
        {
        ++i;
        }
    twoTokens.append(pattern.mid(i));
    return twoTokens;
    }

QString CntSqlSearch::CreateQwertyQuery(const QString& pattern) const
    {
    QStringList qwertyString; 
    qwertyString = qwertyTokens(pattern);
    if(qwertyString.count() == 1 )
        {
        if(qwertyString.at(0).length() == 1)
            {
            return SELECT_CONTACT_ID + selectQweryTable(qwertyString.at(0)) + ORDER_BY_FIRSTNAME_LASTNAME;
            }
        else if (qwertyString.at(0).length() > 1)
            {
            return ExactMatchSearchQwerty(qwertyString.at(0)) + ORDER_BY_FIRSTNAME_LASTNAME;
            }
        else
            {
            //Empty string
            return QString(""); 
            }
        }
    else
        {
        return SearchTokensFromOneTable(pattern, qwertyString, CntSqlSearch::QwertyEmail);
        }
    }
// pattern length is between KMinimumSearchPatternLength...KLimitLength
QString CntSqlSearch::CreateQuery(const QString& pattern) const
	{
	QStringList tokens = GetTokens(pattern);
	if (TestPattern(pattern, CntSqlSearch::ZeroIsFirstNumber))
        {
        return CompareTwoColumnsWithModifiedPattern(pattern, tokens);  // Case 7
        }
	else if(tokens.count() < KTwoTokens)
        {
	    if (TestPattern(pattern, CntSqlSearch::ZerosEndOfFirstToken))
            {
            return TwoDifferentTokensSearch(pattern, tokens);  // Case 6
            }
        else
            {
            return ExactMatchSearch(pattern) + Order(tokens); // Case 2
            }
        }
	else
        {
        if (tokens.at(0) == tokens.at(1))
            {
            return IdenticalTokensSearch(pattern, tokens); // Case 3
            }
        else
            {
            return IntersectionSearch(pattern, tokens); // Case 4 or 5 first token start with multible zeros.
            }
        }
	}

QString CntSqlSearch::ExactMatchSearch(const QString& pattern) const
	{
	return QString(SELECT_CONTACT_ID + SelectTable(pattern) +
		           " WHERE " + ExactMatch(pattern)); 
	}

QString CntSqlSearch::ExactMatchSearchQwerty(const QString& pattern) const
    {
    return QString(SELECT_CONTACT_ID + selectQweryTable(pattern) +
                   " WHERE " + ExactMatchQwerty(pattern)); 
    }

// It has been checked that tokens are different, but they might begin with
// the same digit.
QString CntSqlSearch::IntersectionSearch(const QString& pattern,
									     const QStringList& tokens) const
	{
#if defined(SEARCH_FROM_ONE_TABLE)
	return SearchTokensFromOneTable(pattern, tokens);
#else
/* Query for pattern = "205":
SELECT predictivesearch2.contact_id FROM predictivesearch2 WHERE EXISTS
(SELECT contact_id FROM predictivesearch5 WHERE predictivesearch2.contact_id = predictivesearch5.contact_id)
OR
(SELECT contact_id FROM predictivesearch2 
WHERE(predictivesearch2.nbr>145522562959409152 AND predictivesearch2.nbr<145804037936119807) OR
(predictivesearch2.nbr2>145522562959409152 AND predictivesearch2.nbr2<145804037936119807) OR
(predictivesearch2.nbr3>145522562959409152 AND predictivesearch2.nbr3<145804037936119807) OR
(predictivesearch2.nbr4>145522562959409152 AND predictivesearch2.nbr4<145804037936119807))
ORDER BY predictivesearch2.first_name, predictivesearch2.last_name ASC;

This query works if both tokens have just one digit (e.g. "102", but not "1023")
*/	
	if (tokens.at(0).length() == KMinimumSearchPatternLength &&
		tokens.at(1).length() == KMinimumSearchPatternLength) 
		{
		// Case 4
		QString firstTable = SelectTable(tokens.at(0));
		QString secondTable = SelectTable(tokens.at(1));
		QString query =
			"SELECT " + firstTable + ".contact_id FROM " + firstTable +
			" WHERE EXISTS (" + SELECT_CONTACT_ID + secondTable + " WHERE " +
			firstTable + ".contact_id = " + secondTable + ".contact_id) OR (" +
			SELECT_CONTACT_ID + firstTable + " WHERE " + ExactMatch(pattern, firstTable) + ")";
		return query  + Order(tokens);
		}
	if (tokens.at(0).at(0) == tokens.at(1).at(0) ||
	    tokens.at(0).length() > 1 && tokens.at(1).length() > 1)
		{
		// Tokens begin with same digit or both tokens are longer than one digit.
		// Must search from one table.
		return SearchTokensFromOneTable(pattern, tokens);
		}
	return CreateJoinTableSearch(pattern, tokens); // Case 5
#endif
	}

// Find the exact match, or a column whose value is within
// lower..upper(exclusive) and another column whose value is within
// lower2..upper2(exclusive).
// In this case the limits are different, so there are 12 combinations the two
// values can exist in four columns:
// 
// (column = X  AND column2 = Y) OR
// (column = X  AND column3 = Y) OR
// (column = X  AND column4 = Y) OR
// (column2 = X AND column3 = Y) OR
// (column2 = X AND column4 = Y) OR
// (column3 = X AND column4 = Y) OR
// (column = Y  AND column2 = X) OR
// (column = Y  AND column3 = X) OR
// (column = Y  AND column4 = X) OR
// (column2 = Y AND column3 = X) OR
// (column2 = Y AND column4 = X) OR
// (column3 = Y AND column4 = X)
//
//
// Qwert case
// Where X means: (value > lower-limit AND value < upper-limit)
// and Y means: (value > lower-limit-2 AND value < upper-limit-2)
QString CntSqlSearch::SearchTokensFromOneTable(const QString& pattern,
											   const QStringList& tokens,
											   QueryType queryType) const
    {
    QString token;
    QString lower;
    QString upper;
    QString lower2;
    QString upper2;
    int err;
    
    if(queryType == CntSqlSearch::TwelveTable)
        {
        err = mkeyKeyMap->GetNumericLimits(tokens.at(0), lower, upper);
        if(err)
            {
            return QString("");
            }
        err = mkeyKeyMap->GetNumericLimits(tokens.at(1), lower2, upper2);
        if(err)
            {
            return QString("");
            }
        }
    else
        {
        err = mQertyKeyMap->GetNumericLimits(tokens.at(0), lower, upper);
        err = mQertyKeyMap->GetNumericLimits(tokens.at(1), lower2, upper2);
        if(err)
            {
            return QString("");
            }
        }
    QString query; 
    if (queryType == CntSqlSearch::TwelveTable)
		{
        query = SELECT_CONTACT_ID + SelectTable(pattern) + " WHERE NOT(NOT" +
	    ExactMatch(pattern) + " AND NOT" +
		CompareTwoColumns(lower, upper, lower2, upper2) + " AND NOT" +
        CompareTwoColumns(lower2, upper2, lower, upper) + ")";
		}
    else
        {
    
        query = SELECT_CONTACT_ID + selectQweryTable(tokens.at(0)) + " WHERE NOT(NOT" +
        ExactMatchQwerty(pattern) + " AND NOT" +
        CompareTwoQwertyColumns(lower, upper, lower2, upper2) + " AND NOT" +
        CompareTwoQwertyColumns(lower2, upper2, lower, upper) + ")";
        }
	query += Order(tokens, queryType);
	return query;
	}

// Either an exact match is required, or tokens must be found, but not in the
// same column.
// Since tokens are identical, they have same limits, and one call to
// CompareTwoColumns() is enough.
QString CntSqlSearch::IdenticalTokensSearch(const QString& pattern,
										    const QStringList& tokens) const
	{
	QString token = tokens.at(0);
	QString lower;
	QString upper;
	
	TInt err = mkeyKeyMap->GetNumericLimits(token, lower, upper);
	    
#if defined(USE_DEMORGAN)
	QString query(SELECT_CONTACT_ID + SelectTable(pattern) + " WHERE NOT(NOT" +
	    ExactMatch(pattern) +
        " AND NOT" + CompareTwoColumns(lower, upper, lower, upper) + ")");
#else
	QString query(SELECT_CONTACT_ID + SelectTable(pattern) + " WHERE (" +
	    ExactMatch(pattern) +  // exact match (e.g. "101")
        ") OR " + CompareTwoColumns(lower, upper, lower, upper));
#endif
	query += Order(tokens);
	return query;
	}


QString CntSqlSearch::TwoDifferentTokensSearch(const QString& pattern, const QStringList& tokens) const
        {
        QString token = tokens.at(0);
        /*QString sortPatern = pattern;
        sortPatern.truncate(pattern.length()-1);*/
#if defined(USE_DEMORGAN)
        QString query(SELECT_CONTACT_ID + SelectTable(pattern) + " WHERE NOT(NOT" +
            ExactMatch(token) +
        " AND NOT" + ExactMatch(pattern) + ")");
#else
        QString query(SELECT_CONTACT_ID + SelectTable(pattern) + " WHERE (" +
            ExactMatch(token) +  // exact match (e.g. "2")
        ") OR " + ExactMatch(pattern)); // exact match (e.g. "20")
#endif
        query += Order(tokens);
        return query;
        }
/*Modified token is search from two different tables. 
  Patern is modified look-up contacts without first zero e.g exact match 05 or 5.
    SELECT contact_id
    FROM
    (
    SELECT predictivesearch5.contact_id, predictivesearch5.first_name, predictivesearch5.last_name FROM predictivesearch5
        UNION 
        SELECT predictivesearch0.contact_id, predictivesearch0.first_name, predictivesearch0.last_name FROM predictivesearch0
        WHERE ((NOT(NOT(predictivesearch0.nbr>22517998136852479 AND predictivesearch0.nbr<27021597764222976) AND NOT(predictivesearch0.nbr2>22517998136852479 AND predictivesearch0.nbr2<27021597764222976) AND NOT(predictivesearch0.nbr3>22517998136852479 AND predictivesearch0.nbr3<27021597764222976) AND NOT(predictivesearch0.nbr4>22517998136852479 AND predictivesearch0.nbr4<27021597764222976)))) 
    ) AS PR
    ORDER BY PR.first_name, PR.last_name ASC;
    
    - AND case - 
    SELECT contact_id FROM (
    SELECT predictivesearch5.contact_id, predictivesearch5.first_name, predictivesearch5.last_name FROM predictivesearch5 WHERE NOT(NOT(NOT(NOT(nbr>382805968326492159 AND nbr<387309567953862656) AND NOT(nbr2>382805968326492159 AND nbr2<387309567953862656) AND NOT(nbr3>382805968326492159 AND nbr3<387309567953862656) AND NOT(nbr4>382805968326492159 AND nbr4<387309567953862656))) AND NOT(NOT(NOT(nbr2>360287970189639679 AND nbr2<432345564227567616 AND nbr>382805968326492159 AND nbr<387309567953862656) AND NOT(nbr3>360287970189639679 AND nbr3<432345564227567616 AND nbr>382805968326492159 AND nbr<387309567953862656) AND NOT(nbr3>360287970189639679 AND nbr3<432345564227567616 AND nbr2>382805968326492159 AND nbr2<387309567953862656) AND NOT(nbr4>360287970189639679 AND nbr4<432345564227567616 AND nbr>382805968326492159 AND nbr<387309567953862656) AND NOT(nbr4>360287970189639679 AND nbr4<432345564227567616 AND nbr2>382805968326492159 AND nbr2<387309567953862656) AND NOT(nbr4>360287970189639679 AND nbr4<432345564227567616 AND nbr3>382805968326492159 AND nbr3<387309567953862656))) AND NOT(NOT(NOT(nbr2>382805968326492159 AND nbr2<387309567953862656 AND nbr>360287970189639679 AND nbr<432345564227567616) AND NOT(nbr3>382805968326492159 AND nbr3<387309567953862656 AND nbr>360287970189639679 AND nbr<432345564227567616) AND NOT(nbr3>382805968326492159 AND nbr3<387309567953862656 AND nbr2>360287970189639679 AND nbr2<432345564227567616) AND NOT(nbr4>382805968326492159 AND nbr4<387309567953862656 AND nbr>360287970189639679 AND nbr<432345564227567616) AND NOT(nbr4>382805968326492159 AND nbr4<387309567953862656 AND nbr2>360287970189639679 AND nbr2<432345564227567616) AND NOT(nbr4>382805968326492159 AND nbr4<387309567953862656 AND nbr3>360287970189639679 AND nbr3<432345564227567616)))) 
    UNION
    SELECT predictivesearch0.contact_id, predictivesearch0.first_name, predictivesearch0.last_name FROM predictivesearch0 WHERE ((NOT(NOT(predictivesearch0.nbr>23930870578544639 AND predictivesearch0.nbr<23931970090172416) AND NOT(predictivesearch0.nbr2>23930870578544639 AND predictivesearch0.nbr2<23931970090172416) AND NOT(predictivesearch0.nbr3>23930870578544639 AND predictivesearch0.nbr3<23931970090172416) AND NOT(predictivesearch0.nbr4>23930870578544639 AND predictivesearch0.nbr4<23931970090172416)))) AND NOT(NOT(NOT(nbr2>360287970189639679 AND nbr2<432345564227567616 AND nbr>0387309567953862656382805968326492159 AND nbr<0387309567953862656382805968326492159) AND NOT(nbr3>360287970189639679 AND nbr3<432345564227567616 AND nbr>0387309567953862656382805968326492159 AND nbr<0387309567953862656382805968326492159) AND NOT(nbr3>360287970189639679 AND nbr3<432345564227567616 AND nbr2>0387309567953862656382805968326492159 AND nbr2<0387309567953862656382805968326492159) AND NOT(nbr4>360287970189639679 AND nbr4<432345564227567616 AND nbr>0387309567953862656382805968326492159 AND nbr<0387309567953862656382805968326492159) AND NOT(nbr4>360287970189639679 AND nbr4<432345564227567616 AND nbr2>0387309567953862656382805968326492159 AND nbr2<0387309567953862656382805968326492159) AND NOT(nbr4>360287970189639679 AND nbr4<432345564227567616 AND nbr3>0387309567953862656382805968326492159 AND nbr3<0387309567953862656382805968326492159))) AND NOT(NOT(NOT(nbr2>0387309567953862656382805968326492159387309567953862656382805968326492159 AND nbr2<0387309567953862656382805968326492159387309567953862656382805968326492159 AND nbr>360287970189639679 AND nbr<432345564227567616) AND NOT(nbr3>0387309567953862656382805968326492159387309567953862656382805968326492159 AND nbr3<0387309567953862656382805968326492159387309567953862656382805968326492159 AND nbr>360287970189639679 AND nbr<432345564227567616) AND NOT(nbr3>0387309567953862656382805968326492159387309567953862656382805968326492159 AND nbr3<0387309567953862656382805968326492159387309567953862656382805968326492159 AND nbr2>360287970189639679 AND nbr2<432345564227567616) AND NOT(nbr4>0387309567953862656382805968326492159387309567953862656382805968326492159 AND nbr4<0387309567953862656382805968326492159387309567953862656382805968326492159 AND nbr>360287970189639679 AND nbr<432345564227567616) AND NOT(nbr4>0387309567953862656382805968326492159387309567953862656382805968326492159 AND nbr4<0387309567953862656382805968326492159387309567953862656382805968326492159 AND nbr2>360287970189639679 AND nbr2<432345564227567616) AND NOT(nbr4>0387309567953862656382805968326492159387309567953862656382805968326492159 AND nbr4<0387309567953862656382805968326492159387309567953862656382805968326492159 AND nbr3>360287970189639679 AND nbr3<432345564227567616)))
    )AS PR ORDER BY PR.first_name, PR.last_name ASC;
    */

QString CntSqlSearch::CompareTwoColumnsWithModifiedPattern(const QString& pattern,
                                                                  const QStringList& tokens) const
    {
    QString queryString;
    QString lower;
    QString upper;
    QString lower2;
    QString upper2;
    QString lower_without_zero;
    QString upper_without_zero;
    QString lower2_without_zero;
    QString upper2_without_zero;
    int err;
    int i(0);
    
    QString firstTokenWithoutZeros = tokens.at(0);
    firstTokenWithoutZeros.remove(QChar('0'), Qt::CaseInsensitive);

    QString firstTable = SelectTable(pattern);
    QString secondTable = SelectTable(firstTokenWithoutZeros);
    
    QString secondTokenWithoutZeros;
    
    if(tokens.count() > 1)
        {
        secondTokenWithoutZeros = tokens.at(1);
        i = pattern.length()-1;
        while (pattern[i] == '0') 
               {
               --i;
               }
        if(pattern.length()-1 != i)
            {
            pattern.leftRef(i);
            }
        //secondTokenWithoutZeros.remove(QChar('0'), Qt::CaseInsensitive);
        }
    
    // Case like 05
    if (tokens.at(0).count() == 1 && pattern.length() == 2)
        {
        queryString = QString("SELECT contact_id FROM (SELECT " + secondTable + ".contact_id, " + secondTable + ".first_name, " + secondTable + ".last_name FROM " + secondTable 
                                + " UNION SELECT " + firstTable + ".contact_id, " + firstTable + ".first_name, " + firstTable + ".last_name FROM " + firstTable 
                                + " WHERE " + ModifiedMatchColumns( pattern) + ") AS PR ORDER BY PR.first_name, PR.last_name ASC;");
        }
    //case like 05055 or 0506 or 00506 
    else if (tokens.count() > 1)
        { 
        err = mkeyKeyMap->GetNumericLimits(tokens.at(0), lower, upper);
        if(err)
            {
            return QString("");
            }
        err = mkeyKeyMap->GetNumericLimits(tokens.at(1), lower2, upper2);
        if(err)
            {
            return QString("");
            }
        
        err = mkeyKeyMap->GetNumericLimits(firstTokenWithoutZeros, lower_without_zero, upper_without_zero);
        if(err)
            {
            return QString("");
            }
        
        err = mkeyKeyMap->GetNumericLimits(secondTokenWithoutZeros, lower2_without_zero, upper2_without_zero);
        if(err)
           {
           return QString("");
           }

        queryString = QString("SELECT contact_id FROM (SELECT " + secondTable + ".contact_id, " + secondTable + ".first_name, " + secondTable + ".last_name FROM " + secondTable + 
                                   + " WHERE (" + CompareTwoColumns(lower_without_zero, upper_without_zero, lower2_without_zero, upper2_without_zero) + " OR" +
                                   CompareTwoColumns(lower2_without_zero, upper2_without_zero, lower_without_zero, upper_without_zero) + ")" +
                              " UNION" +
                                   " SELECT " + firstTable + ".contact_id, " + firstTable + ".first_name, " + firstTable + ".last_name FROM " + firstTable 
                                   + " WHERE " + ModifiedMatchColumns( pattern) + " OR"
                                   + CompareTwoColumns(lower, upper, lower2, upper2) + " OR"
                                   + CompareTwoColumns(lower2, upper2, lower, upper) +
                              ") AS PR ORDER BY PR.first_name, PR.last_name ASC;");
            
        
            
            /* "SELECT contact_id FROM ( SELECT... UNION SELECT ...) AS PR ORDER BY PR.first_name, PR.last_name ASC;*/
            /* 5 table 
             queryString = QString("SELECT " + secondTable + ".contact_id, " + secondTable + ".first_name, " + secondTable + ".last_name FROM " + secondTable 
                                                + " WHERE NOT(NOT" + ExactMatch(tokens.at(0)) + " AND NOT" +
                                               CompareTwoColumns(lower, upper, lower2, upper2) + " AND NOT" +
                                               CompareTwoColumns(lower2, upper2, lower, upper) + " );");*/
        
            /* table 0 queryString = QString("SELECT " + firstTable + ".contact_id, " + firstTable + ".first_name, " + firstTable + ".last_name FROM " + firstTable 
                                               + " WHERE " + ModifiedMatchColumns( pattern) + " AND NOT"
                                               + CompareTwoColumns(zeroPrefix.append(lower), zeroPrefix.append(upper), lower2, upper2) + " AND NOT"
                                               + CompareTwoColumns(lower2, upper2, zeroPrefix.append(lower), zeroPrefix.append(upper)));*/
                
        }
    else
        {
        //case like 055
        queryString = QString("SELECT contact_id FROM (SELECT " + secondTable + ".contact_id, " + secondTable + ".first_name, " + secondTable + ".last_name FROM " + secondTable 
                                + " WHERE " + ModifiedMatchColumns( firstTokenWithoutZeros) + 
                                + " UNION SELECT " + firstTable + ".contact_id, " + firstTable + ".first_name, " + firstTable + ".last_name FROM " + firstTable 
                                + " WHERE " + ModifiedMatchColumns( pattern) + ") AS PR ORDER BY PR.first_name, PR.last_name ASC;");
        }
    return queryString;
    }

QString CntSqlSearch::ModifiedMatchColumns(const QString& pattern) const
    {
    return "(" + ExactMatch(pattern, SelectTable(pattern)) +  ")";
    }

// Put individual AND / OR operations in such order that in most cases there is
// no need to evaluate all arguments of the AND / OR.
// In case of AND, put the less likely condition on the left side of AND.
// In case of OR, put the more likely condition on the left side of OR.
// Since 2nd column is more likely to be empty, compare it before 1st column.
// Since 1st & 2nd columns are more likely to contain a match, compare them
// before other column combinations (1st & 3rd, 2nd & 3rd etc)
QString CntSqlSearch::CompareTwoColumns(const QString& lower,
										const QString& upper,
										const QString& lower2,
										const QString& upper2) const
	{
#if defined(USE_DEMORGAN)
	// Using De Morgan's law to replace ORs with ANDs:
	// A || B || C || D || E || F --> !(!A && !B && !C && !D && !E && !F)
	//
	// As A (match found in columns 1 and 2) is more likely true than other
	// combinations, then !A is more likely false than other combinations, so
	// it is put first in the AND statement.	
	QString query =
		"(NOT(NOT(" +
		// 2nd & 1st column (='A')
		KColumn2 + ">" + lower2 + " AND " + KColumn2 + "<" + upper2 + " AND " +
		KColumn1 + ">" + lower + " AND " + KColumn1 + "<" + upper +
		") AND NOT(" +
		// 3nd & 1st column (='B')
		KColumn3 + ">" + lower2 + " AND " + KColumn3 + "<" + upper2 + " AND " +
		KColumn1 + ">" + lower + " AND " + KColumn1 + "<" + upper +
		") AND NOT(" +
		// 3rd & 2nd column (='C')
		KColumn3 + ">" + lower2 + " AND " + KColumn3 + "<" + upper2 + " AND " +
		KColumn2 + ">" + lower + " AND " + KColumn2 + "<" + upper +
		") AND NOT(" +
		// 4th & 1st column (='D')
		KColumn4 + ">" + lower2 + " AND " + KColumn4 + "<" + upper2 + " AND " +
		KColumn1 + ">" + lower + " AND " + KColumn1 + "<" + upper +
		") AND NOT(" +
		// 4th & 2nd column (='E')
		KColumn4 + ">" + lower2 + " AND " + KColumn4 + "<" + upper2 + " AND " +
		KColumn2 + ">" + lower + " AND " + KColumn2 + "<" + upper +
		") AND NOT(" +
		// 4th & 3rd column (='F')
		KColumn4 + ">" + lower2 + " AND " + KColumn4 + "<" + upper2 + " AND " +
		KColumn3 + ">" + lower + " AND " + KColumn3 + "<" + upper + ")))";
#else
	QString query =
		"(" +
		// 2nd & 1st column
		KColumn2 + ">" + lower2 + " AND " + KColumn2 + "<" + upper2 + " AND " +
		KColumn1 + ">" + lower + " AND " + KColumn1 + "<" + upper +
		") OR (" +
		// 3nd & 1st column
		KColumn3 + ">" + lower2 + " AND " + KColumn3 + "<" + upper2 + " AND " +
		KColumn1 + ">" + lower + " AND " + KColumn1 + "<" + upper +
		") OR (" +
		// 3rd & 2nd column
		KColumn3 + ">" + lower2 + " AND " + KColumn3 + "<" + upper2 + " AND " +
		KColumn2 + ">" + lower + " AND " + KColumn2 + "<" + upper +
		") OR (" +
		// 4th & 1st column
		KColumn4 + ">" + lower2 + " AND " + KColumn4 + "<" + upper2 + " AND " +
		KColumn1 + ">" + lower + " AND " + KColumn1 + "<" + upper +
		") OR (" +
		// 4th & 2nd column
		KColumn4 + ">" + lower2 + " AND " + KColumn4 + "<" + upper2 + " AND " +
		KColumn2 + ">" + lower + " AND " + KColumn2 + "<" + upper +
		") OR (" +
		// 4th & 3rd column
		KColumn4 + ">" + lower2 + " AND " + KColumn4 + "<" + upper2 + " AND " +
		KColumn3 + ">" + lower + " AND " + KColumn3 + "<" + upper + ")";
#endif
	return query;
	}

QString CntSqlSearch::CompareTwoQwertyColumns(const QString& lower,
                                        const QString& upper,
                                        const QString& lower2,
                                        const QString& upper2) const
    {
    // Using De Morgan's law to replace ORs with ANDs:
    // A || B || C || D || E || F --> !(!A && !B && !C && !D && !E && !F)
    //
    // As A (match found in columns 1 and 2) is more likely true than other
    // combinations, then !A is more likely false than other combinations, so
    // it is put first in the AND statement.    
    QString query =
        "(NOT(NOT(" +
        // 2nd & 1st column (='A')
        KQm2 + ">" + lower2 + " AND " + KQm2 + "<" + upper2 + " AND " +
        KQm1 + ">" + lower + " AND " + KQm1 + "<" + upper +
        ") AND NOT(" +
        // 3nd & 1st column (='B')
        KQm3 + ">" + lower2 + " AND " + KQm3 + "<" + upper2 + " AND " +
        KQm1 + ">" + lower + " AND " + KQm1 + "<" + upper +
        ") AND NOT(" +
        // 3rd & 2nd column (='C')
        KQm3 + ">" + lower2 + " AND " + KQm3 + "<" + upper2 + " AND " +
        KQm2 + ">" + lower + " AND " + KQm2 + "<" + upper +
        ") AND NOT(" +
        // 4th & 1st column (='D')
        KQm4 + ">" + lower2 + " AND " + KQm4 + "<" + upper2 + " AND " +
        KQm1 + ">" + lower + " AND " + KQm1 + "<" + upper +
        ") AND NOT(" +
        // 4th & 2nd column (='E')
        KQm4 + ">" + lower2 + " AND " + KQm4 + "<" + upper2 + " AND " +
        KQm2 + ">" + lower + " AND " + KQm2 + "<" + upper +
        ") AND NOT(" +
        // 4th & 3rd column (='F')
        KQm4 + ">" + lower2 + " AND " + KQm4 + "<" + upper2 + " AND " +
        KQm3 + ">" + lower + " AND " + KQm3 + "<" + upper + ")))";
        // 5th & 1rd column (='G')
        KQm5 + ">" + lower2 + " AND " + KQm5 + "<" + upper2 + " AND " +
        KQm1 + ">" + lower + " AND " + KQm1 + "<" + upper + ")))";
        // 5th & 2rd column (='H')
        KQm5 + ">" + lower2 + " AND " + KQm5 + "<" + upper2 + " AND " +
        KQm2 + ">" + lower + " AND " + KQm2 + "<" + upper + ")))";
        // 5th & 3rd column (='I')
        KQm5 + ">" + lower2 + " AND " + KQm5 + "<" + upper2 + " AND " +
        KQm3 + ">" + lower + " AND " + KQm3 + "<" + upper + ")))";
        // 5th & 4rd column (='I')
        KQm5 + ">" + lower2 + " AND " + KQm5 + "<" + upper2 + " AND " +
        KQm4 + ">" + lower + " AND " + KQm4 + "<" + upper + ")))";
        // 6th & 1rd column (='J')
        KQm6 + ">" + lower2 + " AND " + KQm6 + "<" + upper2 + " AND " +
        KQm1 + ">" + lower + " AND " + KQm1 + "<" + upper + ")))";
        // 6th & 2rd column (='K')
        KQm6 + ">" + lower2 + " AND " + KQm6 + "<" + upper2 + " AND " +
        KQm2 + ">" + lower + " AND " + KQm2 + "<" + upper + ")))";
        // 6th & 3rd column (='L')
        KQm6 + ">" + lower2 + " AND " + KQm6 + "<" + upper2 + " AND " +
        KQm3 + ">" + lower + " AND " + KQm3 + "<" + upper + ")))";
        // 6th & 43rd column (='M')
        KQm6 + ">" + lower2 + " AND " + KQm6 + "<" + upper2 + " AND " +
        KQm4 + ">" + lower + " AND " + KQm4 + "<" + upper + ")))";
        // 6th & 5rd column (='M')
        KQm6 + ">" + lower2 + " AND " + KQm6 + "<" + upper2 + " AND " +
        KQm5 + ">" + lower + " AND " + KQm5 + "<" + upper + ")))";
        // 7th & 1rd column (='N')
        KQm7 + ">" + lower2 + " AND " + KQm7 + "<" + upper2 + " AND " +
        KQm1 + ">" + lower + " AND " + KQm1 + "<" + upper + ")))";
        // 7th & 1rd column (='O')
        KQm7 + ">" + lower2 + " AND " + KQm7 + "<" + upper2 + " AND " +
        KQm2 + ">" + lower + " AND " + KQm2 + "<" + upper + ")))";
        // 7th & 3rd column (='P')
        KQm7 + ">" + lower2 + " AND " + KQm7 + "<" + upper2 + " AND " +
        KQm3 + ">" + lower + " AND " + KQm3 + "<" + upper + ")))";
        // 7th & 4rd column (='Q')
        KQm7 + ">" + lower2 + " AND " + KQm7 + "<" + upper2 + " AND " +
        KQm4 + ">" + lower + " AND " + KQm4 + "<" + upper + ")))";
        // 7th & 5rd column (='Q')
        KQm7 + ">" + lower2 + " AND " + KQm7 + "<" + upper2 + " AND " +
        KQm5 + ">" + lower + " AND " + KQm5 + "<" + upper + ")))";
        // 7th & 5rd column (='R')
        KQm7 + ">" + lower2 + " AND " + KQm7 + "<" + upper2 + " AND " +
        KQm6 + ">" + lower + " AND " + KQm6 + "<" + upper + ")))";
       
    return query;
    }
QString CntSqlSearch::ExactMatch(const QString& pattern, QString table) const
    {
    QString lower;
    QString upper;
    
    int err = mkeyKeyMap->GetNumericLimits(pattern, lower, upper);
    if(err)
        {
        return QString("");
        }
    
    if (table.length() > 0)
        {
        table += ".";
        }
#if defined(USE_DEMORGAN)
	// Using De Morgan's law to replace ORs with ANDs:
	// column1 || column2 || column3 || column4
	// ->
	// (NOT(NOT(column1) AND NOT(column2) AND NOT(column3) AND NOT(column4))
	//
	// Which means:
	// (NOT(NOT(N>lower  && < N<upper)  AND NOT(N2>lower && < N2<upper) AND
	//      NOT(N3>lower && < N3<upper) AND NOT(N4>lower && < N4<upper))
	//
	// As KColumn1 is most likely to contain a match, "NOT(KColumn1)" is more
	// likely to be false than "NOT(KColumn2)" etc. So put KColumn1 first in the
	// AND statement.
	return QString("(NOT(NOT(" +
        table + KColumn1 + ">" + lower + " AND " + table + KColumn1 + "<" + upper + ") AND NOT(" +
        table + KColumn2 + ">" + lower + " AND " + table + KColumn2 + "<" + upper + ") AND NOT(" +
        table + KColumn3 + ">" + lower + " AND " + table + KColumn3 + "<" + upper + ") AND NOT(" +
        table + KColumn4 + ">" + lower + " AND " + table + KColumn4 + "<" + upper + ")))");
#else
	// Since first column has always some value, and 2nd column is more likely to
	// have a value than 3rd column etc. Put columns in that order:
	// (column 1 comparison) OR (column 2 comparison) OR (column 3 comparison) OR
	// (column 4 comparison)
	// If 1st column results true, there is no need to compare other columns etc.
    return QString("(" +
        table + KColumn1 + ">" + lower + " AND " + table + KColumn1 + "<" + upper + ") OR (" +
        table + KColumn2 + ">" + lower + " AND " + table + KColumn2 + "<" + upper + ") OR (" +
        table + KColumn3 + ">" + lower + " AND " + table + KColumn3 + "<" + upper + ") OR (" +
        table + KColumn4 + ">" + lower + " AND " + table + KColumn4 + "<" + upper + ")");
#endif
    }

QString CntSqlSearch::ExactMatchQwerty(const QString& pattern, QString table) const
    {
    QString lower;
    QString upper;
    TInt err = mQertyKeyMap->GetNumericLimits(pattern, lower, upper);
    
    if (table.length() > 0)
        {
        table += ".";
        }
#if defined(USE_DEMORGAN)
    // Using De Morgan's law to replace ORs with ANDs:
    // column1 || column2 || column3 || column4
    // ->
    // (NOT(NOT(column1) AND NOT(column2) AND NOT(column3) AND NOT(column4))
    //
    // Which means:
    // (NOT(NOT(N>lower  && < N<upper)  AND NOT(N2>lower && < N2<upper) AND
    //      NOT(N3>lower && < N3<upper) AND NOT(N4>lower && < N4<upper))
    //
    // As KColumn1 is most likely to contain a match, "NOT(KColumn1)" is more
    // likely to be false than "NOT(KColumn2)" etc. So put KColumn1 first in the
    // AND statement.
    return QString("(NOT(NOT(" +
        table + KQm1 + ">" + lower + " AND " + table + KQm1 + "<" + upper + ") AND NOT(" +
        table + KQm2 + ">" + lower + " AND " + table + KQm2 + "<" + upper + ") AND NOT(" +
        table + KQm3 + ">" + lower + " AND " + table + KQm3 + "<" + upper + ") AND NOT(" +
        table + KQm4 + ">" + lower + " AND " + table + KQm4 + "<" + upper + ") AND NOT(" +
        table + KQm5 + ">" + lower + " AND " + table + KQm5 + "<" + upper + ") AND NOT(" +
        table + KQm6 + ">" + lower + " AND " + table + KQm6 + "<" + upper + ") AND NOT(" +
        table + KQm7 + ">" + lower + " AND " + table + KQm7 + "<" + upper + ")))");
#else
    // Since first column has always some value, and 2nd column is more likely to
    // have a value than 3rd column etc. Put columns in that order:
    // (column 1 comparison) OR (column 2 comparison) OR (column 3 comparison) OR
    // (column 4 comparison)
    // If 1st column results true, there is no need to compare other columns etc.
    return QString("(" +
        table + KQm1 + ">" + lower + " AND " + table + KQm1 + "<" + upper + ") OR (" +
        table + KQm2 + ">" + lower + " AND " + table + KQm2 + "<" + upper + ") OR (" +
        table + KQm3 + ">" + lower + " AND " + table + KQm3 + "<" + upper + ") OR (" +
        table + KQm4 + ">" + lower + " AND " + table + KQm4 + "<" + upper + ") OR (" +
        table + KQm5 + ">" + lower + " AND " + table + KQm5 + "<" + upper + ") OR (" +
        table + KQm6 + ">" + lower + " AND " + table + KQm6 + "<" + upper + ") OR (" +
        table + KQm7 + ">" + lower + " AND " + table + KQm7 + "<" + upper + ")");
#endif
    }

// TODO: if SEARCH_FROM_ONE_TABLE case is slower than the one that searches from
// two tables, then this function is needed for cases where pattern is:
// "1023", "12300450"
//
/* This query makes sub-query into table 5 and searches for a number that begins
with 56606. but it does not support cases where both tokens are longer than one digit.

SELECT predictivesearch5.contact_id FROM predictivesearch5 WHERE EXISTS (SELECT contact_id FROM predictivesearch5 
WHERE (predictivesearch5.nbr>389005014883893248 AND predictivesearch5.nbr<389006114395521023) OR
(predictivesearch5.nbr2>389005014883893248 AND predictivesearch5.nbr2<389006114395521023) OR
(predictivesearch5.nbr3>389005014883893248 AND predictivesearch5.nbr3<389006114395521023) OR
(predictivesearch5.nbr4>389005014883893248 AND predictivesearch5.nbr4<389006114395521023))
OR
(SELECT predictivesearch5.contact_id FROM predictivesearch5 JOIN predictivesearch6 ON
predictivesearch5.contact_id = predictivesearch6.contact_id
WHERE(predictivesearch5.nbr>388998417814126592 AND predictivesearch5.nbr<389279892790837247) OR
(predictivesearch5.nbr2>388998417814126592 AND predictivesearch5.nbr2<389279892790837247) OR
(predictivesearch5.nbr3>388998417814126592 AND predictivesearch5.nbr3<389279892790837247) OR
(predictivesearch5.nbr4>388998417814126592 AND predictivesearch5.nbr4<389279892790837247))
ORDER BY predictivesearch5.first_name, predictivesearch5.last_name ASC;

SELECT contact_id
FROM
(
SELECT predictivesearch5.contact_id, predictivesearch5.first_name, predictivesearch5.last_name  FROM predictivesearch5
WHERE (predictivesearch5.nbr>387415121070129152 AND predictivesearch5.nbr<387432713256173567) OR
(predictivesearch5.nbr2>387415121070129152 AND predictivesearch5.nbr2<387432713256173567) OR
(predictivesearch5.nbr3>387415121070129152 AND predictivesearch5.nbr3<387432713256173567) OR
(predictivesearch5.nbr4>387415121070129152 AND predictivesearch5.nbr4<387432713256173567)
UNION
SELECT predictivesearch5.contact_id, predictivesearch5.first_name, predictivesearch5.last_name  FROM predictivesearch5 JOIN predictivesearch6 ON predictivesearch5.contact_id = predictivesearch6.contact_id
WHERE((predictivesearch5.nbr>387309567953862656 AND predictivesearch5.nbr<391813167581233151) OR (predictivesearch5.nbr2>387309567953862656 AND predictivesearch5.nbr2<391813167581233151) OR (predictivesearch5.nbr3>387309567953862656 AND predictivesearch5.nbr3<391813167581233151) OR (predictivesearch5.nbr4>387309567953862656 AND predictivesearch5.nbr4<391813167581233151)
AND
(predictivesearch6.nbr>387309567953862656 AND predictivesearch6.nbr<391813167581233151) OR (predictivesearch6.nbr2>387309567953862656 AND predictivesearch6.nbr2<391813167581233151) OR (predictivesearch6.nbr3>387309567953862656 AND predictivesearch6.nbr3<391813167581233151) OR (predictivesearch6.nbr4>387309567953862656 AND predictivesearch6.nbr4<391813167581233151))
) AS PR
ORDER BY PR.first_name, PR.last_name ASC;

Here is a De Morgan version

SELECT contact_id
FROM
(
SELECT predictivesearch5.contact_id, predictivesearch5.first_name, predictivesearch5.last_name  FROM predictivesearch5
WHERE NOT((NOT (predictivesearch5.nbr >= 387415121070129152 AND predictivesearch5.nbr <= 387432713256173567)) AND (NOT (predictivesearch5.nbr2 >= 387415121070129152 AND predictivesearch5.nbr2 <= 387432713256173567)) AND (NOT (predictivesearch5.nbr3 >= 387415121070129152 AND predictivesearch5.nbr3 <= 387432713256173567)) AND (NOT (predictivesearch5.nbr4 >= 387415121070129152 AND predictivesearch5.nbr4 <= 387432713256173567)))
UNION
SELECT predictivesearch5.contact_id, predictivesearch5.first_name, predictivesearch5.last_name  FROM predictivesearch5 JOIN predictivesearch6 ON predictivesearch5.contact_id = predictivesearch6.contact_id
WHERE NOT((NOT (predictivesearch5.nbr >= 387309567953862656 AND predictivesearch5.nbr <= 391813167581233151)) AND (NOT (predictivesearch5.nbr2 >= 387309567953862656 AND predictivesearch5.nbr2 <= 391813167581233151)) AND (NOT (predictivesearch5.nbr3 >= 387309567953862656 AND predictivesearch5.nbr3 <= 391813167581233151)) AND (NOT (predictivesearch5.nbr4 >= 387309567953862656 AND predictivesearch5.nbr4 <= 391813167581233151)))
AND
NOT((NOT (predictivesearch6.nbr >= 387309567953862656 AND predictivesearch6.nbr <= 391813167581233151)) AND (NOT (predictivesearch6.nbr2 >= 387309567953862656 AND predictivesearch6.nbr2 <= 391813167581233151)) AND (NOT (predictivesearch6.nbr3 >= 387309567953862656 AND predictivesearch6.nbr3 <= 391813167581233151)) AND (NOT (predictivesearch6.nbr4 >= 387309567953862656 AND predictivesearch6.nbr4 <= 391813167581233151)))
) AS PR
ORDER BY PR.first_name, PR.last_name ASC;

*/
QString CntSqlSearch::CreateJoinTableSearch(QString pattern, QStringList numbers) const
	{
	// It has been checked earlier that tables are not same
	QString firstTable = SelectTable(numbers.at(0));
	QString secondTable = SelectTable(numbers.at(1));

    QString queryString = QString("SELECT contact_id FROM (SELECT "
                                  + firstTable + ".contact_id, " + firstTable + ".first_name, " + firstTable + ".last_name FROM " + firstTable +
                                    " WHERE " + ExactMatch(pattern, firstTable) +
                                    " UNION SELECT " + firstTable + ".contact_id, " + firstTable + ".first_name, " + firstTable + ".last_name FROM " + firstTable + " JOIN " + secondTable + " ON " + firstTable + ".contact_id = " + secondTable +  ".contact_id WHERE" +
                                ExactMatchColumns(numbers) + ") AS PR ORDER BY PR.first_name, PR.last_name ASC;");
	return queryString;
	}

QString CntSqlSearch::ExactMatchColumns(QStringList numbers) const
    {
    const int KFirstColumn = 0;
    const int KSecondColumn = 1;
    QString firstColumn = numbers.at(KFirstColumn);
    QString secondColumn = numbers.at(KSecondColumn);

    if( firstColumn.count() >  1 && secondColumn.count() > 1)
        {
        return "(" + ExactMatch(numbers.at(KFirstColumn), SelectTable(numbers.at(KFirstColumn)))
            + " AND " +
            ExactMatch(numbers.at(KSecondColumn), SelectTable(numbers.at(KSecondColumn))) + ")";
        }
    else if(firstColumn.count() > 1)
        {
        return ExactMatch(numbers.at(KFirstColumn), SelectTable(numbers.at(KFirstColumn)));
        }
    else
        {
        return ExactMatch(numbers.at(KSecondColumn), SelectTable(numbers.at(KSecondColumn)));
        }
    }

QString CntSqlSearch::Order(QStringList tokens, QueryType queryType) const
	{
    QString table;
	if (tokens.count() > 1 )
		{
        if(queryType == CntSqlSearch::QwertyEmail)
            {
            table = selectQweryTable(tokens.at(0));
            }
        else
            {
            table = SelectTable(tokens.at(0));
            }
        return QString(" ORDER BY " + table + ".first_name, " + table + ".last_name ASC;");
		}
	return QString(ORDER_BY_FIRSTNAME_LASTNAME);
	}

QString CntSqlSearch::ChangeStringPadings( const QString &pattern ) const
    { 
    QString newPattern = pattern;
    if (QLocale::system().language() == QLocale::Thai)
        {
        newPattern.remove(KStarChar, Qt::CaseInsensitive);
        newPattern.remove(KPlusChar, Qt::CaseInsensitive);
        newPattern.remove(KPChar, Qt::CaseInsensitive);
        newPattern.remove(KWChar, Qt::CaseInsensitive);
        newPattern.remove(KHashChar, Qt::CaseInsensitive);
        }
    else
        { 
        newPattern.replace(KStarChar, 'A');
        newPattern.replace(KPlusChar, 'A');
        newPattern.replace(KPChar, 'A');
        newPattern.replace(KWChar, 'A');
        newPattern.replace(KHashChar, 'B');
        }
    return newPattern;
    }

bool CntSqlSearch::TestPattern( const QString &pattern, SearchMethod searchMethod ) const
    {
    QStringList tokens = GetTokens(pattern);
    if (!tokens.isEmpty() && !pattern.isEmpty())
        {
        if (CntSqlSearch::ZerosEndOfFirstToken == searchMethod)
            {
            if( tokens.count() == KOneToken /*&& !tokens.at(0).contains("0")*/
                && !pattern.startsWith('0') /*&& pattern.count('0') == 1*/
                && pattern.endsWith('0'))
                {
                return true;
                }
            }
        if (CntSqlSearch::ZeroIsFirstNumber == searchMethod )
            {
            if(pattern.startsWith('0') && pattern.count() > 1 )
                {
                return true;
                }
            if (pattern.startsWith('0') && tokens.count() >= KTwoTokens )
                {
                return true;
                }
            }
        }
    return false;
    }

QString CntSqlSearch::Pad( const QString &pattern, char padChar ) const
    {
    int padCount = KLimitLength - pattern.length();    
    QString result;
    if ( padCount < 0 )
        {
        result = pattern.left(KLimitLength);
        }
    else
        {
        result = pattern;
        for( int i = 0; i < padCount ;i++ )
            {
            result.append(padChar);
            }
        }
    bool ok;
    // Use signed int to prevent underflow when replaced is "00...00"
    qint64 value = result.toLongLong(&ok, KHexadecimalBase); 
    if (!ok)
    	{
    	// TODO: handle error (=invalid characters in pattern)
    	}

	// In order to write queries using '>' and '<' instead of '>=' and '<=',
	// expand the limit by one.
	if (padChar == KUpperLimitPadding)
		{
		++value;
		}
	else
		{
		--value;
		}

    return QString::number(value, 10);
    }

bool CntSqlSearch::isQwerty(const QString &pattern)
    {
    QChar rs(30);
    int rs_index = pattern.indexOf(rs);  
    int qwerty_index = pattern.indexOf("vqwerty");
    if(rs_index >= 0 && qwerty_index >= 0 )
        {
        if(rs_index + 1 == qwerty_index)
            {
            return true;
            }
        return false;
        }
    return false;
    }
QStringList CntSqlSearch::qwertyTokens(const QString &pattern) const
    {
    QString decodePatern;
    QString keymapsString = mQertyKeyMap->GetMappedString(pattern);
    int index = pattern.indexOf(30);
    if(index > 0 )
        {
        QString decodePatern = keymapsString.left(index);
        return decodePatern.split(32, QString::SkipEmptyParts);
        }
    else
        {
        return QStringList("");
        }
    }
QString CntSqlSearch::UpperLimit( const QString &pattern ) const
    {
    return Pad( pattern, KUpperLimitPadding );
    }

QString CntSqlSearch::LowerLimit( const QString &pattern ) const
    {
    return Pad( pattern, KLowerLimitPadding );
    }