commit 04d763ee0bce48fe0504becd2cbb323a58b642c2
Author: Mathias Hasselmann <mathias@openismus.com>
Date:   Mon Dec 10 13:11:56 2012 +0100

    sqlitedb: Permit indexes with normalized phone numbers
    
    Creating indexes with normalized phone numbers permits fast phone number
    lookup even if the stored vCards or the queried phone numbers are freely
    formatted or incomplete.
    
    The numbers are formatted according to E.164 when building the index.
    Incomplete numbers are prefixed with a country-code matching the current
    locale settings while processing query results.
    
    See: https://bugzilla.gnome.org/show_bug.cgi?id=689622

diff --git a/addressbook/libebook/e-book-types.h b/addressbook/libebook/e-book-types.h
index f7d896f..5990ad6 100644
--- a/addressbook/libebook/e-book-types.h
+++ b/addressbook/libebook/e-book-types.h
@@ -116,6 +116,10 @@ typedef struct {
  * EBookIndexType:
  * @E_BOOK_INDEX_PREFIX: An index suitable for searching contacts with a prefix pattern
  * @E_BOOK_INDEX_SUFFIX: An index suitable for searching contacts with a suffix pattern
+ * @E_BOOK_INDEX_PHONE: An index suitable for searching contacts for phone numbers.
+ * Note that phone numbers must be convertible into FQTN according to E.164 to be
+ * stored in this index. The number "+9999999" for instance won't be stored because
+ * the country calling code "+999" currently is not assigned.
  *
  * The type of index defined for e_source_backend_summary_setup_set_indexed_fields()
  *
@@ -123,7 +127,8 @@ typedef struct {
  */
 typedef enum {
 	E_BOOK_INDEX_PREFIX = 0,
-	E_BOOK_INDEX_SUFFIX
+	E_BOOK_INDEX_SUFFIX,
+	E_BOOK_INDEX_PHONE
 } EBookIndexType;
 
 G_END_DECLS
diff --git a/addressbook/libebook/e-source-backend-summary-setup.c b/addressbook/libebook/e-source-backend-summary-setup.c
index 018a941..57b77dc 100644
--- a/addressbook/libebook/e-source-backend-summary-setup.c
+++ b/addressbook/libebook/e-source-backend-summary-setup.c
@@ -535,9 +535,11 @@ e_source_backend_summary_setup_get_indexed_fields (ESourceBackendSummarySetup *e
  * Defines indexes for quick reference for the given given #EContactFields in the addressbook.
  *
  * The same #EContactField may be specified multiple times to create multiple indexes
- * with different charachteristics. If an #E_BOOK_INDEX_PREFIX index is created it will
- * be used for #E_BOOK_QUERY_BEGINS_WITH queries; A #E_BOOK_INDEX_SUFFIX index will be
- * constructed efficiently for suffix matching and will be used for #E_BOOK_QUERY_ENDS_WITH queries.
+ * with different characteristics. If an #E_BOOK_INDEX_PREFIX index is created it will
+ * be used for #E_BOOK_QUERY_BEGINS_WITH queries. A #E_BOOK_INDEX_SUFFIX index
+ * will be constructed efficiently for suffix matching and will be used for
+ * #E_BOOK_QUERY_ENDS_WITH queries. Similar a #E_BOOK_INDEX_PHONE index will optimize
+ * #E_BOOK_QUERY_EQUALS_PHONE_NUMBER searches.
  *
  * <note><para>The specified indexed fields must also be a part of the summary, any indexed fields
  * specified that are not already a part of the summary will be ignored.</para></note>
diff --git a/addressbook/libedata-book/e-book-backend-sqlitedb.c b/addressbook/libedata-book/e-book-backend-sqlitedb.c
index 15b7bc1..019621b 100644
--- a/addressbook/libedata-book/e-book-backend-sqlitedb.c
+++ b/addressbook/libedata-book/e-book-backend-sqlitedb.c
@@ -19,9 +19,9 @@
  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  */
 
+#include "e-book-backend-sqlitedb.h"
+
 #include <string.h>
-#include <ctype.h>
-#include <stdlib.h>
 #include <errno.h>
 
 #include <glib/gi18n.h>
@@ -31,7 +31,6 @@
 #include <libebackend/libebackend.h>
 
 #include "e-book-backend-sexp.h"
-#include "e-book-backend-sqlitedb.h"
 
 #define E_BOOK_BACKEND_SQLITEDB_GET_PRIVATE(obj) \
 	(G_TYPE_INSTANCE_GET_PRIVATE \
@@ -40,7 +39,7 @@
 #define d(x)
 
 #define DB_FILENAME "contacts.db"
-#define FOLDER_VERSION 3
+#define FOLDER_VERSION 5
 
 #define READER_LOCK(ebsdb) g_rw_lock_reader_lock (&ebsdb->priv->rwlock)
 #define READER_UNLOCK(ebsdb) g_rw_lock_reader_unlock (&ebsdb->priv->rwlock)
@@ -49,7 +48,8 @@
 
 typedef enum {
 	INDEX_PREFIX = (1 << 0),
-	INDEX_SUFFIX = (1 << 1)
+	INDEX_SUFFIX = (1 << 1),
+	INDEX_PHONE  = (1 << 2)
 } IndexFlags;
 
 typedef struct {
@@ -73,8 +73,7 @@ struct _EBookBackendSqliteDBPrivate {
 	SummaryField   *summary_fields;
 	gint            n_summary_fields;
 	guint           have_attr_list : 1;
-	guint           have_attr_list_prefix : 1;
-	guint           have_attr_list_suffix : 1;
+	IndexFlags      attr_list_indexes;
 };
 
 G_DEFINE_TYPE (EBookBackendSqliteDB, e_book_backend_sqlitedb, G_TYPE_OBJECT)
@@ -110,10 +109,14 @@ static EBookIndexType default_index_types[] = {
 	E_BOOK_INDEX_PREFIX
 };
 
-static gboolean append_summary_field (GArray         *array,
-				      EContactField   field,
-				      gboolean       *have_attr_list,
-				      GError        **error);
+static SummaryField * append_summary_field (GArray         *array,
+                                            EContactField   field,
+                                            gboolean       *have_attr_list,
+                                            GError        **error);
+
+static gboolean upgrade_contacts_table (EBookBackendSqliteDB  *ebsdb,
+                                        const gchar           *folderid,
+                                        GError               **error);
 
 static const gchar *
 summary_dbname_from_field (EBookBackendSqliteDB *ebsdb,
@@ -491,6 +494,7 @@ collect_versions_cb (gpointer ref,
 
 static gboolean
 create_folders_table (EBookBackendSqliteDB *ebsdb,
+                      gint *previous_schema,
                       GError **error)
 {
 	gboolean success;
@@ -510,12 +514,11 @@ create_folders_table (EBookBackendSqliteDB *ebsdb,
 		"( folder_id  TEXT PRIMARY KEY,"
 		" folder_name TEXT,"
 		"  sync_data TEXT,"
-		" is_populated INTEGER,"
-		"  partial_content INTEGER,"
+		" is_populated INTEGER DEFAULT 0,"
+		"  partial_content INTEGER DEFAULT 0,"
 		" version INTEGER,"
 		"  revision TEXT,"
-		" multivalues TEXT,"
-		"  reverse_multivalues INTEGER )";
+		" multivalues TEXT )";
 
 	if (!book_backend_sqlitedb_start_transaction (ebsdb, error))
 		return FALSE;
@@ -560,15 +563,33 @@ create_folders_table (EBookBackendSqliteDB *ebsdb,
 	/* Upgrade DB to version 3, add multivalues introspection columns
 	 */
 	if (version >= 1 && version < 3) {
-
 		stmt = "ALTER TABLE folders ADD COLUMN multivalues TEXT";
 		success = book_backend_sql_exec (
 			ebsdb->priv->db, stmt, NULL, NULL, error);
 
 		if (!success)
 			goto rollback;
+	}
+
+	/* Upgrade DB to version 4: Nothing to do. The country-code column it
+	 * added got redundant already.
+	 */
+
+	/* Upgrade DB to version 5: Drop the reverse_multivalues column, but
+	 * wait with converting phone summary values to new format until
+	 * create_contacts_table() as we need introspection details for doing
+	 * that.
+	 */
+	if (version >= 3 && version < FOLDER_VERSION) {
+		stmt = "UPDATE folders SET "
+				"multivalues = REPLACE(RTRIM(REPLACE("
+					"multivalues || ':', ':', "
+					"CASE reverse_multivalues "
+						"WHEN 0 THEN ';prefix ' "
+						"ELSE ';prefix;suffix ' "
+					"END)), ' ', ':'), "
+				"reverse_multivalues = NULL";
 
-		stmt = "ALTER TABLE folders ADD COLUMN reverse_multivalues INTEGER";
 		success = book_backend_sql_exec (
 			ebsdb->priv->db, stmt, NULL, NULL, error);
 
@@ -576,6 +597,8 @@ create_folders_table (EBookBackendSqliteDB *ebsdb,
 			goto rollback;
 	}
 
+	/* Finish the eventual upgrade by storing the current schema version.
+	 */
 	if (version >= 1 && version < FOLDER_VERSION) {
 		gchar *version_update_stmt =
 			sqlite3_mprintf ("UPDATE folders SET version = %d", FOLDER_VERSION);
@@ -589,23 +612,23 @@ create_folders_table (EBookBackendSqliteDB *ebsdb,
 	if (!success)
 		goto rollback;
 
+	*previous_schema = version;
 	return book_backend_sqlitedb_commit_transaction (ebsdb, error);
 
 rollback:
 	/* The GError is already set. */
 	book_backend_sqlitedb_rollback_transaction (ebsdb, NULL);
 
+	*previous_schema = 0;
 	return FALSE;
 }
 
 static gchar *
-format_multivalues (EBookBackendSqliteDB *ebsdb,
-                    gboolean *reverse_multivalues)
+format_multivalues (EBookBackendSqliteDB *ebsdb)
 {
 	gint i;
 	GString *string;
 	gboolean first = TRUE;
-	gboolean has_reverse = FALSE;
 
 	string = g_string_new (NULL);
 
@@ -618,14 +641,15 @@ format_multivalues (EBookBackendSqliteDB *ebsdb,
 
 			g_string_append (string, ebsdb->priv->summary_fields[i].dbname);
 
+			if ((ebsdb->priv->summary_fields[i].index & INDEX_PREFIX) != 0)
+				g_string_append (string, ";prefix");
 			if ((ebsdb->priv->summary_fields[i].index & INDEX_SUFFIX) != 0)
-				has_reverse = TRUE;
+				g_string_append (string, ";suffix");
+			if ((ebsdb->priv->summary_fields[i].index & INDEX_PHONE) != 0)
+				g_string_append (string, ";phone");
 		}
 	}
 
-	if (reverse_multivalues)
-		*reverse_multivalues = has_reverse;
-
 	return g_string_free (string, FALSE);
 }
 
@@ -637,19 +661,18 @@ add_folder_into_db (EBookBackendSqliteDB *ebsdb,
 {
 	gchar *stmt;
 	gboolean success;
-	gboolean has_reverse = FALSE;
 	gchar *multivalues;
 
 	if (!book_backend_sqlitedb_start_transaction (ebsdb, error))
 		return FALSE;
 
-	multivalues = format_multivalues (ebsdb, &has_reverse);
+	multivalues = format_multivalues (ebsdb);
 
 	stmt = sqlite3_mprintf (
-		"INSERT OR IGNORE INTO folders VALUES "
-		"( %Q, %Q, %Q, %d, %d, %d, %Q, %Q, %d ) ",
-		folderid, folder_name, NULL, 0, 0, FOLDER_VERSION,
-		NULL, multivalues, has_reverse);
+		"INSERT OR IGNORE INTO "
+		"folders ( folder_id, folder_name, version, multivalues ) "
+		"VALUES ( %Q, %Q, %d, %Q ) ",
+		folderid, folder_name, FOLDER_VERSION, multivalues);
 	success = book_backend_sql_exec (
 		ebsdb->priv->db, stmt, NULL, NULL, error);
 	sqlite3_free (stmt);
@@ -704,9 +727,7 @@ introspect_summary (EBookBackendSqliteDB *ebsdb,
 	GList *summary_columns = NULL, *l;
 	GArray *summary_fields = NULL;
 	gchar *multivalues = NULL;
-	gboolean reverse_multivalues = FALSE;
-	gchar **split;
-	gint i;
+	gint i, j;
 
 	stmt = sqlite3_mprintf ("PRAGMA table_info (%Q);", folderid);
 	success = book_backend_sql_exec (
@@ -724,13 +745,15 @@ introspect_summary (EBookBackendSqliteDB *ebsdb,
 		EContactField field;
 		gchar *col = l->data;
 		gchar *p;
-		gboolean reverse = FALSE;
+		IndexFlags computed = 0;
 
 		/* Check if we're parsing a reverse field */
-		p = strstr (col, "_reverse");
-		if (p) {
+		if ((p = strstr (col, "_reverse")) != NULL) {
+			computed = INDEX_SUFFIX;
+			*p = '\0';
+		} else  if ((p = strstr (col, "_phone")) != NULL) {
+			computed = INDEX_PHONE;
 			*p = '\0';
-			reverse = TRUE;
 		}
 
 		/* First check exception fields */
@@ -750,16 +773,16 @@ introspect_summary (EBookBackendSqliteDB *ebsdb,
 			break;
 		}
 
-		/* Reverse columns are always declared after the normal columns,
+		/* Computed columns are always declared after the normal columns,
 		 * if a reverse field is encountered we need to set the suffix
 		 * index on the coresponding summary field
 		 */
-		if (reverse) {
+		if (computed) {
 			for (i = 0; i < summary_fields->len; i++) {
 				SummaryField *iter = &g_array_index (summary_fields, SummaryField, i);
 
 				if (iter->field == field) {
-					iter->index |= INDEX_SUFFIX;
+					iter->index |= computed;
 					break;
 				}
 			}
@@ -781,36 +804,34 @@ introspect_summary (EBookBackendSqliteDB *ebsdb,
 	if (!success)
 		goto introspect_summary_finish;
 
-	stmt = sqlite3_mprintf (
-		"SELECT reverse_multivalues FROM folders WHERE folder_id = %Q", folderid);
-	success = book_backend_sql_exec (
-		ebsdb->priv->db, stmt, get_bool_cb, &reverse_multivalues, error);
-	sqlite3_free (stmt);
-
-	if (!success)
-		goto introspect_summary_finish;
-
 	if (multivalues) {
-		split = g_strsplit (multivalues, ":", 0);
+		gchar **fields = g_strsplit (multivalues, ":", 0);
 
-		for (i = 0; split[i] != NULL; i++) {
+		for (i = 0; fields[i] != NULL; i++) {
 			EContactField field;
+			SummaryField *iter;
+			gchar **params;
+
+			params = g_strsplit (fields[i], ";", 0);
+			field = e_contact_field_id (params[0]);
+			iter = append_summary_field (summary_fields, field, NULL, NULL);
+
+			if (iter) {
+				for (j = 1; params[j]; ++j) {
+					if (strcmp (params[j], "prefix") == 0) {
+						iter->index |= INDEX_PREFIX;
+					} else if (strcmp (params[j], "suffix") == 0) {
+						iter->index |= INDEX_SUFFIX;
+					} else if (strcmp (params[j], "phone") == 0) {
+						iter->index |= INDEX_PHONE;
+					}
+				}
+			}
 
-			field = e_contact_field_id (split[i]);
-			append_summary_field (summary_fields, field, NULL, NULL);
+			g_strfreev (params);
 		}
-		g_strfreev (split);
-	}
-
-	/* If there is a reverse multivalue column, enable lookups for every multivalue field in reverse */
-	if (reverse_multivalues) {
-
-		for (i = 0; i < summary_fields->len; i++) {
-			SummaryField *iter = &g_array_index (summary_fields, SummaryField, i);
 
-			if (iter->type == E_TYPE_CONTACT_ATTR_LIST)
-				iter->index |= INDEX_SUFFIX;
-		}
+		g_strfreev (fields);
 	}
 
  introspect_summary_finish:
@@ -834,6 +855,7 @@ introspect_summary (EBookBackendSqliteDB *ebsdb,
 static gboolean
 create_contacts_table (EBookBackendSqliteDB *ebsdb,
                        const gchar *folderid,
+                       gint previous_schema,
                        GError **error)
 {
 	gint i;
@@ -856,10 +878,16 @@ create_contacts_table (EBookBackendSqliteDB *ebsdb,
 			g_warn_if_reached ();
 
 		/* Additional columns holding normalized reverse values for suffix matching */
-		if (ebsdb->priv->summary_fields[i].type == G_TYPE_STRING &&
-		    (ebsdb->priv->summary_fields[i].index & INDEX_SUFFIX) != 0) {
-			g_string_append  (string, ebsdb->priv->summary_fields[i].dbname);
-			g_string_append  (string, "_reverse TEXT, ");
+		if (ebsdb->priv->summary_fields[i].type == G_TYPE_STRING) {
+			if (ebsdb->priv->summary_fields[i].index & INDEX_SUFFIX) {
+				g_string_append  (string, ebsdb->priv->summary_fields[i].dbname);
+				g_string_append  (string, "_reverse TEXT, ");
+			}
+
+			if (ebsdb->priv->summary_fields[i].index & INDEX_PHONE) {
+				g_string_append  (string, ebsdb->priv->summary_fields[i].dbname);
+				g_string_append  (string, "_phone TEXT, ");
+			}
 		}
 	}
 	g_string_append (string, "vcard TEXT, bdata TEXT)");
@@ -906,6 +934,19 @@ create_contacts_table (EBookBackendSqliteDB *ebsdb,
 			sqlite3_free (stmt);
 			g_free (tmp);
 		}
+
+		if ((ebsdb->priv->summary_fields[i].index & INDEX_PHONE) != 0 &&
+		    ebsdb->priv->summary_fields[i].type != E_TYPE_CONTACT_ATTR_LIST) {
+			/* Derive index name from field & folder */
+			tmp = g_strdup_printf ("PINDEX_%s_%s",
+					       summary_dbname_from_field (ebsdb, ebsdb->priv->summary_fields[i].field),
+					       folderid);
+			stmt = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON %Q (%s_phone)", tmp, folderid,
+						summary_dbname_from_field (ebsdb, ebsdb->priv->summary_fields[i].field));
+			success = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, error);
+			sqlite3_free (stmt);
+			g_free (tmp);
+		}
 	}
 
 	/* Construct the create statement from the attribute list summary table */
@@ -913,8 +954,10 @@ create_contacts_table (EBookBackendSqliteDB *ebsdb,
 		string = g_string_new ("CREATE TABLE IF NOT EXISTS %Q ( uid TEXT NOT NULL REFERENCES %Q(uid), "
 			"field TEXT, value TEXT");
 
-		if (ebsdb->priv->have_attr_list_suffix)
+		if ((ebsdb->priv->attr_list_indexes & INDEX_SUFFIX) != 0)
 			g_string_append (string, ", value_reverse TEXT");
+		if ((ebsdb->priv->attr_list_indexes & INDEX_PHONE) != 0)
+			g_string_append (string, ", value_phone TEXT");
 
 		g_string_append_c (string, ')');
 
@@ -931,33 +974,241 @@ create_contacts_table (EBookBackendSqliteDB *ebsdb,
 		sqlite3_free (stmt);
 
 		/* Create indexes if specified */
-		if (success && ebsdb->priv->have_attr_list_prefix) {
+		if (success && (ebsdb->priv->attr_list_indexes & INDEX_PREFIX) != 0) {
 			stmt = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS VALINDEX ON %Q (value)", tmp);
 			success = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, error);
 			sqlite3_free (stmt);
 		}
 
-		if (success && ebsdb->priv->have_attr_list_suffix) {
+		if (success && (ebsdb->priv->attr_list_indexes & INDEX_SUFFIX) != 0) {
 			stmt = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS RVALINDEX ON %Q (value_reverse)", tmp);
 			success = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, error);
 			sqlite3_free (stmt);
 		}
 
+		if (success && (ebsdb->priv->attr_list_indexes & INDEX_PHONE) != 0) {
+			stmt = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS PVALINDEX ON %Q (value_phone)", tmp);
+			success = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, error);
+			sqlite3_free (stmt);
+		}
 		g_free (tmp);
-
 	}
 
 	WRITER_UNLOCK (ebsdb);
 
 	if (success)
 		success = introspect_summary (ebsdb, folderid, error);
+	if (success && previous_schema == 4)
+		success = upgrade_contacts_table (ebsdb, folderid, error);
 
 	return success;
 }
 
+typedef struct {
+	sqlite3 *db;
+	const gchar *collation;
+	const gchar *table;
+} CollationInfo;
+
+static gint
+create_phone_indexes_for_columns (gpointer data,
+				  gint n_cols,
+				  gchar **cols,
+				  gchar **name)
+{
+	const gchar *column_name = cols[1];
+	CollationInfo *info = data;
+
+	if (g_str_has_suffix (column_name, "_phone")) {
+		gchar *index_name, *stmt;
+		GError *error = NULL;
+
+		index_name = g_strdup_printf (
+			"PINDEX_%s_ON_%s_WITH_%s", column_name, info->table, info->collation);
+		stmt = sqlite3_mprintf (
+			"CREATE INDEX IF NOT EXISTS %Q ON %Q (%s COLLATE %Q)",
+			index_name, info->table, column_name, info->collation);
+
+		if (!book_backend_sql_exec (info->db, stmt, NULL, NULL, &error)) {
+			g_warning ("%s: %s", G_STRFUNC, error->message);
+			g_error_free (error);
+		}
+
+		sqlite3_free (stmt);
+		g_free (index_name);
+	}
+
+	return 0;
+}
+
+static gint
+create_phone_indexes_for_tables (gpointer data,
+				 gint n_cols,
+				 gchar **cols,
+				 gchar **name)
+{
+	CollationInfo *info = data;
+	GError *error = NULL;
+	gchar *tmp, *stmt;
+
+	info->table = cols[0];
+	stmt = sqlite3_mprintf ("PRAGMA table_info(%Q)", info->table);
+
+	if (!book_backend_sql_exec (
+		info->db, stmt, create_phone_indexes_for_columns, info, &error)) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_clear_error (&error);
+	}
+
+	sqlite3_free (stmt);
+
+	info->table = tmp = g_strconcat (info->table, "_lists", NULL);
+	stmt = sqlite3_mprintf ("PRAGMA table_info(%Q)", info->table);
+
+	if (!book_backend_sql_exec (
+		info->db, stmt, create_phone_indexes_for_columns, info, &error)) {
+		g_warning ("%s: %s", G_STRFUNC, error->message);
+		g_clear_error (&error);
+	}
+
+
+	sqlite3_free (stmt);
+	g_free (tmp);
+
+	return 0;
+}
+
+static GString *
+ixphone_str (gint               country_code,
+	     const gchar *const national_str,
+	     gint               national_len)
+{
+	GString *const str = g_string_sized_new (6 + national_len);
+	g_string_append_printf (str, "+%d|", country_code);
+	g_string_append_len (str, national_str, national_len);
+	return str;
+}
+
+static gint
+e_strcmp2n (const gchar *str1,
+	    size_t       len1,
+	    const gchar *str2,
+	    size_t       len2)
+{
+	const gint cmp = memcmp (str1, str2, MIN (len1, len2));
+
+	return (cmp != 0 ? cmp :
+		len1 == len2 ? 0 :
+		len1 < len2 ? -1 : 1);
+}
+
+static gint
+ixphone_compare_for_country (gpointer      data,
+			     gint          len1,
+			     gconstpointer arg1,
+			     gint          len2,
+			     gconstpointer arg2)
+{
+	const gchar *const str1 = arg1;
+	const gchar *const str2 = arg2;
+	const gchar *const sep1 = memchr(str1, '|', len1);
+	const gchar *const sep2 = memchr(str2, '|', len2);
+	const gint country_code = GPOINTER_TO_INT(data);
+
+	g_return_val_if_fail (sep1 != NULL, 0);
+	g_return_val_if_fail (sep2 != NULL, 0);
+
+	if ((str1 == sep1) == (str2 == sep2))
+		return e_strcmp2n (str1, len1, str2, len2);
+
+	if (str1 == sep1) {
+		GString *const tmp = ixphone_str (country_code, str1, len1);
+		const gint cmp = e_strcmp2n (tmp->str, tmp->len, str2, len2);
+		g_string_free (tmp, TRUE);
+		return cmp;
+	} else {
+		GString *const tmp = ixphone_str (country_code, str2, len2);
+		const gint cmp = e_strcmp2n (str1, len1, tmp->str, tmp->len);
+		g_string_free (tmp, TRUE);
+		return cmp;
+	}
+}
+
+static gint
+ixphone_compare_national (gpointer      data,
+			  gint          len1,
+			  gconstpointer arg1,
+			  gint          len2,
+			  gconstpointer arg2)
+{
+	const gchar *const str1 = arg1;
+	const gchar *const str2 = arg2;
+	const gchar *sep1 = memchr(str1, '|', len1);
+	const gchar *sep2 = memchr(str2, '|', len2);
+
+	gint cmp;
+
+	g_return_val_if_fail (sep1 != NULL, 0);
+	g_return_val_if_fail (sep2 != NULL, 0);
+
+	cmp = e_strcmp2n (sep1 + 1, len1 - (sep1 + 1 - str1),
+			  sep2 + 1, len2 - (sep2 + 1 - str2));
+
+	if (booksql_debug ()) {
+		gchar *const tmp1 = g_strndup (str1, len1);
+		gchar *const tmp2 = g_strndup (str2, len2);
+
+		g_printerr
+			("  DEBUG %s('%s', '%s') = %d\n",
+			 G_STRFUNC, tmp1, tmp2, cmp);
+
+		g_free (tmp2);
+		g_free (tmp1);
+	}
+
+	return cmp;
+}
+
+static void
+create_collation (gpointer     data,
+		  sqlite3     *db,
+		  gint         encoding,
+		  const gchar *name)
+{
+	gint ret = SQLITE_DONE;
+	gint country_code;
+
+	g_warn_if_fail (encoding == SQLITE_UTF8);
+
+	if  (1 == sscanf (name, "ixphone_%d", &country_code)) {
+		ret = sqlite3_create_collation (
+			db, name, SQLITE_UTF8, GINT_TO_POINTER(country_code),
+			ixphone_compare_for_country);
+	} else if (strcmp (name, "ixphone_nn") == 0) {
+		ret = sqlite3_create_collation (
+			db, name, SQLITE_UTF8, NULL,
+			ixphone_compare_national);
+	}
+
+	if (ret == SQLITE_OK) {
+		CollationInfo info = { db, name };
+		GError *error = NULL;
+
+		if (!book_backend_sql_exec (
+			db, "SELECT folder_id FROM folders",
+			create_phone_indexes_for_tables, &info, &error)) {
+			g_warning ("%s(%s): %s", G_STRFUNC, name, error->message);
+			g_error_free (error);
+		}
+	} else if (ret != SQLITE_DONE) {
+		g_warning ("%s(%s): %s", G_STRFUNC, name, sqlite3_errmsg (db));
+	}
+}
+
 static gboolean
 book_backend_sqlitedb_load (EBookBackendSqliteDB *ebsdb,
                             const gchar *filename,
+                            gint *previous_schema,
                             GError **error)
 {
 	gint ret;
@@ -965,7 +1216,11 @@ book_backend_sqlitedb_load (EBookBackendSqliteDB *ebsdb,
 	e_sqlite3_vfs_init ();
 
 	ret = sqlite3_open (filename, &ebsdb->priv->db);
-	if (ret) {
+
+	if (ret == SQLITE_OK)
+		ret = sqlite3_collation_needed(ebsdb->priv->db, ebsdb, create_collation);
+
+	if (ret != SQLITE_OK) {
 		if (!ebsdb->priv->db) {
 			g_set_error (
 				error, E_BOOK_SDB_ERROR,
@@ -999,7 +1254,7 @@ book_backend_sqlitedb_load (EBookBackendSqliteDB *ebsdb,
 
 	WRITER_UNLOCK (ebsdb);
 
-	return create_folders_table (ebsdb, error);
+	return create_folders_table (ebsdb, previous_schema, error);
 }
 
 static EBookBackendSqliteDB *
@@ -1011,12 +1266,12 @@ e_book_backend_sqlitedb_new_internal (const gchar *path,
                                       SummaryField *fields,
                                       gint n_fields,
                                       gboolean have_attr_list,
-                                      gboolean have_attr_list_prefix,
-                                      gboolean have_attr_list_suffix,
+                                      IndexFlags attr_list_indexes,
                                       GError **error)
 {
 	EBookBackendSqliteDB *ebsdb;
 	gchar *hash_key, *filename;
+	gint previous_schema = 0;
 
 	g_return_val_if_fail (path != NULL, NULL);
 	g_return_val_if_fail (emailid != NULL, NULL);
@@ -1042,8 +1297,7 @@ e_book_backend_sqlitedb_new_internal (const gchar *path,
 	ebsdb->priv->summary_fields = fields;
 	ebsdb->priv->n_summary_fields = n_fields;
 	ebsdb->priv->have_attr_list = have_attr_list;
-	ebsdb->priv->have_attr_list_prefix = have_attr_list_prefix;
-	ebsdb->priv->have_attr_list_suffix = have_attr_list_suffix;
+	ebsdb->priv->attr_list_indexes = attr_list_indexes;
 	ebsdb->priv->store_vcard = store_vcard;
 	if (g_mkdir_with_parents (path, 0777) < 0) {
 		g_mutex_unlock (&dbcon_lock);
@@ -1054,7 +1308,7 @@ e_book_backend_sqlitedb_new_internal (const gchar *path,
 	}
 	filename = g_build_filename (path, DB_FILENAME, NULL);
 
-	if (!book_backend_sqlitedb_load (ebsdb, filename, error)) {
+	if (!book_backend_sqlitedb_load (ebsdb, filename, &previous_schema, error)) {
 		g_mutex_unlock (&dbcon_lock);
 		g_object_unref (ebsdb);
 		g_free (filename);
@@ -1079,7 +1333,7 @@ exit:
 		return NULL;
 	}
 
-	if (!create_contacts_table (ebsdb, folderid, error)) {
+	if (!create_contacts_table (ebsdb, folderid, previous_schema, error)) {
 		g_object_unref (ebsdb);
 		return NULL;
 	}
@@ -1087,7 +1341,7 @@ exit:
 	return ebsdb;
 }
 
-static gboolean
+static SummaryField *
 append_summary_field (GArray *array,
                       EContactField field,
                       gboolean *have_attr_list,
@@ -1102,14 +1356,14 @@ append_summary_field (GArray *array,
 		g_set_error (
 			error, E_BOOK_SDB_ERROR, E_BOOK_SDB_ERROR_OTHER,
 			_("Invalid contact field '%d' specified in summary"), field);
-		return FALSE;
+		return NULL;
 	}
 
 	/* Avoid including the same field twice in the summary */
 	for (i = 0; i < array->len; i++) {
 		SummaryField *iter = &g_array_index (array, SummaryField, i);
 		if (field == iter->field)
-			return TRUE;
+			return iter;
 	}
 
 	/* Resolve some exceptions, we store these
@@ -1138,7 +1392,7 @@ append_summary_field (GArray *array,
 			_("Contact field '%s' of type '%s' specified in summary, "
 			"but only boolean, string and string list field types are supported"),
 			e_contact_pretty_name (field), g_type_name (type));
-		return FALSE;
+		return NULL;
 	}
 
 	if (type == E_TYPE_CONTACT_ATTR_LIST && have_attr_list)
@@ -1147,18 +1401,18 @@ append_summary_field (GArray *array,
 	new_field.field  = field;
 	new_field.dbname = dbname;
 	new_field.type   = type;
+	new_field.index  = INDEX_PREFIX;
 	g_array_append_val (array, new_field);
 
-	return TRUE;
+	return &g_array_index (array, SummaryField, array->len - 1);
 }
 
 static void
-summary_fields_add_indexes (GArray *array,
-                            EContactField *indexes,
+summary_fields_add_indexes (GArray         *array,
+                            EContactField  *indexes,
                             EBookIndexType *index_types,
-                            gint n_indexes,
-                            gboolean *have_attr_list_prefix,
-                            gboolean *have_attr_list_suffix)
+                            gint            n_indexes,
+                            IndexFlags     *attr_list_indexes)
 {
 	gint i, j;
 
@@ -1172,13 +1426,19 @@ summary_fields_add_indexes (GArray *array,
 					sfield->index |= INDEX_PREFIX;
 
 					if (sfield->type == E_TYPE_CONTACT_ATTR_LIST)
-						*have_attr_list_prefix = TRUE;
+						*attr_list_indexes |= INDEX_PREFIX;
 					break;
 				case E_BOOK_INDEX_SUFFIX:
 					sfield->index |= INDEX_SUFFIX;
 
 					if (sfield->type == E_TYPE_CONTACT_ATTR_LIST)
-						*have_attr_list_suffix = TRUE;
+						*attr_list_indexes |= INDEX_SUFFIX;
+					break;
+				case E_BOOK_INDEX_PHONE:
+					sfield->index |= INDEX_PHONE;
+
+					if (sfield->type == E_TYPE_CONTACT_ATTR_LIST)
+						*attr_list_indexes |= INDEX_PHONE;
 					break;
 				default:
 					g_warn_if_reached ();
@@ -1228,8 +1488,7 @@ e_book_backend_sqlitedb_new_full (const gchar *path,
 	EContactField *indexed_fields;
 	EBookIndexType *index_types = NULL;
 	gboolean have_attr_list = FALSE;
-	gboolean have_attr_list_prefix = FALSE;
-	gboolean have_attr_list_suffix = FALSE;
+	IndexFlags attr_list_indexes = 0;
 	gboolean had_error = FALSE;
 	GArray *summary_fields;
 	gint n_fields = 0, n_indexed_fields = 0, i;
@@ -1271,7 +1530,7 @@ e_book_backend_sqlitedb_new_full (const gchar *path,
 	/* Add the 'indexed' flag to the SummaryField structs */
 	summary_fields_add_indexes (
 		summary_fields, indexed_fields, index_types, n_indexed_fields,
-		&have_attr_list_prefix, &have_attr_list_suffix);
+		&attr_list_indexes);
 
 	ebsdb = e_book_backend_sqlitedb_new_internal (
 		path, emailid, folderid, folder_name,
@@ -1279,8 +1538,7 @@ e_book_backend_sqlitedb_new_full (const gchar *path,
 		(SummaryField *) summary_fields->data,
 		summary_fields->len,
 		have_attr_list,
-		have_attr_list_prefix,
-		have_attr_list_suffix,
+		attr_list_indexes,
 		error);
 
 	g_free (fields);
@@ -1318,8 +1576,7 @@ e_book_backend_sqlitedb_new (const gchar *path,
 	EBookBackendSqliteDB *ebsdb;
 	GArray *summary_fields;
 	gboolean have_attr_list = FALSE;
-	gboolean have_attr_list_prefix = FALSE;
-	gboolean have_attr_list_suffix = FALSE;
+	IndexFlags attr_list_indexes = 0;
 	gint i;
 
 	/* Create the default summary structs */
@@ -1333,7 +1590,7 @@ e_book_backend_sqlitedb_new (const gchar *path,
 		default_indexed_fields,
 		default_index_types,
 		G_N_ELEMENTS (default_indexed_fields),
-		&have_attr_list_prefix, &have_attr_list_suffix);
+		&attr_list_indexes);
 
 	ebsdb = e_book_backend_sqlitedb_new_internal (
 		path, emailid, folderid, folder_name,
@@ -1341,9 +1598,9 @@ e_book_backend_sqlitedb_new (const gchar *path,
 		(SummaryField *) summary_fields->data,
 		summary_fields->len,
 		have_attr_list,
-		have_attr_list_prefix,
-		have_attr_list_suffix,
+		attr_list_indexes,
 		error);
+
 	g_array_free (summary_fields, FALSE);
 
 	return ebsdb;
@@ -1370,13 +1627,106 @@ e_book_backend_sqlitedb_unlock_updates (EBookBackendSqliteDB *ebsdb,
 		book_backend_sqlitedb_rollback_transaction (ebsdb, error);
 }
 
+static gchar *
+mprintf_suffix (const gchar *normal)
+{
+	gchar *reverse = normal ? g_utf8_strreverse (normal, -1) : NULL;
+	gchar *stmt = sqlite3_mprintf ("%Q", reverse);
+
+	g_free (reverse);
+	return stmt;
+}
+
+static EPhoneNumber *
+phone_number_from_string (const gchar *normal,
+                          const gchar *default_region)
+{
+	EPhoneNumber *number = NULL;
+	GError *error = NULL;
+
+	if (normal)
+		number = e_phone_number_from_string (normal, default_region, &error);
+
+	if (error) {
+		/* Only barf on unusual errors */
+		if (error->domain != E_PHONE_NUMBER_ERROR
+			|| error->code != E_PHONE_NUMBER_ERROR_INVALID_COUNTRY_CODE) {
+			g_warning ("%s: '%s': %s", G_STRLOC, normal, error->message);
+		}
+
+		g_clear_error (&error);
+	}
+
+	return number;
+}
+
+static gchar *
+convert_phone_national (const gchar *normal,
+                        const gchar *default_region)
+{
+	EPhoneNumber *number = phone_number_from_string (normal, default_region);
+	gchar *indexed_phone_number = NULL;
+	gchar *national_number = NULL;
+
+	if (number) {
+		national_number = e_phone_number_get_national_number (number);
+		e_phone_number_free (number);
+	}
+
+	if (national_number) {
+		indexed_phone_number = g_strconcat ("|", national_number, NULL);
+		g_free (national_number);
+	}
+
+	return indexed_phone_number;
+}
+
+static gchar *
+convert_phone (const gchar *normal,
+               const gchar *default_region)
+{
+	EPhoneNumber *number = phone_number_from_string (normal, default_region);
+	gchar *indexed_phone_number = NULL;
+	gchar *national_number = NULL;
+	gint country_code = 0;
+
+	if (number) {
+		national_number = e_phone_number_get_national_number (number);
+		country_code = e_phone_number_get_country_code (number, NULL);
+		e_phone_number_free (number);
+	}
+
+	if (national_number) {
+		indexed_phone_number = g_strdup_printf ("+%d|%s", country_code, national_number);
+		g_free (national_number);
+	}
+
+	return indexed_phone_number;
+}
+
+static gchar *
+mprintf_phone (const gchar *normal,
+               const gchar *default_region)
+{
+	gchar *phone = convert_phone (normal, default_region);
+	gchar *stmt = NULL;
+
+	if (phone) {
+		stmt = sqlite3_mprintf ("%Q", phone);
+		g_free (phone);
+	}
+
+	return stmt;
+}
+
 /* Add Contact (free the result with g_free() ) */
 static gchar *
 insert_stmt_from_contact (EBookBackendSqliteDB *ebsdb,
                           EContact *contact,
                           const gchar *folderid,
                           gboolean store_vcard,
-			  gboolean replace_existing)
+                          gboolean replace_existing,
+                          const gchar *default_region)
 {
 	GString *string;
 	gchar *str, *vcard_str;
@@ -1388,7 +1738,6 @@ insert_stmt_from_contact (EBookBackendSqliteDB *ebsdb,
 	sqlite3_free (str);
 
 	for (i = 0; i < ebsdb->priv->n_summary_fields; i++) {
-
 		if (ebsdb->priv->summary_fields[i].type == G_TYPE_STRING) {
 			gchar *val;
 			gchar *normal;
@@ -1410,18 +1759,21 @@ insert_stmt_from_contact (EBookBackendSqliteDB *ebsdb,
 			sqlite3_free (str);
 
 			if ((ebsdb->priv->summary_fields[i].index & INDEX_SUFFIX) != 0) {
-				gchar *reverse = normal ? g_utf8_strreverse (normal, -1) : NULL;
-
-				str = sqlite3_mprintf ("%Q", reverse);
+				str = mprintf_suffix (normal);
 				g_string_append (string, ", ");
 				g_string_append (string, str);
 				sqlite3_free (str);
-				g_free (reverse);
+			}
+
+			if ((ebsdb->priv->summary_fields[i].index & INDEX_PHONE) != 0) {
+				str = mprintf_phone (normal, default_region);
+				g_string_append (string, ", ");
+				g_string_append (string, str ? str : "NULL");
+				sqlite3_free (str);
 			}
 
 			g_free (normal);
 			g_free (val);
-
 		} else if (ebsdb->priv->summary_fields[i].type == G_TYPE_BOOLEAN) {
 			gboolean val;
 
@@ -1451,6 +1803,7 @@ insert_contact (EBookBackendSqliteDB *ebsdb,
 		EContact *contact,
 		const gchar *folderid,
 		gboolean replace_existing,
+		const gchar *default_region,
 		GError **error)
 {
 	EBookBackendSqliteDBPrivate *priv;
@@ -1460,7 +1813,7 @@ insert_contact (EBookBackendSqliteDB *ebsdb,
 	priv = ebsdb->priv;
 
 	/* Update main summary table */
-	stmt = insert_stmt_from_contact (ebsdb, contact, folderid, priv->store_vcard, replace_existing);
+	stmt = insert_stmt_from_contact (ebsdb, contact, folderid, priv->store_vcard, replace_existing, default_region);
 	success = book_backend_sql_exec (priv->db, stmt, NULL, NULL, error);
 	g_free (stmt);
 
@@ -1478,7 +1831,6 @@ insert_contact (EBookBackendSqliteDB *ebsdb,
 		sqlite3_free (stmt);
 
 		for (i = 0; success && i < priv->n_summary_fields; i++) {
-
 			if (priv->summary_fields[i].type != E_TYPE_CONTACT_ATTR_LIST)
 				continue;
 
@@ -1487,24 +1839,32 @@ insert_contact (EBookBackendSqliteDB *ebsdb,
 			for (l = values; success && l != NULL; l = l->next) {
 				gchar *value = (gchar *) l->data;
 				gchar *normal = e_util_utf8_normalize (value);
-
-				if (priv->have_attr_list_suffix) {
-					gchar *reverse = normal ? g_utf8_strreverse (normal, -1) : NULL;
-
-					stmt = sqlite3_mprintf ("INSERT INTO %Q (uid, field, value, value_reverse) "
-								"VALUES (%Q, %Q, %Q, %Q)",
-								list_folder, uid,
-								priv->summary_fields[i].dbname,
-								normal, reverse);
-
-					g_free (reverse);
-				} else {
-					stmt = sqlite3_mprintf ("INSERT INTO %Q (uid, field, value) "
-								"VALUES (%Q, %Q, %Q)",
-								list_folder, uid,
-								priv->summary_fields[i].dbname,
-								normal);
-				}
+				gchar *stmt_suffix = NULL;
+				gchar *stmt_phone = NULL;
+
+				if ((priv->attr_list_indexes & INDEX_SUFFIX) != 0
+					&& (priv->summary_fields[i].index & INDEX_SUFFIX) != 0)
+					stmt_suffix = mprintf_suffix (normal);
+
+				if ((priv->attr_list_indexes & INDEX_PHONE) != 0
+					&& (priv->summary_fields[i].index & INDEX_PHONE) != 0)
+					stmt_phone = mprintf_phone (normal, default_region);
+
+				stmt = sqlite3_mprintf ("INSERT INTO %Q (uid, field, value%s%s) "
+							"VALUES (%Q, %Q, %Q%s%s%s%s)",
+							list_folder,
+							stmt_suffix ? ", value_reverse" : "",
+							stmt_phone ? ", value_phone" : "",
+							uid, priv->summary_fields[i].dbname, normal,
+							stmt_suffix ? ", " : "",
+							stmt_suffix ? stmt_suffix : "",
+							stmt_phone ? ", " : "",
+							stmt_phone ? stmt_phone : "");
+
+				if (stmt_suffix)
+					sqlite3_free (stmt_suffix);
+				if (stmt_phone)
+					sqlite3_free (stmt_phone);
 
 				success = book_backend_sql_exec (priv->db, stmt, NULL, NULL, error);
 				sqlite3_free (stmt);
@@ -1583,19 +1943,25 @@ e_book_backend_sqlitedb_new_contacts (EBookBackendSqliteDB *ebsdb,
 {
 	GSList *l;
 	gboolean success;
+	gchar *default_region;
 
 	g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITEDB (ebsdb), FALSE);
 	g_return_val_if_fail (folderid != NULL, FALSE);
 	g_return_val_if_fail (contacts != NULL, FALSE);
 
 	success = book_backend_sqlitedb_start_transaction (ebsdb, error);
+	default_region = e_phone_number_get_default_region ();
 
 	for (l = contacts; success && l != NULL; l = g_slist_next (l)) {
 		EContact *contact = (EContact *) l->data;
 
-		success = insert_contact (ebsdb, contact, folderid, replace_existing, error);
+		success = insert_contact (
+			ebsdb, contact, folderid, replace_existing,
+			default_region, error);
 	}
 
+	g_free (default_region);
+
 	if (success)
 		return book_backend_sqlitedb_commit_transaction (ebsdb, error);
 
@@ -2029,7 +2395,6 @@ store_data_to_vcard (gpointer ref,
 		/* Only UID & REV can be used to create contacts from the summary columns */
 		if (!g_ascii_strcasecmp (name[i], "uid")) {
 			e_contact_set (contact, E_CONTACT_UID, cols[i]);
-
 			search_data->uid = g_strdup (cols[i]);
 		} else if (!g_ascii_strcasecmp (name[i], "Rev")) {
 			e_contact_set (contact, E_CONTACT_REV, cols[i]);
@@ -2112,9 +2477,7 @@ e_book_backend_sqlitedb_get_vcard_string (EBookBackendSqliteDB *ebsdb,
 		}
 
 		local_with_all_required_fields = TRUE;
-
 	} else if (ebsdb->priv->store_vcard) {
-
 		stmt = sqlite3_mprintf (
 			"SELECT vcard FROM %Q WHERE uid = %Q", folderid, uid);
 		book_backend_sql_exec (
@@ -2188,6 +2551,41 @@ func_check_subset (ESExp *f,
 	return r;
 }
 
+static gint
+func_check_field_test (EBookBackendSqliteDB *ebsdb,
+                       const gchar *query_name,
+                       const gchar *query_value)
+{
+	gint i;
+	gint ret_val = 0;
+
+	if (ebsdb) {
+		for (i = 0; i < ebsdb->priv->n_summary_fields; i++) {
+			if (!g_ascii_strcasecmp (e_contact_field_name (ebsdb->priv->summary_fields[i].field), query_name)) {
+				ret_val |= CHECK_IS_SUMMARY;
+
+				if (ebsdb->priv->summary_fields[i].type == E_TYPE_CONTACT_ATTR_LIST)
+					ret_val |= CHECK_IS_LIST_ATTR;
+
+				break;
+			}
+		}
+	} else {
+		for (i = 0; i < G_N_ELEMENTS (default_summary_fields); i++) {
+			if (!g_ascii_strcasecmp (e_contact_field_name (default_summary_fields[i]), query_name)) {
+				ret_val |= CHECK_IS_SUMMARY;
+
+				if (e_contact_field_type (default_summary_fields[i]) == E_TYPE_CONTACT_ATTR_LIST)
+					ret_val |= CHECK_IS_LIST_ATTR;
+
+				break;
+			}
+		}
+	}
+
+	return ret_val;
+}
+
 static ESExpResult *
 func_check (struct _ESExp *f,
             gint argc,
@@ -2203,7 +2601,6 @@ func_check (struct _ESExp *f,
 	    && argv[1]->type == ESEXP_RES_STRING) {
 		const gchar *query_name = argv[0]->value.string;
 		const gchar *query_value = argv[1]->value.string;
-		gint i;
 
 		/* Special case, when testing the special symbolic 'any field' we can
 		 * consider it a summary query (it's similar to a 'no query'). */
@@ -2213,33 +2610,50 @@ func_check (struct _ESExp *f,
 			goto check_finish;
 		}
 
-		if (ebsdb) {
-			for (i = 0; i < ebsdb->priv->n_summary_fields; i++) {
-				if (!g_ascii_strcasecmp (e_contact_field_name (ebsdb->priv->summary_fields[i].field), query_name)) {
-					ret_val |= CHECK_IS_SUMMARY;
+		ret_val |= func_check_field_test (ebsdb, query_name, query_value);
+	} else if (argc == 3
+	    && argv[0]->type == ESEXP_RES_STRING
+	    && argv[1]->type == ESEXP_RES_STRING
+	    && argv[2]->type == ESEXP_RES_STRING) {
+		const gchar *query_name = argv[0]->value.string;
+		const gchar *query_value = argv[1]->value.string;
+		ret_val |= func_check_field_test (ebsdb, query_name, query_value);
+	}
 
-					if (ebsdb->priv->summary_fields[i].type == E_TYPE_CONTACT_ATTR_LIST)
-						ret_val |= CHECK_IS_LIST_ATTR;
-				}
-			}
-		} else {
-			for (i = 0; i < G_N_ELEMENTS (default_summary_fields); i++) {
+ check_finish:
+	r = e_sexp_result_new (f, ESEXP_RES_INT);
+	r->value.number = ret_val;
 
-				if (!g_ascii_strcasecmp (e_contact_field_name (default_summary_fields[i]), query_name)) {
-					ret_val |= CHECK_IS_SUMMARY;
+	return r;
+}
 
-					if (e_contact_field_type (default_summary_fields[i]) == E_TYPE_CONTACT_ATTR_LIST)
-						ret_val |= CHECK_IS_LIST_ATTR;
-				}
+static ESExpResult *
+func_check_phone (struct _ESExp         *f,
+                  gint                  argc,
+                  struct _ESExpResult **argv,
+                  gpointer              data)
+{
+	ESExpResult *const r = func_check (f, argc, argv, data);
+
+	if (r && r->value.number) {
+		GError *error = NULL;
+		const gchar *const query_value = argv[1]->value.string;
+		EPhoneNumber *const number = e_phone_number_from_string (
+				query_value, NULL, &error);
+
+		if (number == NULL) {
+			if (error) {
+				g_warning ("Bad value \"%s\" in phone number query: %s.",
+					   query_value, error->message);
+				g_clear_error (&error);
 			}
+
+			r->value.number = 0;
+		} else {
+			e_phone_number_free (number);
 		}
 	}
 
- check_finish:
-
-	r = e_sexp_result_new (f, ESEXP_RES_INT);
-	r->value.number = ret_val;
-
 	return r;
 }
 
@@ -2257,7 +2671,10 @@ static const struct {
 	{ "is", func_check, 0 },
 	{ "beginswith", func_check, 0 },
 	{ "endswith", func_check, 0 },
-	{ "exists", func_check, 0 }
+	{ "exists", func_check, 0 },
+	{ "eqphone", func_check_phone, 0 },
+	{ "eqphone_national", func_check_phone, 0 },
+	{ "eqphone_short", func_check_phone, 0 }
 };
 
 /**
@@ -2409,27 +2826,61 @@ typedef enum {
 	MATCH_CONTAINS,
 	MATCH_IS,
 	MATCH_BEGINS_WITH,
-	MATCH_ENDS_WITH
-} match_type;
+	MATCH_ENDS_WITH,
+	MATCH_PHONE_NUMBER,
+	MATCH_NATIONAL_PHONE_NUMBER,
+	MATCH_SHORT_PHONE_NUMBER
+} MatchType;
+
+typedef enum {
+	CONVERT_NOTHING   =  0,
+	CONVERT_NORMALIZE = (1 << 0),
+	CONVERT_REVERSE   = (1 << 1),
+	CONVERT_PHONE     = (1 << 2)
+} ConvertFlags;
 
 static gchar *
-convert_string_value (const gchar *value,
-                      gboolean normalize,
-                      gboolean reverse,
-                      match_type match)
+extract_digits (const gchar *normal)
+{
+	gchar *digits = g_new (char, strlen (normal) + 1);
+	const gchar *src = normal;
+	gchar *dst = digits;
+
+	/* extract digits also considering eastern arabic numerals */
+	for (src = normal; *src; src = g_utf8_next_char (src)) {
+		const gunichar uc = g_utf8_get_char_validated (src, -1);
+		const gint value = g_unichar_digit_value (uc);
+
+		if (uc == -1)
+			break;
+
+		if (value != -1)
+			*dst++ = '0' + value;
+	}
+
+	*dst = '\0';
+
+	return digits;
+}
+
+static gchar *
+convert_string_value (EBookBackendSqliteDB *ebsdb,
+                      const gchar          *value,
+                      ConvertFlags          flags,
+                      MatchType             match)
 {
 	GString *str;
 	size_t len;
 	gchar c;
 	gboolean escape_modifier_needed = FALSE;
 	const gchar *escape_modifier = " ESCAPE '^'";
-	gchar *reverse_val = NULL;
+	gchar *computed = NULL;
 	gchar *normal;
 	const gchar *ptr;
 
 	g_return_val_if_fail (value != NULL, NULL);
 
-	if (normalize)
+	if (flags & CONVERT_NORMALIZE)
 		normal = e_util_utf8_normalize (value);
 	else
 		normal = g_strdup (value);
@@ -2445,17 +2896,28 @@ convert_string_value (const gchar *value,
 	switch (match) {
 	case MATCH_CONTAINS:
 	case MATCH_ENDS_WITH:
+	case MATCH_SHORT_PHONE_NUMBER:
 		g_string_append_c (str, '%');
 		break;
 
 	case MATCH_BEGINS_WITH:
 	case MATCH_IS:
+	case MATCH_PHONE_NUMBER:
+	case MATCH_NATIONAL_PHONE_NUMBER:
 		break;
 	}
 
-	if (reverse) {
-		reverse_val = g_utf8_strreverse (normal, -1);
-		ptr = reverse_val;
+	if (flags & CONVERT_REVERSE) {
+		computed = g_utf8_strreverse (normal, -1);
+		ptr = computed;
+	} else if (flags & CONVERT_PHONE) {
+		if (match == MATCH_NATIONAL_PHONE_NUMBER) {
+			computed = convert_phone_national (normal, NULL);
+		} else {
+			computed = convert_phone (normal, NULL);
+		}
+
+		ptr = computed;
 	} else {
 		ptr = normal;
 	}
@@ -2479,6 +2941,9 @@ convert_string_value (const gchar *value,
 
 	case MATCH_ENDS_WITH:
 	case MATCH_IS:
+	case MATCH_PHONE_NUMBER:
+	case MATCH_NATIONAL_PHONE_NUMBER:
+	case MATCH_SHORT_PHONE_NUMBER:
 		break;
 	}
 
@@ -2487,7 +2952,7 @@ convert_string_value (const gchar *value,
 	if (escape_modifier_needed)
 		g_string_append (str, escape_modifier);
 
-	g_free (reverse_val);
+	g_free (computed);
 	g_free (normal);
 
 	return g_string_free (str, FALSE);
@@ -2495,16 +2960,18 @@ convert_string_value (const gchar *value,
 
 static gchar *
 field_name_and_query_term (EBookBackendSqliteDB *ebsdb,
-                           const gchar *folderid,
-                           const gchar *field_name_input,
-                           const gchar *query_term_input,
-                           match_type match,
-                           gboolean *is_list_attr,
-                           gchar **query_term)
+			   const gchar          *folderid,
+			   const gchar          *field_name_input,
+			   const gchar          *query_term_input,
+			   MatchType             match,
+			   gboolean             *is_list_attr,
+			   gchar               **query_term,
+			   gchar               **extra_term)
 {
 	gint summary_index;
 	gchar *field_name = NULL;
 	gchar *value = NULL;
+	gchar *extra = NULL;
 	gboolean list_attr = FALSE;
 
 	summary_index = summary_index_from_field_name (ebsdb, field_name_input);
@@ -2512,15 +2979,23 @@ field_name_and_query_term (EBookBackendSqliteDB *ebsdb,
 	if (summary_index < 0) {
 		g_critical ("Only summary field matches should be converted to sql queries");
 		field_name = g_strconcat (folderid, ".", field_name_input, NULL);
-		value = convert_string_value (query_term_input, TRUE, FALSE, match);
+		value = convert_string_value (ebsdb, query_term_input, CONVERT_NORMALIZE, match);
 	} else {
 		gboolean suffix_search = FALSE;
+		gboolean phone_search = FALSE;
 
 		/* If its a suffix search and we have reverse data to search... */
 		if (match == MATCH_ENDS_WITH &&
 		    (ebsdb->priv->summary_fields[summary_index].index & INDEX_SUFFIX) != 0)
 			suffix_search = TRUE;
 
+		/* If its a phone-number search and we have E.164 data to search... */
+		else if ((match == MATCH_PHONE_NUMBER ||
+				match == MATCH_NATIONAL_PHONE_NUMBER ||
+				match == MATCH_SHORT_PHONE_NUMBER) &&
+		    (ebsdb->priv->summary_fields[summary_index].index & INDEX_PHONE) != 0)
+			phone_search = TRUE;
+
 		/* Or also if its an exact match, and we *only* have reverse data which is indexed,
 		 * then prefer the indexed reverse search. */
 		else if (match == MATCH_IS &&
@@ -2546,14 +3021,46 @@ field_name_and_query_term (EBookBackendSqliteDB *ebsdb,
 			if (ebsdb->priv->summary_fields[summary_index].field == E_CONTACT_UID ||
 			    ebsdb->priv->summary_fields[summary_index].field == E_CONTACT_REV)
 				value = convert_string_value (
-					query_term_input, FALSE, TRUE,
+					ebsdb, query_term_input, CONVERT_REVERSE,
 					(match == MATCH_ENDS_WITH) ? MATCH_BEGINS_WITH : MATCH_IS);
 			else
 				value = convert_string_value (
-					query_term_input, TRUE, TRUE,
+					ebsdb, query_term_input, CONVERT_REVERSE | CONVERT_NORMALIZE,
 					(match == MATCH_ENDS_WITH) ? MATCH_BEGINS_WITH : MATCH_IS);
-		} else {
+		} else if (phone_search) {
+			/* Special case for E.164 matching:
+			 *  o Normalize the string
+			 *  o Check the E.164 column instead
+			 */
+			const gint country_code = e_phone_number_get_country_code_for_region (NULL);
+
+			if (ebsdb->priv->summary_fields[summary_index].type == E_TYPE_CONTACT_ATTR_LIST) {
+				field_name = g_strdup ("multi.value_phone");
+				list_attr = TRUE;
+			} else {
+				field_name = g_strdup_printf ("summary.%s_phone",
+					ebsdb->priv->summary_fields[summary_index].dbname);
+			}
+
+			if (match == MATCH_PHONE_NUMBER) {
+				extra = g_strdup_printf (" COLLATE ixphone_%d", country_code);
+
+				value = convert_string_value (
+					ebsdb, query_term_input,
+					CONVERT_NORMALIZE | CONVERT_PHONE, match);
+			} else {
+				gchar *const digits = extract_digits (query_term_input);
+				extra = g_strdup (" COLLATE ixphone_nn");
 
+				if (match == MATCH_NATIONAL_PHONE_NUMBER) {
+					value = convert_string_value (ebsdb, digits, CONVERT_PHONE, MATCH_NATIONAL_PHONE_NUMBER);
+				} else {
+					value = convert_string_value (ebsdb, digits, CONVERT_NOTHING, MATCH_ENDS_WITH);
+				}
+
+				g_free (digits);
+			}
+		} else {
 			if (ebsdb->priv->summary_fields[summary_index].type == E_TYPE_CONTACT_ATTR_LIST) {
 				field_name = g_strdup ("multi.value");
 				list_attr = TRUE;
@@ -2564,9 +3071,9 @@ field_name_and_query_term (EBookBackendSqliteDB *ebsdb,
 
 			if (ebsdb->priv->summary_fields[summary_index].field == E_CONTACT_UID ||
 			    ebsdb->priv->summary_fields[summary_index].field == E_CONTACT_REV)
-				value = convert_string_value (query_term_input, FALSE, FALSE, match);
+				value = convert_string_value (ebsdb, query_term_input, CONVERT_NOTHING, match);
 			else
-				value = convert_string_value (query_term_input, TRUE, FALSE, match);
+				value = convert_string_value (ebsdb, query_term_input, CONVERT_NORMALIZE, match);
 		}
 	}
 
@@ -2575,6 +3082,9 @@ field_name_and_query_term (EBookBackendSqliteDB *ebsdb,
 
 	*query_term = value;
 
+	if (extra_term)
+		*extra_term = extra;
+
 	return field_name;
 }
 
@@ -2583,12 +3093,31 @@ typedef struct {
 	const gchar          *folderid;
 } BuildQueryData;
 
+static const gchar *
+field_oper (MatchType match)
+{
+	switch (match) {
+	case MATCH_IS:
+	case MATCH_PHONE_NUMBER:
+	case MATCH_NATIONAL_PHONE_NUMBER:
+		return "=";
+
+	case MATCH_CONTAINS:
+	case MATCH_BEGINS_WITH:
+	case MATCH_ENDS_WITH:
+	case MATCH_SHORT_PHONE_NUMBER:
+		break;
+	}
+
+	return "LIKE";
+}
+
 static ESExpResult *
 convert_match_exp (struct _ESExp *f,
                    gint argc,
                    struct _ESExpResult **argv,
                    gpointer data,
-                   match_type match)
+                   MatchType match)
 {
 	BuildQueryData *qdata = (BuildQueryData *) data;
 	EBookBackendSqliteDB *ebsdb = qdata->ebsdb;
@@ -2603,11 +3132,8 @@ convert_match_exp (struct _ESExp *f,
 		field = argv[0]->value.string;
 
 		if (argv[1]->type == ESEXP_RES_STRING && argv[1]->value.string[0] != 0) {
-			const gchar *oper = "LIKE";
-			gchar *field_name, *query_term;
-
-			if (match == MATCH_IS)
-				oper = "=";
+			const gchar *const oper = field_oper (match);
+			gchar *field_name, *query_term, *extra_term;
 
 			if (!g_ascii_strcasecmp (field, "full_name")) {
 				GString *names = g_string_new (NULL);
@@ -2615,7 +3141,7 @@ convert_match_exp (struct _ESExp *f,
 				field_name = field_name_and_query_term (
 					ebsdb, qdata->folderid, "full_name",
 					argv[1]->value.string,
-					match, NULL, &query_term);
+					match, NULL, &query_term, NULL);
 				g_string_append_printf (
 					names, "(%s IS NOT NULL AND %s %s %s)",
 					field_name, field_name, oper, query_term);
@@ -2623,11 +3149,10 @@ convert_match_exp (struct _ESExp *f,
 				g_free (query_term);
 
 				if (summary_dbname_from_field (ebsdb, E_CONTACT_FAMILY_NAME)) {
-
 					field_name = field_name_and_query_term (
 						ebsdb, qdata->folderid, "family_name",
 						argv[1]->value.string,
-						match, NULL, &query_term);
+						match, NULL, &query_term, NULL);
 					g_string_append_printf (
 						names, " OR (%s IS NOT NULL AND %s %s %s)",
 						field_name, field_name, oper, query_term);
@@ -2636,11 +3161,10 @@ convert_match_exp (struct _ESExp *f,
 				}
 
 				if (summary_dbname_from_field (ebsdb, E_CONTACT_GIVEN_NAME)) {
-
 					field_name = field_name_and_query_term (
 						ebsdb, qdata->folderid, "given_name",
 						argv[1]->value.string,
-						match, NULL, &query_term);
+						match, NULL, &query_term, NULL);
 					g_string_append_printf (
 						names, " OR (%s IS NOT NULL AND %s %s %s)",
 						field_name, field_name, oper, query_term);
@@ -2649,11 +3173,10 @@ convert_match_exp (struct _ESExp *f,
 				}
 
 				if (summary_dbname_from_field (ebsdb, E_CONTACT_NICKNAME)) {
-
 					field_name = field_name_and_query_term (
 						ebsdb, qdata->folderid, "nickname",
 						argv[1]->value.string,
-						match, NULL, &query_term);
+						match, NULL, &query_term, NULL);
 					g_string_append_printf (
 						names, " OR (%s IS NOT NULL AND %s %s %s)",
 						field_name, field_name, oper, query_term);
@@ -2665,29 +3188,44 @@ convert_match_exp (struct _ESExp *f,
 				g_string_free (names, FALSE);
 
 			} else {
+				const gchar *const query_locale =
+					argc > 2 && argv[2]->type == ESEXP_RES_STRING ?
+					argv[2]->value.string : NULL;
+				const gchar *const saved_locale = query_locale ?
+					setlocale (LC_ADDRESS, query_locale) : NULL;
+
 				gboolean is_list = FALSE;
 
 				/* This should ideally be the only valid case from all the above special casing, but oh well... */
 				field_name = field_name_and_query_term (
 					ebsdb, qdata->folderid, field,
 					argv[1]->value.string,
-					match, &is_list, &query_term);
+					match, &is_list, &query_term, &extra_term);
 
+				/* User functions like eqphone_national() cannot utilize indexes. Therefore we
+				 * should reduce the result set first before applying any user functions. This
+				 * is done by applying a seemingly redundant suffix match first.
+				 */
 				if (is_list) {
 					gchar *tmp;
 
 					tmp = sqlite3_mprintf ("summary.uid = multi.uid AND multi.field = %Q", field);
 					str = g_strdup_printf (
-						"(%s AND %s %s %s)",
-						tmp, field_name, oper, query_term);
+						"(%s AND (%s %s %s%s))",
+						tmp, field_name, oper, query_term,
+						extra_term ? extra_term : "");
 					sqlite3_free (tmp);
 				} else
 					str = g_strdup_printf (
-						"(%s IS NOT NULL AND %s %s %s)",
-						field_name, field_name, oper, query_term);
+						"(%s IS NOT NULL AND (%s %s %s%s))",
+						field_name, field_name, oper, query_term,
+						extra_term ? extra_term : "");
 
 				g_free (field_name);
 				g_free (query_term);
+
+				if (saved_locale)
+					setlocale (LC_ADDRESS, saved_locale);
 			}
 		}
 	}
@@ -2734,6 +3272,33 @@ func_endswith (struct _ESExp *f,
 	return convert_match_exp (f, argc, argv, data, MATCH_ENDS_WITH);
 }
 
+static ESExpResult *
+func_eqphone (struct _ESExp *f,
+              gint argc,
+              struct _ESExpResult **argv,
+              gpointer data)
+{
+	return convert_match_exp (f, argc, argv, data, MATCH_PHONE_NUMBER);
+}
+
+static ESExpResult *
+func_eqphone_national (struct _ESExp *f,
+                       gint argc,
+                       struct _ESExpResult **argv,
+                       gpointer data)
+{
+	return convert_match_exp (f, argc, argv, data, MATCH_NATIONAL_PHONE_NUMBER);
+}
+
+static ESExpResult *
+func_eqphone_short (struct _ESExp *f,
+                    gint argc,
+                    struct _ESExpResult **argv,
+                    gpointer data)
+{
+	return convert_match_exp (f, argc, argv, data, MATCH_SHORT_PHONE_NUMBER);
+}
+
 /* 'builtin' functions */
 static struct {
 	const gchar *name;
@@ -2747,6 +3312,9 @@ static struct {
 	{ "is", func_is, 0 },
 	{ "beginswith", func_beginswith, 0 },
 	{ "endswith", func_endswith, 0 },
+	{ "eqphone", func_eqphone, 0 },
+	{ "eqphone_national", func_eqphone_national, 0 },
+	{ "eqphone_short", func_eqphone_short, 0 }
 };
 
 static gchar *
@@ -3822,3 +4390,49 @@ e_book_backend_sqlitedb_remove (EBookBackendSqliteDB *ebsdb,
 
 	return TRUE;
 }
+
+static void
+destroy_search_data (gpointer data)
+{
+	e_book_backend_sqlitedb_search_data_free (data);
+}
+
+static gboolean
+upgrade_contacts_table (EBookBackendSqliteDB  *ebsdb,
+                        const gchar           *folderid,
+                        GError               **error)
+{
+	gchar *stmt;
+	gboolean success = FALSE;
+	GSList *vcard_data = NULL;
+	GSList *l;
+	gchar *default_region;
+
+	stmt = sqlite3_mprintf ("SELECT uid, vcard, NULL FROM %Q", folderid);
+	success = book_backend_sql_exec (
+		ebsdb->priv->db, stmt, addto_vcard_list_cb, &vcard_data, error);
+	sqlite3_free (stmt);
+
+	if (vcard_data == NULL)
+		return TRUE;
+
+	g_message ("The phone number indexes' format has changed. Rebuilding them.");
+	default_region = e_phone_number_get_default_region ();
+
+	for (l = vcard_data; success && l; l = l->next) {
+		EbSdbSearchData *const s_data = l->data;
+		EContact *contact = e_contact_new_from_vcard_with_uid (s_data->vcard, s_data->uid);
+
+		if (contact == NULL)
+			continue;
+
+		success = insert_contact (ebsdb, contact, folderid, TRUE, default_region, error);
+
+		g_object_unref (contact);
+	}
+
+	g_slist_free_full (vcard_data, destroy_search_data);
+	g_free (default_region);
+
+	return success;
+}
diff --git a/tests/libebook/client/test-client-custom-summary.c b/tests/libebook/client/test-client-custom-summary.c
index 7cc74d7..7e71e2e 100644
--- a/tests/libebook/client/test-client-custom-summary.c
+++ b/tests/libebook/client/test-client-custom-summary.c
@@ -23,18 +23,22 @@
 #include <config.h>
 #include <stdlib.h>
 #include <libebook/libebook.h>
+#include <locale.h>
 
 #include "client-test-utils.h"
 #include "e-test-server-utils.h"
 
 typedef struct {
-	ETestServerClosure closure;
+	ETestServerClosure parent;
 	EBookQuery *query;
 	gint num_contacts;
 } ClientTestData;
 
-/* Cleanup the closures, just for the hell of it... */
-static GList *closures = NULL;
+typedef struct {
+	ETestServerFixture parent;
+	EContact *contacts[6];
+	ClientTestData *data;
+} ClientTestFixture;
 
 static void
 client_test_data_free (gpointer p)
@@ -43,6 +47,7 @@ client_test_data_free (gpointer p)
 
 	if (data->query)
 		e_book_query_unref (data->query);
+
 	g_slice_free (ClientTestData, data);
 }
 
@@ -65,6 +70,9 @@ setup_custom_book (ESource *scratch,
 	e_source_backend_summary_setup_set_indexed_fields (
 		setup,
 		E_CONTACT_TEL, E_BOOK_INDEX_SUFFIX,
+#ifdef ENABLE_PHONENUMBER
+		E_CONTACT_TEL, E_BOOK_INDEX_PHONE,
+#endif /* ENABLE_PHONENUMBER */
 		E_CONTACT_FULL_NAME, E_BOOK_INDEX_PREFIX,
 		E_CONTACT_FULL_NAME, E_BOOK_INDEX_SUFFIX,
 		E_CONTACT_FAMILY_NAME, E_BOOK_INDEX_PREFIX,
@@ -73,6 +81,30 @@ setup_custom_book (ESource *scratch,
 }
 
 static void
+client_test_setup (ClientTestFixture *fixture,
+		   gconstpointer user_data)
+{
+	e_test_server_utils_setup (&fixture->parent, user_data);
+	fixture->data = (gpointer) user_data;
+}
+
+static void
+client_test_teardown (ClientTestFixture *fixture,
+		      gconstpointer user_data)
+{
+	gint i;
+
+	for (i = 0; i < G_N_ELEMENTS (fixture->contacts); ++i) {
+		if (fixture->contacts[i])
+			g_object_unref (fixture->contacts[i]);
+	}
+
+	client_test_data_free (fixture->data);
+
+	e_test_server_utils_teardown (&fixture->parent, user_data);
+}
+
+static void
 add_client_test (const gchar *path,
                  gpointer func,
                  EBookQuery *query,
@@ -80,41 +112,49 @@ add_client_test (const gchar *path,
 {
 	ClientTestData *data = g_slice_new0 (ClientTestData);
 
-	data->closure.type = E_TEST_SERVER_ADDRESS_BOOK;
-	data->closure.customize = setup_custom_book;
+	data->parent.type = E_TEST_SERVER_ADDRESS_BOOK;
+	data->parent.customize = setup_custom_book;
 	data->query = query;
 	data->num_contacts = num_contacts;
 
-	g_test_add (path, ETestServerFixture, data, e_test_server_utils_setup, func, e_test_server_utils_teardown);
-	closures = g_list_prepend (closures, data);
+	g_test_add (
+		path, ClientTestFixture, data,
+		client_test_setup, func, client_test_teardown);
 }
 
 static void
-setup_book (EBookClient *book_client)
+setup_book (ClientTestFixture *fixture)
 {
+	EContact **it = fixture->contacts;
+	EBookClient *book_client;
+
+	book_client = E_TEST_SERVER_UTILS_SERVICE (fixture, EBookClient);
+
 	/* Add contacts */
-	if (!add_contact_from_test_case_verify (book_client, "custom-1", NULL) ||
-	    !add_contact_from_test_case_verify (book_client, "custom-2", NULL) ||
-	    !add_contact_from_test_case_verify (book_client, "custom-3", NULL) ||
-	    !add_contact_from_test_case_verify (book_client, "custom-4", NULL) ||
-	    !add_contact_from_test_case_verify (book_client, "custom-5", NULL) ||
-	    !add_contact_from_test_case_verify (book_client, "custom-6", NULL)) {
+	if (!add_contact_from_test_case_verify (book_client, "custom-1", it++) ||
+	    !add_contact_from_test_case_verify (book_client, "custom-2", it++) ||
+	    !add_contact_from_test_case_verify (book_client, "custom-3", it++) ||
+	    !add_contact_from_test_case_verify (book_client, "custom-4", it++) ||
+	    !add_contact_from_test_case_verify (book_client, "custom-5", it++) ||
+	    !add_contact_from_test_case_verify (book_client, "custom-6", it++)) {
 		g_error ("Failed to add contacts");
 	}
+
+	g_assert_cmpint (it - fixture->contacts, <=, G_N_ELEMENTS (fixture->contacts));
 }
 
 static void
-search_test (ETestServerFixture *fixture,
+search_test (ClientTestFixture *fixture,
              gconstpointer user_data)
 {
+	const ClientTestData *const data = user_data;
 	EBookClient *book_client;
 	GSList *results = NULL;
 	GError *error = NULL;
 	gchar *sexp;
-	const ClientTestData *const data = user_data;
 
 	book_client = E_TEST_SERVER_UTILS_SERVICE (fixture, EBookClient);
-	setup_book (book_client);
+	setup_book (fixture);
 
 	sexp = e_book_query_to_string (data->query);
 
@@ -128,17 +168,17 @@ search_test (ETestServerFixture *fixture,
 }
 
 static void
-uid_test (ETestServerFixture *fixture,
+uid_test (ClientTestFixture *fixture,
           gconstpointer user_data)
 {
+	const ClientTestData *const data = user_data;
 	EBookClient *book_client;
 	GSList *results = NULL;
 	GError *error = NULL;
 	gchar *sexp;
-	const ClientTestData *const data = user_data;
 
 	book_client = E_TEST_SERVER_UTILS_SERVICE (fixture, EBookClient);
-	setup_book (book_client);
+	setup_book (fixture);
 
 	sexp = e_book_query_to_string (data->query);
 
@@ -151,6 +191,41 @@ uid_test (ETestServerFixture *fixture,
 	g_free (sexp);
 }
 
+static void
+locale_change_test (ClientTestFixture *fixture,
+                    gconstpointer user_data)
+{
+	EBookClient *book_client;
+	GSList *results = NULL;
+	GError *error = NULL;
+
+	book_client = E_TEST_SERVER_UTILS_SERVICE (fixture, EBookClient);
+	setup_book (fixture);
+
+	if (!e_book_client_get_contacts_uids_sync (
+		book_client, "(eqphone \"phone\" \"221-5423789\" \"en_US.UTF-8\")",
+				&results, NULL, &error)) {
+		g_error ("get contact uids: %s", error->message);
+	}
+
+	g_assert_cmpint (g_slist_length (results), ==, 1);
+
+	g_assert_cmpstr (
+		results->data, ==,
+		e_contact_get_const (fixture->contacts[0], E_CONTACT_UID));
+
+	e_util_free_string_slist (results);
+
+	if (!e_book_client_get_contacts_uids_sync (
+		book_client, "(eqphone \"phone\" \"221-5423789\" \"en_GB.UTF-8\")",
+				&results, NULL, &error)) {
+		g_error ("get contact uids: %s", error->message);
+	}
+
+	g_assert_cmpint (g_slist_length (results), ==, 0);
+	e_util_free_string_slist (results);
+}
+
 gint
 main (gint argc,
       gchar **argv)
@@ -162,6 +237,8 @@ main (gint argc,
 #endif
 	g_test_init (&argc, &argv, NULL);
 
+	setlocale (LC_ALL, "en_US.UTF-8");
+
 	/* Add search tests that fetch contacts */
 	add_client_test (
 		"/client/search/exact/fn", search_test,
@@ -187,20 +264,38 @@ main (gint argc,
 		"/client/search/suffix/email", search_test,
 		e_book_query_field_test (E_CONTACT_EMAIL, E_BOOK_QUERY_ENDS_WITH, "jackson.com"),
 		2);
+
 #ifdef ENABLE_PHONENUMBER
+
+	/* field based phone number queries do an index lookup */
 	add_client_test (
 		"/client/search/eqphone/exact/phone", search_test,
-		e_book_query_vcard_field_test(EVC_TEL, E_BOOK_QUERY_EQUALS_PHONE_NUMBER, "+1 221.542.3789"),
+		e_book_query_field_test(E_CONTACT_TEL, E_BOOK_QUERY_EQUALS_PHONE_NUMBER, "+1 221.542.3789"),
 		1);
 	add_client_test (
 		"/client/search/eqphone/national/phone", search_test,
-		e_book_query_vcard_field_test(EVC_TEL, E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER, "221.542.3789"),
+		e_book_query_field_test(E_CONTACT_TEL, E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER, "221.542.3789"),
 		1);
 	add_client_test (
 		"/client/search/eqphone/short/phone", search_test,
+		e_book_query_field_test(E_CONTACT_TEL, E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER, "5423789"),
+		1);
+
+	/* vCard based phone number queries do a table scan */
+	add_client_test (
+		"/client/search/eqphone/exact/tel", search_test,
+		e_book_query_vcard_field_test(EVC_TEL, E_BOOK_QUERY_EQUALS_PHONE_NUMBER, "+1 221.542.3789"),
+		1);
+	add_client_test (
+		"/client/search/eqphone/national/tel", search_test,
+		e_book_query_vcard_field_test(EVC_TEL, E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER, "221.542.3789"),
+		1);
+	add_client_test (
+		"/client/search/eqphone/short/tel", search_test,
 		e_book_query_vcard_field_test(EVC_TEL, E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER, "5423789"),
 		1);
-#endif
+
+#endif /* ENABLE_PHONENUMBER */
 
 	/* Add search tests that fetch uids */
 	add_client_test (
@@ -228,9 +323,15 @@ main (gint argc,
 		e_book_query_field_test (E_CONTACT_EMAIL, E_BOOK_QUERY_ENDS_WITH, "jackson.com"),
 		2);
 
-	ret = e_test_server_utils_run ();
+#ifdef ENABLE_PHONENUMBER
 
-	g_list_free_full (closures, client_test_data_free);
+	add_client_test (
+		"/client/search-uid/eqphone/locale-change", locale_change_test,
+		NULL, 0);
+
+#endif /* ENABLE_PHONENUMBER */
+
+	ret = e_test_server_utils_run ();
 
 	return ret;
 }
diff --git a/tests/libebook/test-query.c b/tests/libebook/test-query.c
index f2a155b..53fd7cd 100644
--- a/tests/libebook/test-query.c
+++ b/tests/libebook/test-query.c
@@ -1,4 +1,6 @@
+#include <locale.h>
 #include <string.h>
+
 #include <libebook/libebook.h>
 
 #define QUERY_STRING1
@@ -6,7 +8,8 @@
 
 typedef struct {
 	EBookQuery *query;
-	char *sexp;
+	gchar *locale;
+	gchar *sexp;
 } TestData;
 
 static void
@@ -34,6 +37,7 @@ test_query (gconstpointer data)
 	EBookQuery *query;
 	char *sexp;
 
+	setlocale (LC_ADDRESS, test->locale);
 	sexp = e_book_query_to_string (test->query);
 	normalize_space (sexp);
 
@@ -41,6 +45,8 @@ test_query (gconstpointer data)
 	g_free (sexp);
 
 	query = e_book_query_from_string (test->sexp);
+	g_assert (query != NULL);
+
 	sexp = e_book_query_to_string (query);
 	e_book_query_unref (query);
 	normalize_space (sexp);
@@ -56,6 +62,7 @@ test_data_free (gpointer data)
 
 	e_book_query_unref (test->query);
 	g_free (test->sexp);
+	g_free (test->locale);
 
 	g_slice_free (TestData, test);
 }
@@ -67,6 +74,7 @@ add_query_test (const char *path,
 {
 	TestData *data = g_slice_new (TestData);
 
+	data->locale = g_strdup (setlocale (LC_ADDRESS, NULL));
 	data->sexp = g_strdup (sexp);
 	data->query = query;
 
@@ -81,6 +89,8 @@ main (gint argc,
 
 	g_test_init (&argc, &argv, NULL);
 
+	setlocale (LC_ADDRESS, "en_US.UTF-8");
+
 	add_query_test ("/libebook/test-query/sexp/all",
 	                e_book_query_any_field_contains (NULL),
 	                "(contains \"x-evolution-any-field\" \"\")");
@@ -137,8 +147,7 @@ main (gint argc,
 	                                         "5423789"),
 	                "(endswith \"phone\" \"5423789\")");
 
-	add_query_test ("/libebook/test-query/sexp/eqphone",
-
+	add_query_test ("/libebook/test-query/sexp/eqphone/us",
 	                e_book_query_orv (e_book_query_field_test (E_CONTACT_TEL,
 	                                                           E_BOOK_QUERY_EQUALS_PHONE_NUMBER,
 	                                                           "+1-2215423789"),
@@ -150,10 +159,30 @@ main (gint argc,
 	                                                           "5423789"),
 	                                  NULL),
 
-	                "(or (eqphone \"phone\" \"+1-2215423789\")"
-	                   " (eqphone_national \"phone\" \"2215423789\")"
-	                   " (eqphone_short \"phone\" \"5423789\")"
+	                "(or (eqphone \"phone\" \"+1-2215423789\" \"en_US.UTF-8\")"
+	                   " (eqphone_national \"phone\" \"2215423789\" \"en_US.UTF-8\")"
+	                   " (eqphone_short \"phone\" \"5423789\" \"en_US.UTF-8\")"
 	                   " )");
 
+	setlocale (LC_ADDRESS, "en_GB.UTF-8");
+
+	add_query_test ("/libebook/test-query/sexp/eqphone/gb",
+			e_book_query_orv (e_book_query_field_test (E_CONTACT_TEL,
+								   E_BOOK_QUERY_EQUALS_PHONE_NUMBER,
+								   "+1-2215423789"),
+					  e_book_query_field_test (E_CONTACT_TEL,
+								   E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER,
+								   "2215423789"),
+					  e_book_query_field_test (E_CONTACT_TEL,
+								   E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER,
+								   "5423789"),
+					  NULL),
+
+			"(or (eqphone \"phone\" \"+1-2215423789\" \"en_GB.UTF-8\")"
+			   " (eqphone_national \"phone\" \"2215423789\" \"en_GB.UTF-8\")"
+			   " (eqphone_short \"phone\" \"5423789\" \"en_GB.UTF-8\")"
+			   " )");
+
+
 	return g_test_run ();
 }
