[evolution-data-server/openismus-work] sqlitedb: Permit indexing of phone numbers



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

    sqlitedb: Permit indexing of phone numbers

 .../libebook-contacts/e-book-contacts-types.h      |    7 +-
 .../e-source-backend-summary-setup.c               |   11 +-
 .../libedata-book/e-book-backend-sqlitedb.c        |  694 ++++++++++++++++----
 tests/libebook/client/test-client-custom-summary.c |   14 +-
 4 files changed, 588 insertions(+), 138 deletions(-)
---
diff --git a/addressbook/libebook-contacts/e-book-contacts-types.h b/addressbook/libebook-contacts/e-book-contacts-types.h
index a374a16..4bad721 100644
--- a/addressbook/libebook-contacts/e-book-contacts-types.h
+++ b/addressbook/libebook-contacts/e-book-contacts-types.h
@@ -99,12 +99,17 @@ 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 code "+999" currently is not assigned.
  *
  * The type of index defined by e_source_backend_summary_setup_set_indexed_fields()
  */
 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-contacts/e-source-backend-summary-setup.c b/addressbook/libebook-contacts/e-source-backend-summary-setup.c
index 1081830..67a85e0 100644
--- a/addressbook/libebook-contacts/e-source-backend-summary-setup.c
+++ b/addressbook/libebook-contacts/e-source-backend-summary-setup.c
@@ -471,7 +471,6 @@ e_source_backend_summary_setup_get_indexed_fields (ESourceBackendSummarySetup  *
 	ret_types  = g_new0 (EBookIndexType, ret_n_fields);
 
 	for (i = 0; i < ret_n_fields && malformed == FALSE; i++) {
-
 		index_split = g_strsplit (split[i], ",", 2);
 
 		if (index_split[0] && index_split[1]) {
@@ -527,9 +526,11 @@ e_source_backend_summary_setup_get_indexed_fields (ESourceBackendSummarySetup  *
  * 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_E164 index will optimize
+ * #E_BOOK_QUERY_EQUALS_PHONE_NUMBER searches.
  *
  * Since: 3.8
  */
@@ -644,7 +645,7 @@ e_source_backend_summary_setup_set_indexed_fields (ESourceBackendSummarySetup  *
 		}
 
 		if (!first)
-			g_string_append_c (string, ':'); 
+			g_string_append_c (string, ':');
 		else
 			first = FALSE;
 
diff --git a/addressbook/libedata-book/e-book-backend-sqlitedb.c b/addressbook/libedata-book/e-book-backend-sqlitedb.c
index 0bb2d98..4f4761d 100644
--- a/addressbook/libedata-book/e-book-backend-sqlitedb.c
+++ b/addressbook/libedata-book/e-book-backend-sqlitedb.c
@@ -23,6 +23,7 @@
 #include <ctype.h>
 #include <stdlib.h>
 #include <errno.h>
+#include <langinfo.h>
 
 #include <glib/gi18n.h>
 #include <glib/gstdio.h>
@@ -40,7 +41,7 @@
 #define d(x)
 
 #define DB_FILENAME "contacts.db"
-#define FOLDER_VERSION 3
+#define FOLDER_VERSION 4
 
 #define READER_LOCK(ebsdb) g_static_rw_lock_reader_lock (&ebsdb->priv->rwlock)
 #define READER_UNLOCK(ebsdb) g_static_rw_lock_reader_unlock (&ebsdb->priv->rwlock)
@@ -49,7 +50,8 @@
 
 typedef enum {
 	INDEX_PREFIX = (1 << 0),
-	INDEX_SUFFIX = (1 << 1)
+	INDEX_SUFFIX = (1 << 1),
+	INDEX_PHONE  = (1 << 2),
 } IndexFlags;
 
 typedef struct {
@@ -73,8 +75,9 @@ 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;
+
+	gchar          *country_code;
 };
 
 G_DEFINE_TYPE (EBookBackendSqliteDB, e_book_backend_sqlitedb, G_TYPE_OBJECT)
@@ -120,6 +123,10 @@ static gboolean append_summary_field (GArray         *array,
 				      gboolean       *have_attr_list,
 				      GError        **error);
 
+static gboolean validate_county_code (EBookBackendSqliteDB  *ebsdb,
+                                      const gchar           *folderid,
+                                      GError               **error);
+
 static const gchar *
 summary_dbname_from_field (EBookBackendSqliteDB *ebsdb, EContactField field)
 {
@@ -207,6 +214,7 @@ e_book_backend_sqlitedb_finalize (GObject *object)
 
 	g_free (priv->path);
 	g_free (priv->summary_fields);
+	g_free (priv->country_code);
 
 	g_mutex_free (priv->in_transaction_lock);
 
@@ -473,7 +481,8 @@ create_folders_table (EBookBackendSqliteDB *ebsdb,
 		" version INTEGER,"
 		"  revision TEXT,"
 		" multivalues TEXT,"
-		"  reverse_multivalues INTEGER )";
+		"  reverse_multivalues INTEGER,"
+		" countrycode VARCHAR(2) )";
 
 	if (!book_backend_sqlitedb_start_transaction (ebsdb, error))
 		return;
@@ -513,7 +522,6 @@ create_folders_table (EBookBackendSqliteDB *ebsdb,
 	/* Upgrade DB to version 3, add multivalues introspection columns
 	 */
 	if (!err && version >= 1 && version < 3) {
-
 		stmt = "ALTER TABLE folders ADD COLUMN multivalues TEXT";
 		book_backend_sql_exec (
 			ebsdb->priv->db, stmt, NULL, NULL, &err);
@@ -523,6 +531,14 @@ create_folders_table (EBookBackendSqliteDB *ebsdb,
 			ebsdb->priv->db, stmt, NULL, NULL, &err);
 	}
 
+	/* Upgrade DB to version 4, add country-code column
+	 */
+	if (!err && version >= 1 && version < 4) {
+		stmt = "ALTER TABLE folders ADD COLUMN countrycode VARCHAR(2)";
+		book_backend_sql_exec (
+			ebsdb->priv->db, stmt, NULL, NULL, &err);
+	}
+
 	if (!err && version >= 1 && version < FOLDER_VERSION) {
 		gchar *version_update_stmt =
 			sqlite3_mprintf ("UPDATE folders SET version = %d", FOLDER_VERSION);
@@ -546,12 +562,12 @@ create_folders_table (EBookBackendSqliteDB *ebsdb,
 
 static gchar *
 format_multivalues (EBookBackendSqliteDB *ebsdb,
-		    gboolean             *reverse_multivalues)
+		    IndexFlags           *computed_multivalues)
 {
 	gint i;
 	GString *string;
 	gboolean first = TRUE;
-	gboolean has_reverse = FALSE;
+	IndexFlags computed = 0;
 
 	string = g_string_new (NULL);
 
@@ -564,13 +580,17 @@ format_multivalues (EBookBackendSqliteDB *ebsdb,
 
 			g_string_append (string, ebsdb->priv->summary_fields[i].dbname);
 
+			if ((ebsdb->priv->summary_fields[i].index & INDEX_PREFIX) != 0)
+				computed |= INDEX_PREFIX;
 			if ((ebsdb->priv->summary_fields[i].index & INDEX_SUFFIX) != 0)
-				has_reverse = TRUE;
+				computed |= INDEX_SUFFIX;
+			if ((ebsdb->priv->summary_fields[i].index & INDEX_PHONE) != 0)
+				computed |= INDEX_PHONE;
 		}
 	}
 
-	if (reverse_multivalues)
-		*reverse_multivalues = has_reverse;
+	if (computed_multivalues)
+		*computed_multivalues = computed;
 
 	return g_string_free (string, FALSE);
 }
@@ -583,18 +603,18 @@ add_folder_into_db (EBookBackendSqliteDB *ebsdb,
 {
 	gchar *stmt;
 	GError *err = NULL;
-	gboolean has_reverse = FALSE;
+	IndexFlags computed = 0;
 	gchar *multivalues;
 
 	book_backend_sqlitedb_start_transaction (ebsdb, &err);
 
-	multivalues = format_multivalues (ebsdb, &has_reverse);
+	multivalues = format_multivalues (ebsdb, &computed);
 
 	stmt = sqlite3_mprintf (
 		"INSERT OR IGNORE INTO folders VALUES "
-		"( %Q, %Q, %Q, %d, %d, %d, %Q, %Q, %d ) ",
+		"( %Q, %Q, %Q, %d, %d, %d, %Q, %Q, %d, %Q ) ",
 		folderid, folder_name, NULL, 0, 0, FOLDER_VERSION,
-		NULL, multivalues, has_reverse);
+		NULL, multivalues, computed, NULL);
 	book_backend_sql_exec (
 		ebsdb->priv->db, stmt, NULL, NULL, error);
 	sqlite3_free (stmt);
@@ -646,7 +666,7 @@ introspect_summary (EBookBackendSqliteDB *ebsdb,
 	GList *summary_columns = NULL, *l;
 	GArray *summary_fields = NULL;
 	gchar *multivalues = NULL;
-	gboolean reverse_multivalues = FALSE;
+	IndexFlags computed_multivalues = 0;
 	gchar **split;
 	gint i;
 
@@ -666,13 +686,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 */
@@ -692,16 +714,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;
 				}
 			}
@@ -726,7 +748,7 @@ introspect_summary (EBookBackendSqliteDB *ebsdb,
 	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);
+		ebsdb->priv->db, stmt, get_bool_cb, &computed_multivalues, error);
 	sqlite3_free (stmt);
 
 	if (!success)
@@ -744,17 +766,30 @@ introspect_summary (EBookBackendSqliteDB *ebsdb,
 		g_strfreev (split);
 	}
 
-	/* If there is a reverse multivalue column, enable lookups for every multivalue field in reverse */
-	if (reverse_multivalues) {
+	/* With at least one reverse index the multi-value table has a reverse multivalue
+	 * column. Therefore it's safe to enable reverse lookups for every multivalue field.
+	 */
+	computed_multivalues &= (INDEX_SUFFIX | INDEX_PHONE);
 
+	if (computed_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;
+				iter->index |= computed_multivalues;
 		}
 	}
 
+	/* Retreive the country code used for guessing phone numbers */
+	stmt = sqlite3_mprintf (
+		"SELECT countrycode FROM folders WHERE folder_id = %Q", folderid);
+	success = book_backend_sql_exec (
+		ebsdb->priv->db, stmt, get_string_cb, &ebsdb->priv->country_code, error);
+	sqlite3_free (stmt);
+
+	if (!success)
+		goto introspect_summary_finish;
+
  introspect_summary_finish:
 
 	g_list_free_full (summary_columns, (GDestroyNotify)g_free);
@@ -789,7 +824,6 @@ create_contacts_table (EBookBackendSqliteDB *ebsdb,
 	string = g_string_new ("CREATE TABLE IF NOT EXISTS %Q ( uid TEXT PRIMARY KEY, ");
 
 	for (i = 1; i < ebsdb->priv->n_summary_fields; i++) {
-
 		if (ebsdb->priv->summary_fields[i].type == G_TYPE_STRING) {
 			g_string_append (string, ebsdb->priv->summary_fields[i].dbname);
 			g_string_append (string, " TEXT, ");
@@ -800,12 +834,17 @@ 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)");
 
@@ -818,7 +857,6 @@ create_contacts_table (EBookBackendSqliteDB *ebsdb,
 
 	/* Create indexes on the summary fields configured for indexing */
 	for (i = 0; !err && i < ebsdb->priv->n_summary_fields; i++) {
-
 		if ((ebsdb->priv->summary_fields[i].index & INDEX_PREFIX) != 0 &&
 		    ebsdb->priv->summary_fields[i].type != E_TYPE_CONTACT_ATTR_LIST) {
 			/* Derive index name from field & folder */
@@ -844,6 +882,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));
+			ret = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, &err);
+			sqlite3_free (stmt);
+			g_free (tmp);
+		}
 	}
 
 	/* Construct the create statement from the attribute list summary table */
@@ -851,8 +902,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, ')');
 
@@ -869,20 +922,24 @@ create_contacts_table (EBookBackendSqliteDB *ebsdb,
 		sqlite3_free (stmt);
 
 		/* Create indexes if specified */
-		if (!err && ebsdb->priv->have_attr_list_prefix) {
+		if (!err && (ebsdb->priv->attr_list_indexes & INDEX_PREFIX) != 0) {
 			stmt = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS VALINDEX ON %Q (value)", tmp);
 			ret = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, &err);
 			sqlite3_free (stmt);
 		}
 
-		if (!err && ebsdb->priv->have_attr_list_suffix) {
+		if (!err && (ebsdb->priv->attr_list_indexes & INDEX_SUFFIX) != 0) {
 			stmt = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS RVALINDEX ON %Q (value_reverse)", tmp);
 			ret = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, &err);
 			sqlite3_free (stmt);
 		}
 
+		if (!err && (ebsdb->priv->attr_list_indexes & INDEX_PHONE) != 0) {
+			stmt = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS PVALINDEX ON %Q (value_phone)", tmp);
+			ret = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, &err);
+			sqlite3_free (stmt);
+		}
 		g_free (tmp);
-
 	}
 
 	WRITER_UNLOCK (ebsdb);
@@ -890,12 +947,61 @@ create_contacts_table (EBookBackendSqliteDB *ebsdb,
 	if (!err)
 		ret = introspect_summary (ebsdb, folderid, &err);
 
+	if (!err)
+		ret = validate_county_code (ebsdb, folderid, &err);
+
 	if (err)
 		g_propagate_error (error, err);
 
 	return ret;
 }
 
+static void
+eqphone_func (sqlite3_context    *ctxt,
+              int                 argc,
+              sqlite3_value     **argv,
+              EPhoneNumberMatch   required_match)
+{
+	EPhoneNumberMatch actual_match;
+	GError *error;
+
+	if (argc != 2) {
+		sqlite3_result_error (ctxt, "Function requires exactly two arguments", -1);
+		return;
+	}
+
+	actual_match = e_phone_number_compare_strings (
+			(const char *) sqlite3_value_text (argv[0]),
+			(const char *) sqlite3_value_text (argv[1]),
+			&error);
+
+	if (error) {
+		sqlite3_result_error (ctxt, error->message, -1);
+		g_error_free (error);
+		return;
+	}
+
+	sqlite3_result_int
+		(ctxt, actual_match >= E_PHONE_NUMBER_MATCH_EXACT
+		 && actual_match <= required_match);
+}
+
+static void
+eqphone_national_func (sqlite3_context *ctxt,
+                       int              argc,
+                       sqlite3_value  **argv)
+{
+	eqphone_func (ctxt, argc, argv, E_PHONE_NUMBER_MATCH_NATIONAL);
+}
+
+static void
+eqphone_short_func (sqlite3_context *ctxt,
+                    int              argc,
+                    sqlite3_value  **argv)
+{
+	eqphone_func (ctxt, argc, argv, E_PHONE_NUMBER_MATCH_SHORT);
+}
+
 static gboolean
 book_backend_sqlitedb_load (EBookBackendSqliteDB *ebsdb,
                             const gchar *filename,
@@ -927,6 +1033,30 @@ book_backend_sqlitedb_load (EBookBackendSqliteDB *ebsdb,
 		return FALSE;
 	}
 
+	ret = sqlite3_create_function (priv->db, "eqphone_national",
+	                               2, SQLITE_UTF8, ebsdb,
+	                               eqphone_national_func,
+	                               NULL, NULL);
+
+	if (ret) {
+		g_set_error (error, E_BOOK_SDB_ERROR, 0,
+		             "%s", sqlite3_errmsg (priv->db));
+		sqlite3_close (priv->db);
+		return FALSE;
+	}
+
+	ret = sqlite3_create_function (priv->db, "eqphone_short",
+	                               2, SQLITE_UTF8, ebsdb,
+	                               eqphone_short_func,
+	                               NULL, NULL);
+
+	if (ret) {
+		g_set_error (error, E_BOOK_SDB_ERROR, 0,
+		             "%s", sqlite3_errmsg (priv->db));
+		sqlite3_close (priv->db);
+		return FALSE;
+	}
+
 	WRITER_LOCK (ebsdb);
 
 	book_backend_sql_exec (priv->db, "ATTACH DATABASE ':memory:' AS mem", NULL, NULL, NULL);
@@ -950,8 +1080,7 @@ 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;
@@ -982,8 +1111,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_static_mutex_unlock (&dbcon_lock);
@@ -1090,8 +1218,7 @@ summary_fields_add_indexes (GArray         *array,
 			    EContactField  *indexes,
 			    EBookIndexType *index_types,
 			    gint            n_indexes,
-			    gboolean       *have_attr_list_prefix,
-			    gboolean       *have_attr_list_suffix)
+			    IndexFlags     *attr_list_indexes)
 {
 	gint i, j;
 
@@ -1105,13 +1232,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 ();
@@ -1164,8 +1297,7 @@ e_book_backend_sqlitedb_new_full (const gchar *path,
 	EContactField *indexed_fields;
 	EBookIndexType *index_types;
 	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, n_indexed_fields, i;
@@ -1207,15 +1339,14 @@ 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,
 						      store_vcard,
 						      (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);
@@ -1255,8 +1386,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 summery structs */
@@ -1269,15 +1399,14 @@ 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,
 						      store_vcard,
 						      (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);
 
@@ -1303,11 +1432,59 @@ e_book_backend_sqlitedb_unlock_updates (EBookBackendSqliteDB *ebsdb,
 	return book_backend_sqlitedb_end_transaction (ebsdb, do_commit, 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 gchar *
+convert_phone (const gchar *normal,
+               const gchar *country_code)
+{
+	EPhoneNumber *number = NULL;
+	gchar *phone_number = NULL;
+	GError *error = NULL;
+
+	if (normal)
+		number = e_phone_number_from_string (normal, country_code, &error);
+
+	if (error) {
+		g_error ("%s:%d (%s): %s", __FILE__, __LINE__, __func__, error->message);
+		g_clear_error (&error);
+	}
+
+	if (number) {
+		phone_number = e_phone_number_to_string (number, E_PHONE_NUMBER_FORMAT_E164);
+		e_phone_number_free (number);
+	}
+
+	return phone_number;
+}
+
+static gchar *
+mprintf_phone (const gchar *normal,
+               const gchar *country_code)
+{
+	gchar *phone = convert_phone (normal, country_code);
+	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,
-                          gboolean partial_content,
+                          EContact *contact,
                           const gchar *folderid,
                           gboolean store_vcard)
 {
@@ -1342,13 +1519,17 @@ 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 = mprintf_suffix (normal);
+				g_string_append (string, ", ");
+				g_string_append (string, str);
+				sqlite3_free (str);
+			}
 
-				str = sqlite3_mprintf ("%Q", reverse);
+			if ((ebsdb->priv->summary_fields[i].index & INDEX_PHONE) != 0) {
+				str = mprintf_phone (normal, ebsdb->priv->country_code);
 				g_string_append (string, ", ");
 				g_string_append (string, str);
 				sqlite3_free (str);
-				g_free (reverse);
 			}
 
 			g_free (normal);
@@ -1378,25 +1559,25 @@ insert_stmt_from_contact (EBookBackendSqliteDB *ebsdb,
 	return g_string_free (string, FALSE);
 }
 
-static void
+static gboolean
 insert_contact (EBookBackendSqliteDB *ebsdb,
 		EContact *contact,
-		gboolean partial_content,
 		const gchar *folderid,
 		GError **error)
 {
 	EBookBackendSqliteDBPrivate *priv;
+	gboolean success;
 	gchar *stmt;
 
 	priv = ebsdb->priv;
 
 	/* Update main summary table */
-	stmt = insert_stmt_from_contact (ebsdb, contact, partial_content, folderid, priv->store_vcard);
-	book_backend_sql_exec (priv->db, stmt, NULL, NULL, error);
+	stmt = insert_stmt_from_contact (ebsdb, contact, folderid, priv->store_vcard);
+	success = book_backend_sql_exec (priv->db, stmt, NULL, NULL, error);
 	g_free (stmt);
 
 	/* Update attribute list table */
-	if ((!error || (*error == NULL)) && priv->have_attr_list) {
+	if (success && priv->have_attr_list) {
 		gchar *list_folder = g_strdup_printf ("%s_lists", folderid);
 		gchar *uid;
 		gint   i;
@@ -1405,39 +1586,43 @@ insert_contact (EBookBackendSqliteDB *ebsdb,
 		/* First remove all entries for this UID */
 		uid = e_contact_get (contact, E_CONTACT_UID);
 		stmt = sqlite3_mprintf ("DELETE FROM %Q WHERE uid = %Q", list_folder, uid);
-		book_backend_sql_exec (priv->db, stmt, NULL, NULL, error);
+		success = book_backend_sql_exec (priv->db, stmt, NULL, NULL, error);
 		sqlite3_free (stmt);
 
-		for (i = 0; (!error || (*error == NULL)) && i < priv->n_summary_fields; i++) {
-
+		for (i = 0; success && i < priv->n_summary_fields; i++) {
 			if (priv->summary_fields[i].type != E_TYPE_CONTACT_ATTR_LIST)
 				continue;
 
 			values = e_contact_get (contact, priv->summary_fields[i].field);
 
-			for (l = values; (!error || (*error == NULL)) && l != NULL; l = l->next) {
+			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);
-				}
-
-				book_backend_sql_exec (priv->db, stmt, NULL, NULL, error);
+				gchar *stmt_suffix = NULL;
+				gchar *stmt_phone = NULL;
+
+				if ((priv->attr_list_indexes & INDEX_SUFFIX) != 0)
+					stmt_suffix = mprintf_suffix (normal);
+				if ((priv->attr_list_indexes & INDEX_PHONE) != 0)
+					stmt_phone = mprintf_phone (normal, ebsdb->priv->country_code);
+
+				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);
 				g_free (normal);
 			}
@@ -1449,6 +1634,8 @@ insert_contact (EBookBackendSqliteDB *ebsdb,
 		g_free (list_folder);
 		g_free (uid);
 	}
+
+	return success;
 }
 
 
@@ -1523,7 +1710,7 @@ e_book_backend_sqlitedb_add_contacts (EBookBackendSqliteDB *ebsdb,
 	for (l = contacts; !err && l != NULL; l = g_slist_next (l)) {
 		EContact *contact = (EContact *) l->data;
 
-		insert_contact (ebsdb, contact, partial_content, folderid, &err);
+		insert_contact (ebsdb, contact, folderid, &err);
 	}
 
 	book_backend_sqlitedb_end_transaction (ebsdb, !err, err ? NULL : &err);
@@ -2100,29 +2287,62 @@ func_check (struct _ESExp *f,
 
 					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 (!strcmp (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;
 				}
 			}
 		}
 	}
 
  check_finish:
-
 	r = e_sexp_result_new (f, ESEXP_RES_INT);
 	r->value.number = ret_val;
 
 	return r;
 }
 
+static ESExpResult *
+func_check_phone (struct _ESExp         *f,
+                  gint                  argc,
+                  struct _ESExpResult **argv,
+                  gpointer              data)
+{
+	EBookBackendSqliteDB *ebsdb = 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, ebsdb->priv->country_code, &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);
+		}
+	}
+
+	return r;
+}
+
 /* 'builtin' functions */
 static const struct {
 	const gchar *name;
@@ -2137,7 +2357,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 }
 };
 
 /**
@@ -2289,27 +2512,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 *
+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 (const gchar *value,
-		      gboolean normalize,
-		      gboolean reverse,
-                      match_type match)
+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);
@@ -2325,17 +2582,23 @@ convert_string_value (const gchar *value,
 	switch (match) {
 	case MATCH_CONTAINS:
 	case MATCH_ENDS_WITH:
+	case MATCH_NATIONAL_PHONE_NUMBER:
+	case MATCH_SHORT_PHONE_NUMBER:
 		g_string_append_c (str, '%');
 		break;
 
 	case MATCH_BEGINS_WITH:
 	case MATCH_IS:
+	case MATCH_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) {
+		computed = convert_phone (normal, ebsdb->priv->country_code);
+		ptr = computed;
 	} else {
 		ptr = normal;
 	}
@@ -2359,6 +2622,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;
 	}
 
@@ -2367,7 +2633,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);
@@ -2378,13 +2644,15 @@ field_name_and_query_term (EBookBackendSqliteDB *ebsdb,
 			   const gchar          *folderid,
 			   const gchar          *field_name_input,
 			   const gchar          *query_term_input,
-			   match_type            match,
+			   MatchType             match,
 			   gboolean             *is_list_attr,
-			   gchar               **query_term)
+			   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);
@@ -2392,18 +2660,26 @@ 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 && 
+		else if (match == MATCH_IS &&
 			 (ebsdb->priv->summary_fields[summary_index].index & INDEX_SUFFIX) != 0 &&
 			 (ebsdb->priv->summary_fields[summary_index].index & INDEX_PREFIX) == 0)
 			suffix_search = TRUE;
@@ -2418,17 +2694,48 @@ field_name_and_query_term (EBookBackendSqliteDB *ebsdb,
 				field_name = g_strdup ("multi.value_reverse");
 				list_attr = TRUE;
 			} else
-				field_name = g_strconcat ("summary.", 
+				field_name = g_strconcat ("summary.",
 							  ebsdb->priv->summary_fields[summary_index].dbname,
 							  "_reverse", NULL);
 
 			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,
+				value = convert_string_value (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,
+				value = convert_string_value (ebsdb, query_term_input, CONVERT_REVERSE | CONVERT_NORMALIZE,
 							      (match == MATCH_ENDS_WITH) ? MATCH_BEGINS_WITH : MATCH_IS);
+		} else if (phone_search) {
+			/* Special case for E.164 matching:
+			 *  o Normalize the string
+			 *  o Check the E.164 column instead
+			 */
+			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_strconcat ("summary.",
+							  ebsdb->priv->summary_fields[summary_index].dbname,
+							  "_phone", NULL);
+
+			if (match == MATCH_PHONE_NUMBER) {
+				value = convert_string_value (ebsdb, query_term_input,
+							      CONVERT_NORMALIZE | CONVERT_PHONE, match);
+			} else {
+				gchar *digits = extract_digits (query_term_input);
+
+				value = convert_string_value (ebsdb, digits, CONVERT_NOTHING, MATCH_ENDS_WITH);
+
+				if (match == MATCH_NATIONAL_PHONE_NUMBER) {
+					extra = sqlite3_mprintf (" AND eqphone_national(%q, %Q)", field_name, digits);
+				} else if (match == MATCH_SHORT_PHONE_NUMBER) {
+					extra = sqlite3_mprintf (" AND eqphone_short(%q, %Q)", field_name, digits);
+				} else {
+					g_assert_not_reached ();
+				}
+
+				g_free (digits);
+			}
 		} else {
 
 			if (ebsdb->priv->summary_fields[summary_index].type == E_TYPE_CONTACT_ATTR_LIST) {
@@ -2440,9 +2747,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);
 		}
 	}
 
@@ -2451,6 +2758,9 @@ field_name_and_query_term (EBookBackendSqliteDB *ebsdb,
 
 	*query_term = value;
 
+	if (extra_term)
+		*extra_term = extra;
+
 	return field_name;
 }
 
@@ -2459,12 +2769,31 @@ typedef struct {
 	const gchar          *folderid;
 } BuildQueryData;
 
+static const gchar *
+field_oper (MatchType match)
+{
+	switch (match) {
+	case MATCH_IS:
+	case MATCH_PHONE_NUMBER:
+		return "=";
+
+	case MATCH_CONTAINS:
+	case MATCH_BEGINS_WITH:
+	case MATCH_ENDS_WITH:
+	case MATCH_NATIONAL_PHONE_NUMBER:
+	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;
@@ -2479,18 +2808,15 @@ 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 (!strcmp (field, "full_name")) {
 				GString *names = g_string_new (NULL);
 
 				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);
 				g_free (field_name);
@@ -2500,7 +2826,7 @@ convert_match_exp (struct _ESExp *f,
 
 					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);
@@ -2512,7 +2838,7 @@ convert_match_exp (struct _ESExp *f,
 
 					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);
@@ -2524,7 +2850,7 @@ convert_match_exp (struct _ESExp *f,
 
 					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);
@@ -2541,18 +2867,24 @@ convert_match_exp (struct _ESExp *f,
 				/* 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);
+					str = g_strdup_printf ("(%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);
+					str = g_strdup_printf ("(%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);
@@ -2602,6 +2934,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;
@@ -2615,6 +2974,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 *
@@ -3608,3 +3970,75 @@ 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
+validate_county_code (EBookBackendSqliteDB  *ebsdb,
+                      const gchar           *folderid,
+                      GError               **error)
+{
+	gchar *stmt;
+	gboolean success = FALSE;
+	GSList *vcard_data = NULL;
+	GSList *l;
+
+#if HAVE__NL_ADDRESS_COUNTRY_AB2
+	const gchar *country_code = nl_langinfo (_NL_ADDRESS_COUNTRY_AB2);
+#else /* HAVE__NL_ADDRESS_COUNTRY_AB2 */
+#error Cannot resolve default 2-letter country code. Find a replacement for _NL_ADDRESS_COUNTRY_AB2 or implement code to parse the locale name.
+#endif /* HAVE__NL_ADDRESS_COUNTRY_AB2 */
+
+	if (country_code == NULL || strlen (country_code) != 2) {
+		g_warning ("Cannot derive 2-letter country code from current locale.");
+		country_code = "ZZ";
+	}
+
+	/* Nothing has changed. We can stop here. */
+	if (g_strcmp0 (country_code, ebsdb->priv->country_code) == 0)
+		return TRUE;
+
+	g_free (ebsdb->priv->country_code);
+	ebsdb->priv->country_code = g_strdup (country_code);
+
+	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) {
+		g_print ("The country code has changed to \"%s\". "
+		         "Must rebuild phone number indexes for stored vCards.\n",
+		         ebsdb->priv->country_code);
+	}
+
+	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, error);
+
+		g_object_unref (contact);
+	}
+
+	g_slist_free_full (vcard_data, destroy_search_data);
+
+	if (success) {
+		stmt = sqlite3_mprintf (
+			"UPDATE folders SET countrycode = %Q WHERE folder_id = %Q",
+			country_code, folderid);
+		success = book_backend_sql_exec (
+			ebsdb->priv->db, stmt, get_string_cb, &ebsdb->priv->country_code, error);
+		sqlite3_free (stmt);
+	}
+
+	return success;
+}
+
diff --git a/tests/libebook/client/test-client-custom-summary.c b/tests/libebook/client/test-client-custom-summary.c
index 553dbfb..ebe2fc9 100644
--- a/tests/libebook/client/test-client-custom-summary.c
+++ b/tests/libebook/client/test-client-custom-summary.c
@@ -48,6 +48,7 @@ setup_custom_summary (ESource *scratch)
 							   0);
 	e_source_backend_summary_setup_set_indexed_fields (setup,
 							   E_CONTACT_TEL, E_BOOK_INDEX_SUFFIX,
+							   E_CONTACT_TEL, E_BOOK_INDEX_PHONE,
 							   E_CONTACT_FULL_NAME, E_BOOK_INDEX_PREFIX,
 							   E_CONTACT_FULL_NAME, E_BOOK_INDEX_SUFFIX,
 							   E_CONTACT_FAMILY_NAME, E_BOOK_INDEX_PREFIX,
@@ -219,12 +220,21 @@ main (gint argc,
 	                 e_book_query_vcard_field_test(EVC_N, E_BOOK_QUERY_IS, "Janet"),
 	                 1);
 	add_client_test ("/client/search/eqphone/exact/phone", search_test, book_client,
-	                 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, book_client,
-	                 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, book_client,
+	                 e_book_query_field_test(E_CONTACT_TEL, E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER, "5423789"),
+	                 1);
+	add_client_test ("/client/search/eqphone/exact/tel", search_test, book_client,
+	                 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, book_client,
+	                 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, book_client,
 	                 e_book_query_vcard_field_test(EVC_TEL, E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER, "5423789"),
 	                 1);
 



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]