[evolution-data-server/sqlite-refactor: 4/6] Adding new EBookBackendSqlite (refactor in progress)



commit c0c629d7f6412056e9c02ea9032371bb7babe01c
Author: Tristan Van Berkom <tristanvb openismus com>
Date:   Tue Nov 19 21:56:38 2013 +0900

    Adding new EBookBackendSqlite (refactor in progress)

 addressbook/libedata-book/Makefile.am             |    2 +
 addressbook/libedata-book/e-book-backend-sqlite.c | 6170 +++++++++++++++++++++
 addressbook/libedata-book/e-book-backend-sqlite.h |  317 ++
 3 files changed, 6489 insertions(+), 0 deletions(-)
---
diff --git a/addressbook/libedata-book/Makefile.am b/addressbook/libedata-book/Makefile.am
index 6b39d2d..38096d7 100644
--- a/addressbook/libedata-book/Makefile.am
+++ b/addressbook/libedata-book/Makefile.am
@@ -28,6 +28,7 @@ libedata_book_1_2_la_SOURCES = \
        e-book-backend-cache.c \
        e-book-backend-db-cache.c \
        e-book-backend-sqlitedb.c \
+       e-book-backend-sqlite.c \
        e-book-backend.c \
        e-data-book.c \
        e-data-book-cursor.c \
@@ -70,6 +71,7 @@ libedata_bookinclude_HEADERS = \
        e-data-book-direct.h \
        e-book-backend-cache.h \
        e-book-backend-sqlitedb.h \
+       e-book-backend-sqlite.h \
        e-book-backend-db-cache.h \
        $(NULL)
 
diff --git a/addressbook/libedata-book/e-book-backend-sqlite.c 
b/addressbook/libedata-book/e-book-backend-sqlite.c
new file mode 100644
index 0000000..480ba8f
--- /dev/null
+++ b/addressbook/libedata-book/e-book-backend-sqlite.c
@@ -0,0 +1,6170 @@
+/*-*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* e-book-backend-sqlite.c
+ *
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * Authors:
+ *     Tristan Van Berkom <tristanvb openismus com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "e-book-backend-sqlite.h"
+
+#include <locale.h>
+#include <string.h>
+#include <errno.h>
+
+#include <glib/gi18n.h>
+#include <glib/gstdio.h>
+
+#include <sqlite3.h>
+#include <libebackend/libebackend.h>
+
+#include "e-book-backend-sexp.h"
+
+
+/* Some forward declarations */
+typedef struct _SummaryField SummaryField;
+
+static sqlite3_stmt *book_backend_sqlite_prepare_insert       (EBookBackendSqlite *ebsql,
+                                                              gboolean replace_existing,
+                                                              GError **error);
+static sqlite3_stmt *book_backend_sqlite_prepare_multi_insert (EBookBackendSqlite *ebsql,
+                                                              SummaryField *field,
+                                                              GError **error);
+static sqlite3_stmt *book_backend_sqlite_prepare_multi_delete (EBookBackendSqlite *ebsql,
+                                                              SummaryField *field,
+                                                              GError **error);
+
+static gboolean  book_backend_sqlite_insert_contact (EBookBackendSqlite *ebsql,
+                                                    EContact *contact,
+                                                    gboolean replace_existing,
+                                                    const gchar *default_region,
+                                                    GError **error);
+
+static gboolean book_backend_sqlite_set_locale_internal (EBookBackendSqlite *ebsql,
+                                                        const gchar          *locale,
+                                                        GError              **error);
+
+#define d(x)
+
+#if d(1)+0
+#  define LOCK_MUTEX(mutex)                                    \
+       G_STMT_START {                                          \
+               g_printerr ("%s: DB Locking\n", G_STRFUNC);     \
+               g_mutex_lock (mutex);                           \
+               g_printerr ("%s: DB Locked\n", G_STRFUNC);      \
+       } G_STMT_END
+
+#  define UNLOCK_MUTEX(mutex)                                  \
+       G_STMT_START {                                          \
+               g_printerr ("%s: Unlocking\n", G_STRFUNC);      \
+               g_mutex_unlock (mutex);                         \
+               g_printerr ("%s: DB Unlocked\n", G_STRFUNC);    \
+       } G_STMT_END
+#else
+#  define LOCK_MUTEX(mutex)   g_mutex_lock (mutex)
+#  define UNLOCK_MUTEX(mutex) g_mutex_unlock (mutex)
+#endif
+
+#define DB_FILENAME            "contacts.db"
+#define FOLDER_VERSION          8
+#define INSERT_MULTI_STMT_BYTES 128
+#define MAIN_TABLE_ENTRY_BYTES  32
+
+#define INDEX_FLAG(type)  (1 << E_BOOK_INDEX_##type)
+
+typedef gint (* EbSqlSqliteCallback) (gpointer ref,
+                                     gint     n_cols,
+                                     gchar  **cols,
+                                     gchar  **names);
+
+struct _SummaryField {
+       EContactField field;   /* The EContact field */
+       GType         type;    /* The GType (only support string or gboolean) */
+       const gchar  *dbname;  /* The key for this field in the sqlite3 table */
+       gint          index;   /* Types of searches this field should support (see EBookIndexType) */
+};
+
+struct _EBookBackendSqlitePrivate {
+       sqlite3 *db;
+       gchar *path;
+
+       GMutex lock;
+       GMutex updates_lock; /* This is for deprecated e_book_backend_sqlite_lock_updates () */
+
+       gchar *folderid;
+
+       gboolean store_vcard;
+       guint32 in_transaction;
+
+       SummaryField   *summary_fields;
+       gint            n_summary_fields;
+       guint           have_attr_list : 1;
+       gint            attr_list_indexes;
+
+       ECollator      *collator; /* The ECollator to create sort keys for any sortable fields */
+       gchar          *locale;   /* The current locale */
+
+       /* prepared statements for inserts */
+       sqlite3_stmt   *insert_stmt;
+       sqlite3_stmt   *replace_stmt;
+
+       /* One delete & insert statement per 
+        * multivalued attribute in the summary
+        */
+       GHashTable     *multi_deletes;
+       GHashTable     *multi_inserts;
+};
+
+G_DEFINE_TYPE (EBookBackendSqlite, e_book_backend_sqlite, G_TYPE_OBJECT)
+G_DEFINE_QUARK (e-book-backend-sqlite-error-quark,
+               e_book_backend_sqlite_error)
+
+/* The ColumnInfo struct is used to constant data
+ * and dynamically allocated data, the 'type' and
+ * 'extra' members are however always constant.
+ */
+typedef struct {
+       gchar       *name;
+       const gchar *type;
+       const gchar *extra;
+       gchar       *index;
+} ColumnInfo;
+
+static ColumnInfo main_table_columns[] = {
+       { (gchar *) "folder_id",       "TEXT",      "PRIMARY KEY", NULL },
+       { (gchar *) "version",         "INTEGER",    NULL,         NULL },
+       { (gchar *) "multivalues",     "TEXT",       NULL,         NULL },
+       { (gchar *) "lc_collate",      "TEXT",       NULL,         NULL },
+       { (gchar *) "countrycode",     "VARCHAR(2)", NULL,         NULL },
+};
+
+static EContactField default_summary_fields[] = {
+       E_CONTACT_UID,
+       E_CONTACT_REV,
+       E_CONTACT_FILE_AS,
+       E_CONTACT_NICKNAME,
+       E_CONTACT_FULL_NAME,
+       E_CONTACT_GIVEN_NAME,
+       E_CONTACT_FAMILY_NAME,
+       E_CONTACT_EMAIL,
+       E_CONTACT_IS_LIST,
+       E_CONTACT_LIST_SHOW_ADDRESSES,
+       E_CONTACT_WANTS_HTML
+};
+
+/* Create indexes on full_name and email fields as autocompletion queries would mainly
+ * rely on this.
+ */
+static EContactField default_indexed_fields[] = {
+       E_CONTACT_FULL_NAME,
+       E_CONTACT_EMAIL,
+       E_CONTACT_FULL_NAME,
+       E_CONTACT_GIVEN_NAME,
+       E_CONTACT_FAMILY_NAME
+};
+
+static EBookIndexType default_index_types[] = {
+       E_BOOK_INDEX_PREFIX,
+       E_BOOK_INDEX_PREFIX,
+       E_BOOK_INDEX_SORT_KEY,
+       E_BOOK_INDEX_SORT_KEY,
+       E_BOOK_INDEX_SORT_KEY
+};
+
+/******************************************************
+ *      Sharing EBookBackendSqlite instances        *
+ ******************************************************/
+static GHashTable *db_connections = NULL;
+static GMutex dbcon_lock;
+
+static EBookBackendSqlite *
+book_backend_sqlite_ref_from_hash (const gchar *path)
+{
+       EBookBackendSqlite *ebsql = NULL;
+
+       if (db_connections != NULL) {
+               ebsql = g_hash_table_lookup (db_connections, path);
+       }
+
+       if (ebsql)
+               return g_object_ref (ebsql);
+
+       return NULL;
+}
+
+static void
+book_backend_sqlite_register_to_hash (EBookBackendSqlite *ebsql,
+                                     const gchar *path)
+{
+       if (db_connections == NULL)
+               db_connections = g_hash_table_new_full (
+                       (GHashFunc) g_str_hash,
+                       (GEqualFunc) g_str_equal,
+                       (GDestroyNotify) g_free,
+                       (GDestroyNotify) NULL);
+       g_hash_table_insert (db_connections, g_strdup (path), ebsql);
+}
+
+static void
+book_backend_sqlite_unregister_from_hash (EBookBackendSqlite *ebsql)
+{
+       EBookBackendSqlitePrivate *priv = ebsql->priv;
+
+       g_mutex_lock (&dbcon_lock);
+       if (db_connections != NULL) {
+               if (priv->path != NULL) {
+                       g_hash_table_remove (db_connections, priv->path);
+
+                       if (g_hash_table_size (db_connections) == 0) {
+                               g_hash_table_destroy (db_connections);
+                               db_connections = NULL;
+                       }
+
+               }
+       }
+       g_mutex_unlock (&dbcon_lock);
+}
+
+/************************************************************
+ *    Executing SQLite statements and collecting results    *
+ ************************************************************
+ *
+ * Lock must be held at all times when executing statements
+ */
+/* For debugging statements with BOOKSQL_DEBUG environment variable */
+static gint
+print_debug_cb (gpointer ref,
+                gint n_cols,
+                gchar **cols,
+                gchar **name)
+{
+       gint i;
+
+       g_printerr ("  DEBUG BEGIN:\n");
+
+       for (i = 0; i < n_cols; i++)
+               g_printerr ("    NAME: '%s' VALUE: %s\n", name[i], cols[i]);
+
+       g_printerr ("  DEBUG END\n");
+
+       return 0;
+}
+
+/* Collect a GList of column names in the main summary table */
+static gint
+get_columns_cb (gpointer ref,
+               gint col,
+               gchar **cols,
+               gchar **name)
+{
+       GSList **columns = (GSList **) ref;
+       gint i;
+
+       for (i = 0; i < col; i++) {
+               if (strcmp (name[i], "name") == 0) {
+
+                       /* Keep comparing for the legacy 'bdata' column */
+                       if (strcmp (cols[i], "vcard") != 0 &&
+                           strcmp (cols[i], "bdata") != 0) {
+                               gchar *column = g_strdup (cols[i]);
+
+                               *columns = g_slist_prepend (*columns, column);
+                       }
+                       break;
+               }
+       }
+       return 0;
+}
+
+/* Collect the first string result */
+static gint
+get_string_cb (gpointer ref,
+               gint col,
+               gchar **cols,
+               gchar **name)
+{
+       gchar **ret = ref;
+
+       *ret = g_strdup (cols [0]);
+
+       return 0;
+}
+
+/* Collect the first integer result */
+static gint
+get_int_cb (gpointer ref,
+           gint col,
+           gchar **cols,
+           gchar **name)
+{
+       gint *ret = ref;
+
+       *ret = cols [0] ? g_ascii_strtoll (cols[0], NULL, 10) : 0;
+
+       return 0;
+}
+
+/* Collect the result of a SELECT count(*) statement */
+static gint
+get_count_cb (gpointer ref,
+              gint n_cols,
+              gchar **cols,
+              gchar **name)
+{
+       gint64 count = 0;
+       gint *ret = ref;
+       gint i;
+
+       for (i = 0; i < n_cols; i++) {
+               if (name[i] && strncmp (name[i], "count", 5) == 0) {
+                       count = g_ascii_strtoll (cols[i], NULL, 10);
+
+                       break;
+               }
+       }
+
+       *ret = count;
+
+       return 0;
+}
+
+/* Report if there was at least one result */
+static gint
+get_exists_cb (gpointer ref,
+              gint col,
+              gchar **cols,
+              gchar **name)
+{
+       gboolean *exists = ref;
+
+       *exists = TRUE;
+
+       return 0;
+}
+
+static EbSqlSearchData *
+search_data_from_results (gchar **cols)
+{
+       EbSqlSearchData *data = g_slice_new0 (EbSqlSearchData);
+
+       if (cols[0])
+               data->uid = g_strdup (cols[0]);
+
+       if (cols[1])
+               data->vcard = g_strdup (cols[1]);
+
+       return data;
+}
+
+static gint
+collect_full_results_cb (gpointer ref,
+                        gint col,
+                        gchar **cols,
+                        gchar **name)
+{
+       EbSqlSearchData *data;
+       GSList **vcard_data = ref;
+
+       data = search_data_from_results (cols);
+
+       *vcard_data = g_slist_prepend (*vcard_data, data);
+
+       return 0;
+}
+
+static gint
+collect_uid_results_cb (gpointer ref,
+                       gint col,
+                       gchar **cols,
+                       gchar **name)
+{
+       GSList **uids = ref;
+
+       if (cols[0])
+               *uids = g_slist_prepend (*uids, g_strdup (cols [0]));
+
+       return 0;
+}
+
+static gint
+collect_lean_results_cb (gpointer ref,
+                        gint ncol,
+                        gchar **cols,
+                        gchar **name)
+{
+       GSList **vcard_data = ref;
+       EbSqlSearchData *search_data = g_slice_new0 (EbSqlSearchData);
+       EContact *contact = e_contact_new ();
+       gchar *vcard;
+       gint i;
+
+       /* parse through cols, this will be useful if the api starts supporting field restrictions */
+       for (i = 0; i < ncol; i++) {
+               if (!name[i] || !cols[i])
+                       continue;
+
+               /* 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]);
+               }
+       }
+
+       vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
+       search_data->vcard = vcard;
+       *vcard_data = g_slist_prepend (*vcard_data, search_data);
+
+       g_object_unref (contact);
+       return 0;
+}
+
+static gint
+collect_uids_and_rev_cb (gpointer user_data,
+                        gint col,
+                        gchar **cols,
+                        gchar **name)
+{
+       GHashTable *uids_and_rev = user_data;
+
+       if (col == 2 && cols[0])
+               g_hash_table_insert (uids_and_rev, g_strdup (cols[0]), g_strdup (cols[1] ? cols[1] : ""));
+
+       return 0;
+}
+
+static gboolean
+book_backend_sqlite_exec_real (EBookBackendSqlite *ebsql,
+                                const gchar *stmt,
+                                EbSqlSqliteCallback callback,
+                                gpointer data,
+                                GError **error)
+{
+       gchar *errmsg = NULL;
+       gint ret = -1;
+
+       ret = sqlite3_exec (ebsql->priv->db, stmt, callback, data, &errmsg);
+       while (ret == SQLITE_BUSY || ret == SQLITE_LOCKED || ret == -1) {
+               if (errmsg) {
+                       sqlite3_free (errmsg);
+                       errmsg = NULL;
+               }
+               g_thread_yield ();
+               ret = sqlite3_exec (ebsql->priv->db, stmt, callback, data, &errmsg);
+       }
+
+       if (ret != SQLITE_OK) {
+               d (g_printerr ("Error in SQL EXEC statement: %s [%s].\n", stmt, errmsg));
+               g_set_error_literal (
+                       error, E_BOOK_SQL_ERROR,
+                       ret == SQLITE_CONSTRAINT ?
+                       E_BOOK_SQL_ERROR_CONSTRAINT : E_BOOK_SQL_ERROR_OTHER,
+                       errmsg);
+               sqlite3_free (errmsg);
+               errmsg = NULL;
+               return FALSE;
+       }
+
+       if (errmsg) {
+               sqlite3_free (errmsg);
+               errmsg = NULL;
+       }
+
+       return TRUE;
+}
+
+static gint G_GNUC_CONST
+book_backend_sqlite_debug_level (void)
+{
+       static gint booksql_debug = -1;
+
+       if (booksql_debug == -1) {
+               const gchar *const tmp = g_getenv ("BOOKSQL_DEBUG");
+
+               if (tmp)
+                       booksql_debug = g_ascii_strtoll (tmp, NULL, 10);
+               else
+                       booksql_debug = 0;
+       }
+
+       return booksql_debug;
+}
+
+static void
+book_backend_sqlite_debug (EBookBackendSqlite *ebsql,
+                            const gchar *stmt,
+                            EbSqlSqliteCallback callback,
+                            gpointer data,
+                            GError **error)
+{
+       GError *local_error = NULL;
+
+       g_printerr ("DEBUG STATEMENT: %s\n", stmt);
+
+       if (book_backend_sqlite_debug_level () > 1) {
+               gchar *debug = g_strconcat ("EXPLAIN QUERY PLAN ", stmt, NULL);
+               book_backend_sqlite_exec_real (ebsql, debug, print_debug_cb, NULL, &local_error);
+               g_free (debug);
+       }
+
+       if (local_error) {
+               g_printerr ("DEBUG STATEMENT END: Error: %s\n", local_error->message);
+       } else if (book_backend_sqlite_debug_level () > 1) {
+               g_printerr ("DEBUG STATEMENT END: Success\n");
+       }
+
+       g_clear_error (&local_error);
+}
+
+static gboolean
+book_backend_sqlite_exec (EBookBackendSqlite *ebsql,
+                           const gchar *stmt,
+                           EbSqlSqliteCallback callback,
+                           gpointer data,
+                           GError **error)
+{
+       if (book_backend_sqlite_debug_level () > 0)
+               book_backend_sqlite_debug (ebsql, stmt, callback, data, error);
+
+       return book_backend_sqlite_exec_real (ebsql, stmt, callback, data, error);
+}
+
+static gboolean
+book_backend_sqlite_exec_vprintf (EBookBackendSqlite *ebsql,
+                                   const gchar *fmt,
+                                   EbSqlSqliteCallback callback,
+                                   gpointer data,
+                                   GError **error,
+                                   va_list args)
+{
+       gboolean success;
+       gchar *stmt;
+
+       stmt = sqlite3_vmprintf (fmt, args);
+       success = book_backend_sqlite_exec (ebsql, stmt, callback, data, error);
+       sqlite3_free (stmt);
+
+       return success;
+}
+
+static gboolean
+book_backend_sqlite_exec_printf (EBookBackendSqlite *ebsql,
+                                  const gchar *fmt,
+                                  EbSqlSqliteCallback callback,
+                                  gpointer data,
+                                  GError **error,
+                                  ...)
+{
+       gboolean success;
+       va_list args;
+
+       va_start (args, error);
+       success = book_backend_sqlite_exec_vprintf (ebsql, fmt, callback, data, error, args);
+       va_end (args);
+
+       return success;
+}
+
+static gboolean
+book_backend_sqlite_start_transaction (EBookBackendSqlite *ebsql,
+                                         GError **error)
+{
+       gboolean success = TRUE;
+
+       g_return_val_if_fail (ebsql != NULL, FALSE);
+       g_return_val_if_fail (ebsql->priv != NULL, FALSE);
+       g_return_val_if_fail (ebsql->priv->db != NULL, FALSE);
+
+       ebsql->priv->in_transaction++;
+       g_return_val_if_fail (ebsql->priv->in_transaction > 0, FALSE);
+
+       if (ebsql->priv->in_transaction == 1) {
+
+               success = book_backend_sqlite_exec (
+                       ebsql, "BEGIN", NULL, NULL, error);
+       }
+
+       return success;
+}
+
+static gboolean
+book_backend_sqlite_commit_transaction (EBookBackendSqlite *ebsql,
+                                          GError **error)
+{
+       gboolean success = TRUE;
+
+       g_return_val_if_fail (ebsql != NULL, FALSE);
+       g_return_val_if_fail (ebsql->priv != NULL, FALSE);
+       g_return_val_if_fail (ebsql->priv->db != NULL, FALSE);
+
+       g_return_val_if_fail (ebsql->priv->in_transaction > 0, FALSE);
+
+       ebsql->priv->in_transaction--;
+
+       if (ebsql->priv->in_transaction == 0) {
+               success = book_backend_sqlite_exec (
+                       ebsql, "COMMIT", NULL, NULL, error);
+       }
+
+       return success;
+}
+
+static gboolean
+book_backend_sqlite_rollback_transaction (EBookBackendSqlite *ebsql,
+                                            GError **error)
+{
+       gboolean success = TRUE;
+
+       g_return_val_if_fail (ebsql != NULL, FALSE);
+       g_return_val_if_fail (ebsql->priv != NULL, FALSE);
+       g_return_val_if_fail (ebsql->priv->db != NULL, FALSE);
+
+       g_return_val_if_fail (ebsql->priv->in_transaction > 0, FALSE);
+
+       ebsql->priv->in_transaction--;
+
+       if (ebsql->priv->in_transaction == 0) {
+               success = book_backend_sqlite_exec (
+                       ebsql, "ROLLBACK", NULL, NULL, error);
+
+       }
+       return success;
+}
+
+static sqlite3_stmt *
+book_backend_sqlite_prepare_statement (EBookBackendSqlite *ebsql,
+                                        const gchar *stmt_str,
+                                        GError **error)
+{
+       sqlite3_stmt *stmt;
+       const gchar *stmt_tail = NULL;
+       gint ret;
+
+       ret = sqlite3_prepare_v2 (ebsql->priv->db, stmt_str, strlen (stmt_str), &stmt, &stmt_tail);
+
+       if (ret != SQLITE_OK) {
+               const gchar *errmsg = sqlite3_errmsg (ebsql->priv->db);
+               g_set_error_literal (error,
+                                    E_BOOK_SQL_ERROR,
+                                    E_BOOK_SQL_ERROR_OTHER,
+                                    errmsg);
+       } else if (stmt == NULL) {
+               g_set_error_literal (error,
+                                    E_BOOK_SQL_ERROR,
+                                    E_BOOK_SQL_ERROR_OTHER,
+                                    "Unknown error preparing SQL statement");
+       }
+
+       if (stmt_tail && stmt_tail[0])
+               g_warning ("Part of this statement was not parsed: %s", stmt_tail);
+
+       return stmt;
+}
+
+/* Convenience for running statements. After successfully
+ * binding all parameters, just return with this.
+ */
+static gboolean
+book_backend_sqlite_complete_statement (EBookBackendSqlite *ebsql,
+                                         sqlite3_stmt *stmt,
+                                         gint ret,
+                                         GError **error)
+{
+       if (ret == SQLITE_OK) {
+               ret = sqlite3_step (stmt);
+
+               if (ret == SQLITE_DONE)
+                       ret = SQLITE_OK;
+       }
+
+       if (ret != SQLITE_OK) {
+               const gchar *errmsg = sqlite3_errmsg (ebsql->priv->db);
+               g_set_error_literal (error,
+                                    E_BOOK_SQL_ERROR,
+                                    ret == SQLITE_CONSTRAINT ?
+                                    E_BOOK_SQL_ERROR_CONSTRAINT : E_BOOK_SQL_ERROR_OTHER,
+                                    errmsg);
+       } 
+
+       /* Reset / Clear at the end, regardless of error state */
+       sqlite3_reset (stmt);
+       sqlite3_clear_bindings (stmt);
+
+       return (ret == SQLITE_OK);
+}
+
+/******************************************************
+ *                  Summary Fields                    *
+ ******************************************************/
+static gint
+summary_field_array_index (GArray *array,
+                          EContactField field)
+{
+       gint i;
+
+       for (i = 0; i < array->len; i++) {
+               SummaryField *iter = &g_array_index (array, SummaryField, i);
+               if (field == iter->field)
+                       return i;
+       }
+
+       return -1;
+}
+
+static SummaryField *
+summary_field_append (GArray *array,
+                      EContactField field,
+                      gboolean *have_attr_list,
+                      GError **error)
+{
+       const gchar *dbname = NULL;
+       GType        type = G_TYPE_INVALID;
+       gint         index;
+       SummaryField new_field = { 0, };
+
+       if (field < 1 || field >= E_CONTACT_FIELD_LAST) {
+               g_set_error (
+                       error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_OTHER,
+                       _("Invalid contact field '%d' specified in summary"), field);
+               return NULL;
+       }
+
+       /* Avoid including the same field twice in the summary */
+       index = summary_field_array_index (array, field);
+       if (index >= 0)
+               return &g_array_index (array, SummaryField, index);
+
+       /* Resolve some exceptions, we store these
+        * specific contact fields with different names
+        * than those found in the EContactField table
+        */
+       switch (field) {
+       case E_CONTACT_UID:
+               dbname = "uid";
+               break;
+       case E_CONTACT_IS_LIST:
+               dbname = "is_list";
+               break;
+       default:
+               dbname = e_contact_field_name (field);
+               break;
+       }
+
+       type = e_contact_field_type (field);
+
+       if (type != G_TYPE_STRING &&
+           type != G_TYPE_BOOLEAN &&
+           type != E_TYPE_CONTACT_ATTR_LIST) {
+               g_set_error (
+                       error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_OTHER,
+                       _("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 NULL;
+       }
+
+       if (type == E_TYPE_CONTACT_ATTR_LIST && have_attr_list)
+               *have_attr_list = TRUE;
+
+       new_field.field  = field;
+       new_field.dbname = dbname;
+       new_field.type   = type;
+       new_field.index  = 0;
+       g_array_append_val (array, new_field);
+
+       return &g_array_index (array, SummaryField, array->len - 1);
+}
+
+static gboolean
+summary_field_remove (GArray *array,
+                      EContactField field)
+{
+       gint index;
+
+       index = summary_field_array_index (array, field);
+       if (index < 0)
+               return FALSE;
+
+       g_array_remove_index_fast (array, index);
+       return TRUE;
+}
+
+static void
+summary_fields_add_indexes (GArray *array,
+                            EContactField *indexes,
+                            EBookIndexType *index_types,
+                            gint n_indexes,
+                            gint *attr_list_indexes)
+{
+       gint i, j;
+
+       for (i = 0; i < array->len; i++) {
+               SummaryField *sfield = &g_array_index (array, SummaryField, i);
+
+               for (j = 0; j < n_indexes; j++) {
+                       if (sfield->field == indexes[j]) {
+                               sfield->index |= (1 << index_types[j]);
+
+                               if (sfield->type == E_TYPE_CONTACT_ATTR_LIST)
+                                       *attr_list_indexes |= (1 << index_types[j]);
+                       }
+               }
+       }
+}
+
+static SummaryField *
+summary_field_get (EBookBackendSqlite *ebsql,
+                  EContactField         field)
+{
+       gint i;
+
+       for (i = 0; i < ebsql->priv->n_summary_fields; i++) {
+               if (ebsql->priv->summary_fields[i].field == field)
+                       return &(ebsql->priv->summary_fields[i]);
+       }
+
+       return NULL;
+}
+
+static SummaryField *
+summary_field_get_by_name (EBookBackendSqlite *ebsql,
+                          const gchar        *field_name)
+{
+       EContactField field;
+
+       field = e_contact_field_id (field_name);
+
+       return summary_field_get (ebsql, field);
+}
+
+static GSList *
+summary_field_list_main_columns (SummaryField *field,
+                                const gchar  *folderid)
+{
+       GSList *columns = NULL;
+       ColumnInfo *info;
+
+       /* Only strings and booleans are supported in the main
+        * summary, string lists are supported in the auxiliary tables
+        */
+       if (field->type != G_TYPE_STRING &&
+           field->type != G_TYPE_BOOLEAN)
+               return NULL;
+
+       /* Normal / default column */
+       info = g_slice_new0 (ColumnInfo);
+       info->name = g_strdup (field->dbname);
+
+       if (field->type == G_TYPE_STRING)
+               info->type = "TEXT";
+       else if (field->type == G_TYPE_BOOLEAN)
+               info->type = "INTEGER";
+
+       if (field->field == E_CONTACT_UID)
+               info->extra = "PRIMARY KEY";
+
+       if ((field->index & INDEX_FLAG (PREFIX)) != 0)
+               info->index = g_strconcat ("INDEX_", field->dbname, "_", folderid, NULL);
+
+       columns = g_slist_prepend (columns, info);
+
+       /* Localized column, for storing sort keys */
+       if (field->type == G_TYPE_STRING && (field->index & INDEX_FLAG (SORT_KEY))) {
+               info = g_slice_new0 (ColumnInfo);
+               info->name  = g_strconcat (field->dbname, "_localized", NULL);
+               info->type  = "TEXT";
+               info->index = g_strconcat ("INDEX_", field->dbname, "_localized_", folderid, NULL);
+               columns = g_slist_prepend (columns, info);
+       }
+
+       /* Suffix match column */
+       if (field->type == G_TYPE_STRING && (field->index & INDEX_FLAG (SUFFIX)) != 0) {
+               info = g_slice_new0 (ColumnInfo);
+               info->name  = g_strconcat (field->dbname, "_reverse", NULL);
+               info->type  = "TEXT";
+               info->index = g_strconcat ("RINDEX_", field->dbname, "_", folderid, NULL);
+               columns = g_slist_prepend (columns, info);
+       }
+
+       /* Phone match column */
+       if (field->type == G_TYPE_STRING && (field->index & INDEX_FLAG (PHONE)) != 0) {
+               info = g_slice_new0 (ColumnInfo);
+               info->name  = g_strconcat (field->dbname, "_phone", NULL);
+               info->type  = "TEXT";
+               info->index = g_strconcat ("PINDEX_", field->dbname, "_", folderid, NULL);
+               columns = g_slist_prepend (columns, info);
+       }
+
+       return g_slist_reverse (columns);
+}
+
+static void
+column_info_free (ColumnInfo *info)
+{
+       if (info) {
+               g_free (info->name);
+               g_free (info->index);
+               g_slice_free (ColumnInfo, info);
+       }
+}
+
+/******************************************************
+ *          OLD PHONE NUMBER STUFF TO REMOVE          *
+ ******************************************************/
+typedef struct {
+       EBookBackendSqlite *ebsql;
+       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_sqlite_exec (info->ebsql, 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_sqlite_exec (
+               info->ebsql, 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_sqlite_exec (
+               info->ebsql, 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 country_code = data;
+       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);
+
+       /* First only check national portions */
+       cmp = e_strcmp2n (
+               sep1 + 1, len1 - (sep1 + 1 - str1),
+               sep2 + 1, len2 - (sep2 + 1 - str2));
+
+       /* On match we also have to check for potential country codes.
+        * Note that we can't just compare the full phone number string
+        * in the case that both numbers have a country code, because
+        * this would break the collations sorting order. As a result
+        * the binary search performed on the index would miss matches.
+        * Consider the index contains "|2215423789" and "+31|2215423789"
+        * while we look for "+1|2215423789". By performing full string
+        * compares in case of fully qualified numbers, we might check
+        * "+31|2215423789" first and then miss "|2215423789" because
+        * we traverse the binary tree in wrong direction.
+        */
+       if (cmp == 0) {
+               if (sep1 == str1) {
+                       if (sep2 != str2)
+                               cmp = e_strcmp2n (country_code, strlen (country_code), str2, sep2 - str2);
+               } else if (sep2 == str2) {
+                       cmp = e_strcmp2n (str1, sep1 - str1, country_code, strlen (country_code));
+               } else {
+                       /* Also compare the country code if the national number
+                        * matches and both numbers have a country code. */
+                       cmp = e_strcmp2n (str1, sep1 - str1, str2, sep2 - str2);
+               }
+       }
+
+       if (book_backend_sqlite_debug_level ()) {
+               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;
+       EBookBackendSqlite *ebsql = (EBookBackendSqlite *)data;
+
+       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_national") == 0) {
+               country_code = e_phone_number_get_country_code_for_region (NULL, NULL);
+
+               ret = sqlite3_create_collation_v2 (
+                       db, name, SQLITE_UTF8,
+                       g_strdup_printf ("+%d", country_code),
+                       ixphone_compare_national, g_free);
+       }
+
+       if (ret == SQLITE_OK) {
+               CollationInfo info = { ebsql, name };
+               GError *error = NULL;
+
+               if (!book_backend_sqlite_exec (
+                       ebsql, "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));
+       }
+}
+
+/******************************************************
+ *         Implementation for REGEX keyword           *
+ ******************************************************/
+static void
+ebsql_regexp (sqlite3_context *context,
+              gint argc,
+              sqlite3_value **argv)
+{
+       GRegex *regex;
+       const gchar *expression;
+       const gchar *text;
+
+       /* Reuse the same GRegex for all REGEXP queries with the same expression */
+       regex = sqlite3_get_auxdata (context, 0);
+       if (!regex) {
+               GError *error = NULL;
+
+               expression = (const gchar *) sqlite3_value_text (argv[0]);
+
+               regex = g_regex_new (expression, 0, 0, &error);
+
+               if (!regex) {
+                       sqlite3_result_error (
+                               context,
+                               error ?
+                               error->message :
+                               _("Error parsing regular expression"),
+                               -1);
+                       g_clear_error (&error);
+                       return;
+               }
+
+               /* SQLite will take care of freeing the GRegex when we're done with the query */
+               sqlite3_set_auxdata (context, 0, regex, (void (*)(gpointer)) g_regex_unref);
+       }
+
+       /* Now perform the comparison */
+       text = (const gchar *) sqlite3_value_text (argv[1]);
+       if (text != NULL) {
+               gboolean match;
+
+               match = g_regex_match (regex, text, 0, NULL);
+               sqlite3_result_int (context, match ? 1 : 0);
+       }
+}
+
+/**********************************************************
+ *  Schema Loading / Introspecing / Creating / Upgrating  *
+ **********************************************************
+ *
+ * Sequence of opening the SQLite and creating / upgrading the schema
+ *
+ * NOTE: This plan assumes that the _localized and _translit auxiliary columns
+ * are configurable via new index types in the ESourceBackendSummarySetup API.
+ *
+ * a.) Collect summary fields and index types from the ESourceBackendSummarySetup.
+ *
+ * b.) Open the SQLite.
+ *
+ *     In this stage setup various preferences, case insensitive LIKE statements,
+ *     WAL journaling mode, install REGEX handler, etc.
+ *
+ * c.) Initialize main tables
+ *
+ *     Create main table and add any missing columns which may have been introduced in
+ *     newer versions of the schema
+ *
+ * d.) Introspect current summary.
+ *
+ *     If the summary table exists we know there is already data, at this point
+ *     we override any configuration collected from the ESourceBackendSummarySetup,
+ *     currently we only support setting up the summary configuration once (this might change
+ *     to be more flexible in the future).
+ *
+ *     Furthermore, at this stage we need to collect any auxiliary columns which actually
+ *     exist, we prefer this to upgrading those based on the existing schema.
+ *
+ *     This step will also provide us with the currently set schema version number, if available
+ *
+ * e.) Build / upgrade the schema
+ *
+ *     e1.)
+ *
+ *     If the main table did not previously exist, try to build it in a single statement
+ *     and then create the appropriate indexes in other statements.
+ *
+ *     e2.)
+ *
+ *     Otherwise, if the schema version is found to be less than the current version, perform
+ *     upgrades.
+ *
+ *     On the main table there are a few columns to be added for various version upgrades.
+ *
+ *     Also, identify at this point which summary fields were added to the defaults
+ *     since the last version, these must be added to the SummaryField array and also
+ *     added to the schema.
+ *
+ *     This is specifically important for migration of the email_[1-4] columns as well
+ *     as auxiliary columns required for normal functionality of the cursor (i.e. the _translit column).
+ *
+ * f.) Record any changes that were made to the multi-value summary field record, i.e. save any
+ *     data that will be required for the next summary introspection phase (next time someone opens
+ *     the SQLite for this folder).
+ */
+
+static inline gint
+main_table_index_by_name (const gchar *name)
+{
+       gint i;
+
+       for (i = 0; i < G_N_ELEMENTS (main_table_columns); i++) {
+               if (g_strcmp0 (name, main_table_columns[i].name) == 0)
+                       return i;
+       }
+
+       return -1;
+}
+
+static gint
+check_main_table_columns (gpointer data,
+                         gint n_cols,
+                         gchar **cols,
+                         gchar **name)
+{
+       guint *columns_mask = (guint *)data;
+       gint i;
+
+       for (i = 0; i < n_cols; i++) {
+
+               if (g_strcmp0 (name[i], "name") == 0) {
+                       gint index = main_table_index_by_name (cols[i]);
+
+                       if (index >= 0)
+                               *columns_mask |= (1 << index);
+
+                       break;
+               }
+       }
+       return 0;
+}
+
+static gboolean
+book_backend_sqlite_init_sqlite (EBookBackendSqlite *ebsql,
+                                  const gchar *filename,
+                                  GError **error)
+{
+       gint ret;
+
+       e_sqlite3_vfs_init ();
+
+       ret = sqlite3_open (filename, &ebsql->priv->db);
+
+       /* XXX Remove me... old phone number stuff */
+       if (ret == SQLITE_OK)
+               ret = sqlite3_collation_needed (ebsql->priv->db, ebsql, create_collation);
+
+       if (ret == SQLITE_OK)
+               ret = sqlite3_create_function (
+                       ebsql->priv->db, "regexp", 2, SQLITE_UTF8, ebsql,
+                       ebsql_regexp, NULL, NULL);
+
+       if (ret != SQLITE_OK) {
+               if (!ebsql->priv->db) {
+                       g_set_error (
+                               error, E_BOOK_SQL_ERROR,
+                               E_BOOK_SQL_ERROR_OTHER,
+                               _("Insufficient memory"));
+               } else {
+                       const gchar *errmsg;
+                       errmsg = sqlite3_errmsg (ebsql->priv->db);
+                       d (g_printerr ("Can't open database %s: %s\n", filename, errmsg));
+                       g_set_error_literal (
+                               error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_OTHER, errmsg);
+                       sqlite3_close (ebsql->priv->db);
+               }
+               return FALSE;
+       }
+
+       book_backend_sqlite_exec (ebsql, "ATTACH DATABASE ':memory:' AS mem",
+                                   NULL, NULL, NULL);
+       book_backend_sqlite_exec (ebsql, "PRAGMA foreign_keys = ON",
+                                   NULL, NULL, NULL);
+       book_backend_sqlite_exec (ebsql, "PRAGMA case_sensitive_like = ON",
+                                   NULL, NULL, NULL);
+
+       return TRUE;
+}
+
+static gboolean
+book_backend_sqlite_init_folders (EBookBackendSqlite *ebsql,
+                                   gint *previous_schema,
+                                   GError **error)
+{
+       GString *string;
+       gint version = 0, i;
+       guint existing_columns_mask = 0;
+       gboolean success;
+
+       if (!book_backend_sqlite_start_transaction (ebsql, error))
+               return FALSE;
+
+       string = g_string_sized_new (MAIN_TABLE_ENTRY_BYTES * G_N_ELEMENTS (main_table_columns));
+       g_string_append (string, "CREATE TABLE IF NOT EXISTS folders (");
+       for (i = 0; i < G_N_ELEMENTS (main_table_columns); i++) {
+
+               if (i > 0)
+                       g_string_append (string, ", ");
+
+               g_string_append (string, main_table_columns[i].name);
+               g_string_append_c (string, ' ');
+
+               g_string_append (string, main_table_columns[i].type);
+
+               if (main_table_columns[i].extra) {
+                       g_string_append_c (string, ' ');
+                       g_string_append (string, main_table_columns[i].extra);
+               }
+       }
+       g_string_append_c (string, ')');
+
+       /* Create main folders table */
+       success = book_backend_sqlite_exec (ebsql,
+                                             string->str,
+                                             NULL, NULL, error);
+       g_string_free (string, TRUE);
+       if (!success)
+               goto rollback;
+
+       /* Fetch the version, it should be the same for all folders (hence the LIMIT). */
+       if (!book_backend_sqlite_exec (
+               ebsql, "SELECT version FROM folders LIMIT 1",
+               get_int_cb, &version, error))
+               goto rollback;
+
+       /* Check which columns in the main table already exist */
+       if (!book_backend_sqlite_exec (
+               ebsql, "PRAGMA table_info (folders)",
+               check_main_table_columns, &existing_columns_mask, error))
+               goto rollback;
+
+       /* Add columns which may be missing */
+       for (i = 0; i < G_N_ELEMENTS (main_table_columns); i++) {
+               ColumnInfo *info = &(main_table_columns[i]);
+
+               if ((existing_columns_mask & (1 << i)) != 0)
+                       continue;
+
+               if (!book_backend_sqlite_exec_printf (
+                       ebsql, "ALTER TABLE folders ADD COLUMN %s %s %s",
+                       NULL, NULL, error, info->name, info->type,
+                       info->extra ? info->extra : ""))
+                       goto rollback;
+       }
+
+       /* Special case upgrade for schema versions 3 & 4.
+        * 
+        * Drops the reverse_multivalues column.
+        */
+       if (version >= 3 && version < 5) {
+
+               if (!book_backend_sqlite_exec (
+                       ebsql, 
+                       "UPDATE folders SET "
+                               "multivalues = REPLACE(RTRIM(REPLACE("
+                                       "multivalues || ':', ':', "
+                                       "CASE reverse_multivalues "
+                                               "WHEN 0 THEN ';prefix ' "
+                                               "ELSE ';prefix;suffix ' "
+                                       "END)), ' ', ':'), "
+                               "reverse_multivalues = NULL",
+                       NULL, NULL, error))
+                       goto rollback;
+       }
+
+       /* Finish the eventual upgrade by storing the current schema version.
+        */
+       if (version >= 1 && version < FOLDER_VERSION) {
+
+               if (!book_backend_sqlite_exec_printf (
+                       ebsql, "UPDATE folders SET version = %d",
+                       NULL, NULL, error, FOLDER_VERSION))
+                       goto rollback;
+       }
+
+       /* Remember the schema version for later use and finish the transaction. */
+       *previous_schema = version;
+       return book_backend_sqlite_commit_transaction (ebsql, error);
+
+ rollback:
+       /* The GError is already set. */
+       book_backend_sqlite_rollback_transaction (ebsql, NULL);
+
+       *previous_schema = 0;
+       return FALSE;
+}
+
+static gboolean
+book_backend_sqlite_init_keys (EBookBackendSqlite *ebsql,
+                                GError **error)
+{
+       if (!book_backend_sqlite_start_transaction (ebsql, error))
+               return FALSE;
+
+       /* Create a child table to store key/value pairs for a folder. */
+       if (!book_backend_sqlite_exec (
+               ebsql, 
+               "CREATE TABLE IF NOT EXISTS keys ("
+               " key TEXT PRIMARY KEY,"
+               " value TEXT,"
+               " folder_id TEXT REFERENCES folders)",
+               NULL, NULL, error))
+               goto rollback;
+
+       /* Add an index on the keys */
+       if (!book_backend_sqlite_exec (
+               ebsql, 
+               "CREATE INDEX IF NOT EXISTS keysindex ON keys (folder_id)",
+               NULL, NULL, error))
+               goto rollback;
+
+       return book_backend_sqlite_commit_transaction (ebsql, error);
+
+ rollback:
+       /* The GError is already set. */
+       book_backend_sqlite_rollback_transaction (ebsql, NULL);
+
+       return FALSE;
+}
+
+static gchar *
+format_multivalues (EBookBackendSqlite *ebsql)
+{
+       gint i;
+       GString *string;
+       gboolean first = TRUE;
+
+       string = g_string_new (NULL);
+
+       for (i = 0; i < ebsql->priv->n_summary_fields; i++) {
+               if (ebsql->priv->summary_fields[i].type == E_TYPE_CONTACT_ATTR_LIST) {
+                       if (first)
+                               first = FALSE;
+                       else
+                               g_string_append_c (string, ':');
+
+                       g_string_append (string, ebsql->priv->summary_fields[i].dbname);
+
+                       /* E_BOOK_INDEX_SORT_KEY is not supported in the multivalue fields */
+                       if ((ebsql->priv->summary_fields[i].index & INDEX_FLAG (PREFIX)) != 0)
+                               g_string_append (string, ";prefix");
+                       if ((ebsql->priv->summary_fields[i].index & INDEX_FLAG (SUFFIX)) != 0)
+                               g_string_append (string, ";suffix");
+                       if ((ebsql->priv->summary_fields[i].index & INDEX_FLAG (PHONE)) != 0)
+                               g_string_append (string, ";phone");
+               }
+       }
+
+       return g_string_free (string, FALSE);
+}
+
+static gboolean
+book_backend_sqlite_add_folder (EBookBackendSqlite *ebsql,
+                               gboolean *already_exists,
+                               GError **error)
+{
+       gboolean success;
+       gchar *multivalues;
+       gint count = 0;
+
+       if (!book_backend_sqlite_start_transaction (ebsql, error))
+               return FALSE;
+
+       /* Check if this folder is already declared in the main folders table */
+       success = book_backend_sqlite_exec_printf (
+               ebsql, 
+               "SELECT count(*) FROM sqlite_master WHERE type='table' AND name=%Q;",
+               get_count_cb, &count, error, ebsql->priv->folderid);
+       if (!success)
+               goto rollback;
+
+       if (count == 0) {
+               const gchar *lc_collate;
+
+               multivalues = format_multivalues (ebsql);
+               lc_collate = setlocale (LC_COLLATE, NULL);
+
+               success = book_backend_sqlite_exec_printf (
+                       ebsql,
+                       "INSERT OR IGNORE INTO folders"
+                       " ( folder_id, version, multivalues, lc_collate ) "
+                       "VALUES ( %Q, %d, %Q, %Q ) ",
+                       NULL, NULL, error,
+                       ebsql->priv->folderid, FOLDER_VERSION, multivalues, lc_collate);
+
+               g_free (multivalues);
+
+               if (!success)
+                       goto rollback;
+       }
+
+       if (already_exists)
+               *already_exists = (count > 0);
+
+       return book_backend_sqlite_commit_transaction (ebsql, error);
+
+rollback:
+       book_backend_sqlite_rollback_transaction (ebsql, NULL);
+
+       return FALSE;
+}
+
+static gboolean
+book_backend_sqlite_introspect_summary (EBookBackendSqlite *ebsql,
+                                       gint previous_schema,
+                                       GSList **introspected_columns,
+                                       GError **error)
+{
+       gboolean success, have_attr_list;
+       GSList *summary_columns = NULL, *l;
+       GArray *summary_fields = NULL;
+       gchar *multivalues = NULL;
+       gint i, j;
+
+       success = book_backend_sqlite_exec_printf (
+               ebsql, "PRAGMA table_info (%Q);",
+               get_columns_cb, &summary_columns, error, ebsql->priv->folderid);
+
+       if (!success)
+               goto introspect_summary_finish;
+
+       summary_columns = g_slist_reverse (summary_columns);
+       summary_fields = g_array_new (FALSE, FALSE, sizeof (SummaryField));
+
+       /* Introspect the normal summary fields */
+       for (l = summary_columns; l; l = l->next) {
+               EContactField field;
+               const gchar *col = l->data;
+               gchar *p;
+               gint computed = 0;
+               gchar *freeme = NULL;
+
+               /* Note that we don't have any way to introspect
+                * E_BOOK_INDEX_PREFIX, this is not important because if
+                * the prefix index is specified, it will be created
+                * the first time the SQLite tables are created, so
+                * it's not important to ensure prefix indexes after
+                * introspecting the summary.
+                */
+
+               /* Check if we're parsing a reverse field */
+               if ((p = strstr (col, "_reverse")) != NULL) {
+                       computed = INDEX_FLAG (SUFFIX);
+                       freeme   = g_strndup (col, p - col);
+                       col      = freeme;
+               } else if ((p = strstr (col, "_phone")) != NULL) {
+                       computed = INDEX_FLAG (PHONE);
+                       freeme   = g_strndup (col, p - col);
+                       col      = freeme;
+               } else if ((p = strstr (col, "_localized")) != NULL) {
+                       computed = INDEX_FLAG (SORT_KEY);
+                       freeme   = g_strndup (col, p - col);
+                       col      = freeme;
+               }
+
+               /* First check exception fields */
+               if (g_ascii_strcasecmp (col, "uid") == 0)
+                       field = E_CONTACT_UID;
+               else if (g_ascii_strcasecmp (col, "is_list") == 0)
+                       field = E_CONTACT_IS_LIST;
+               else
+                       field = e_contact_field_id (col);
+
+               /* Check for parse error */
+               if (field == 0) {
+                       g_set_error (
+                               error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_OTHER,
+                               _("Error introspecting unknown summary field '%s'"), col);
+                       success = FALSE;
+                       g_free (freeme);
+                       break;
+               }
+
+               /* 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 (computed) {
+                       for (i = 0; i < summary_fields->len; i++) {
+                               SummaryField *iter = &g_array_index (summary_fields, SummaryField, i);
+
+                               if (iter->field == field) {
+                                       iter->index |= computed;
+                                       break;
+                               }
+                       }
+               } else {
+                       summary_field_append (summary_fields, field, NULL, NULL);
+               }
+
+               g_free (freeme);
+       }
+
+       if (!success)
+               goto introspect_summary_finish;
+
+       /* Introspect the multivalied summary fields */
+       success = book_backend_sqlite_exec_printf (
+               ebsql,
+               "SELECT multivalues FROM folders WHERE folder_id = %Q",
+               get_string_cb, &multivalues, error, ebsql->priv->folderid);
+
+       if (!success)
+               goto introspect_summary_finish;
+
+       ebsql->priv->attr_list_indexes = 0;
+       ebsql->priv->have_attr_list = have_attr_list = FALSE;
+
+       if (multivalues) {
+               gchar **fields = g_strsplit (multivalues, ":", 0);
+
+               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 = summary_field_append (summary_fields, field, &have_attr_list, NULL);
+
+                       if (iter) {
+                               for (j = 1; params[j]; ++j) {
+                                       /* Sort keys not supported for multivalued fields */
+                                       if (strcmp (params[j], "prefix") == 0) {
+                                               iter->index |= INDEX_FLAG (PREFIX);
+                                       } else if (strcmp (params[j], "suffix") == 0) {
+                                               iter->index |= INDEX_FLAG (SUFFIX);
+                                       } else if (strcmp (params[j], "phone") == 0) {
+                                               iter->index |= INDEX_FLAG (PHONE);
+                                       }
+                               }
+
+                               ebsql->priv->attr_list_indexes |= iter->index;
+                       }
+
+                       g_strfreev (params);
+               }
+
+               ebsql->priv->have_attr_list = have_attr_list;
+
+               g_strfreev (fields);
+       }
+
+       /* HARD CODE UP AHEAD
+        *
+        * Now we're finished introspecting, if the summary is from a previous version,
+        * we need to add any summary fields which we're added to the default summary
+        * since the schema version which was introduced here
+        */
+       if (previous_schema >= 1) {
+               SummaryField *summary_field;
+
+               /* XXX We have to take care of the localized columns too now
+                *
+                * By default in schema version 7, we added localized columns for
+                * every G_TYPE_STRING field
+                */
+               if (previous_schema < 8) {
+
+                       /* We used to keep 4 email fields in the summary, before we supported
+                        * the multivaliued E_CONTACT_EMAIL... convert the old summary to use
+                        * the multivaliued field instead.
+                        */
+                       if (summary_field_array_index (summary_fields, E_CONTACT_EMAIL_1) >= 0 &&
+                           summary_field_array_index (summary_fields, E_CONTACT_EMAIL_2) >= 0 &&
+                           summary_field_array_index (summary_fields, E_CONTACT_EMAIL_3) >= 0 &&
+                           summary_field_array_index (summary_fields, E_CONTACT_EMAIL_4) >= 0) {
+
+                               summary_field_remove (summary_fields, E_CONTACT_EMAIL_1);
+                               summary_field_remove (summary_fields, E_CONTACT_EMAIL_2);
+                               summary_field_remove (summary_fields, E_CONTACT_EMAIL_3);
+                               summary_field_remove (summary_fields, E_CONTACT_EMAIL_4);
+
+                               summary_field = summary_field_append (summary_fields, E_CONTACT_EMAIL, 
&have_attr_list, NULL);
+                               summary_field->index |= INDEX_FLAG (PREFIX);
+
+                               ebsql->priv->have_attr_list = TRUE;
+                               ebsql->priv->attr_list_indexes |= INDEX_FLAG (PREFIX);
+                       }
+               }
+       }
+
+ introspect_summary_finish:
+
+       /* Apply the introspected summary fields */
+       if (success) {
+               g_free (ebsql->priv->summary_fields);
+               ebsql->priv->n_summary_fields = summary_fields->len;
+               ebsql->priv->summary_fields = (SummaryField *) g_array_free (summary_fields, FALSE);
+
+               *introspected_columns = summary_columns;
+       } else if (summary_fields) {
+               g_array_free (summary_fields, TRUE);
+               g_slist_free_full (summary_columns, (GDestroyNotify) g_free);
+       }
+
+       g_free (multivalues);
+
+       return success;
+}
+
+static gboolean
+book_backend_sqlite_init_contacts (EBookBackendSqlite *ebsql,
+                                  GSList *introspected_columns,
+                                  GError **error)
+{
+       gint i;
+       gboolean success = TRUE;
+       GString *string;
+       GSList *summary_columns = NULL, *l;
+
+       /* Get a list of all columns and indexes which should be present
+        * in the main summary table */
+       for (i = 0; i < ebsql->priv->n_summary_fields; i++) {
+               SummaryField *field = &(ebsql->priv->summary_fields[i]);
+
+               l = summary_field_list_main_columns (field, ebsql->priv->folderid);
+               summary_columns = g_slist_concat (summary_columns, l);
+       }
+
+       /* Create the main contacts table for this folder
+        */
+       string = g_string_sized_new (32 * g_slist_length (summary_columns));
+       g_string_append (string, "CREATE TABLE IF NOT EXISTS %Q (");
+
+       for (l = summary_columns; l; l = l->next) {
+               ColumnInfo *info = l->data;
+
+               if (l != summary_columns)
+                       g_string_append (string, ", ");
+
+               g_string_append (string, info->name);
+               g_string_append_c (string, ' ');
+
+               g_string_append (string, info->type);
+
+               if (info->extra) {
+                       g_string_append_c (string, ' ');
+                       g_string_append (string, info->extra);
+               }
+       }
+       g_string_append (string, ", vcard TEXT)");
+
+       success = book_backend_sqlite_exec_printf (
+               ebsql, string->str,
+               NULL, NULL, error, ebsql->priv->folderid);
+
+       g_string_free (string, TRUE);
+
+       /* If we introspected something, let's first adjust the contacts table
+        * so that it includes the right columns */
+       if (introspected_columns) {
+
+               /* Add any missing columns which are in the summary fields but
+                * not found in the contacts table
+                */
+               for (l = summary_columns; success && l; l = l->next) {
+                       ColumnInfo *info = l->data;
+
+                       if (g_slist_find_custom (introspected_columns,
+                                                info->name, (GCompareFunc)g_ascii_strcasecmp))
+                               continue;
+
+                       success = book_backend_sqlite_exec_printf (
+                               ebsql, "ALTER TABLE %Q ADD COLUMN %s %s %s",
+                               NULL, NULL, error, ebsql->priv->folderid,
+                               info->name, info->type,
+                               info->extra ? info->extra : "");
+               }
+       }
+
+       /* Add indexes to columns in the main contacts table
+        */
+       for (l = summary_columns; success && l; l = l->next) {
+               ColumnInfo *info = l->data;
+
+               if (!info->index)
+                       continue;
+
+               success = book_backend_sqlite_exec_printf (
+                       ebsql, "CREATE INDEX IF NOT EXISTS %Q ON %Q (%s)",
+                       NULL, NULL, error,
+                       info->index, ebsql->priv->folderid, info->name);
+       }
+
+       g_slist_free_full (summary_columns, (GDestroyNotify)column_info_free);
+
+       return success;
+}
+
+static gboolean
+book_backend_sqlite_init_aux_tables (EBookBackendSqlite *ebsql,
+                                    GError **error)
+{
+       GString *string;
+       gboolean success = TRUE;
+       gchar *stmt, *tmp;
+
+       /* Construct the create statement from the attribute list summary table */
+       if (ebsql->priv->have_attr_list) {
+               string = g_string_new ("CREATE TABLE IF NOT EXISTS %Q ( uid TEXT NOT NULL REFERENCES %Q(uid), 
"
+                       "field TEXT, value TEXT");
+
+               if ((ebsql->priv->attr_list_indexes & INDEX_FLAG (SUFFIX)) != 0)
+                       g_string_append (string, ", value_reverse TEXT");
+               if ((ebsql->priv->attr_list_indexes & INDEX_FLAG (PHONE)) != 0)
+                       g_string_append (string, ", value_phone TEXT");
+
+               g_string_append_c (string, ')');
+
+               tmp = g_strdup_printf ("%s_lists", ebsql->priv->folderid);
+               stmt = sqlite3_mprintf (string->str, tmp, ebsql->priv->folderid);
+               g_string_free (string, TRUE);
+
+               success = book_backend_sqlite_exec (ebsql, stmt, NULL, NULL, error);
+               sqlite3_free (stmt);
+
+               /* Give the UID an index in this table, always */
+               success = book_backend_sqlite_exec_printf (
+                       ebsql, "CREATE INDEX IF NOT EXISTS LISTINDEX ON %Q (uid)",
+                       NULL, NULL, error, tmp);
+
+               /* Create indexes if specified */
+               if (success && (ebsql->priv->attr_list_indexes & INDEX_FLAG (PREFIX)) != 0) {
+                       success = book_backend_sqlite_exec_printf (
+                               ebsql, "CREATE INDEX IF NOT EXISTS VALINDEX ON %Q (value)",
+                               NULL, NULL, error, tmp);
+               }
+
+               if (success && (ebsql->priv->attr_list_indexes & INDEX_FLAG (SUFFIX)) != 0) {
+                       success = book_backend_sqlite_exec_printf (
+                               ebsql, "CREATE INDEX IF NOT EXISTS RVALINDEX ON %Q (value_reverse)",
+                               NULL, NULL, error, tmp);
+               }
+
+               if (success && (ebsql->priv->attr_list_indexes & INDEX_FLAG (PHONE)) != 0) {
+                       success = book_backend_sqlite_exec_printf (
+                               ebsql, "CREATE INDEX IF NOT EXISTS PVALINDEX ON %Q (value_phone)",
+                               NULL, NULL, error, tmp);
+               }
+
+               g_free (tmp);
+       }
+
+       return success;
+}
+
+static gboolean
+book_backend_sqlite_init_statements (EBookBackendSqlite *ebsql,
+                                    GError **error)
+{
+       sqlite3_stmt *stmt;
+       gint i;
+
+       ebsql->priv->insert_stmt = book_backend_sqlite_prepare_insert (ebsql, FALSE, error);
+       if (!ebsql->priv->insert_stmt)
+               goto preparation_failed;
+
+       ebsql->priv->replace_stmt = book_backend_sqlite_prepare_insert (ebsql, TRUE, error);
+       if (!ebsql->priv->replace_stmt)
+               goto preparation_failed;
+
+       ebsql->priv->multi_deletes =
+               g_hash_table_new_full (g_direct_hash, g_direct_equal,
+                                      NULL,
+                                      (GDestroyNotify)sqlite3_finalize);
+       ebsql->priv->multi_inserts =
+               g_hash_table_new_full (g_direct_hash, g_direct_equal,
+                                      NULL,
+                                      (GDestroyNotify)sqlite3_finalize);
+
+       for (i = 0; i < ebsql->priv->n_summary_fields; i++) {
+               SummaryField *field = &(ebsql->priv->summary_fields[i]);
+
+               if (field->type != E_TYPE_CONTACT_ATTR_LIST)
+                       continue;
+
+               stmt = book_backend_sqlite_prepare_multi_insert (
+                       ebsql, field, error);
+
+               if (!stmt)
+                       goto preparation_failed;
+
+               g_hash_table_insert (ebsql->priv->multi_inserts,
+                                    GUINT_TO_POINTER (field->field),
+                                    stmt);
+       }
+
+       /* XXX HACK, for now there is only one delete statement and
+        * only one multi value table... that will change soon */
+       stmt = book_backend_sqlite_prepare_multi_delete (ebsql, NULL, error);
+       if (!stmt)
+               goto preparation_failed;
+
+       g_hash_table_insert (ebsql->priv->multi_deletes,
+                            GUINT_TO_POINTER (0),
+                            stmt);
+
+       return TRUE;
+
+ preparation_failed:
+
+       return FALSE;
+}
+
+static gboolean
+book_backend_sqlite_upgrade (EBookBackendSqlite *ebsql,
+                            const gchar        *region,
+                            const gchar        *lc_collate,
+                            GError            **error)
+{
+       gboolean success = FALSE;
+       GSList *vcard_data = NULL;
+       GSList *l;
+
+       success = book_backend_sqlite_exec_printf (
+               ebsql, "SELECT uid, vcard, NULL FROM %Q",
+               collect_full_results_cb, &vcard_data, error, ebsql->priv->folderid);
+
+       for (l = vcard_data; success && l; l = l->next) {
+               EbSqlSearchData *const s_data = l->data;
+               EContact *contact = NULL;
+
+               /* It can be we're opening a light summary which was created without
+                * storing the vcards, such as was used in EDS versions 3.2 to 3.6.
+                *
+                * In this case we just want to skip the contacts we can't load
+                * and leave them as is in the SQLite, they will be added from
+                * the old BDB in the case of a migration anyway.
+                */
+               if (s_data->vcard)
+                       contact = e_contact_new_from_vcard_with_uid (s_data->vcard, s_data->uid);
+
+               if (contact == NULL)
+                       continue;
+
+               success = book_backend_sqlite_insert_contact (ebsql, contact, TRUE, region, error);
+
+               g_object_unref (contact);
+       }
+
+       g_slist_free_full (vcard_data, (GDestroyNotify)e_book_backend_sqlite_search_data_free);
+
+       if (success)
+               success = book_backend_sqlite_exec_printf (
+                       ebsql, "UPDATE folders SET countrycode = %Q WHERE folder_id = %Q",
+                       NULL, NULL, error, region, ebsql->priv->folderid);
+
+       if (success)
+               success = book_backend_sqlite_exec_printf (
+                       ebsql, "UPDATE folders SET lc_collate = %Q WHERE folder_id = %Q",
+                       NULL, NULL, error, lc_collate, ebsql->priv->folderid);
+
+       return success;
+}
+
+static gboolean
+book_backend_sqlite_init_locale (EBookBackendSqlite *ebsql,
+                                gint previous_schema,
+                                gboolean already_exists,
+                                GError **error)
+{
+       gchar *stored_lc_collate = NULL;
+       gchar *current_region = NULL;
+       const gchar *lc_collate = NULL;
+       gboolean success = TRUE;
+       gboolean relocalized = FALSE;
+
+       if (e_phone_number_is_supported ()) {
+               current_region = e_phone_number_get_default_region (error);
+
+               if (current_region == NULL)
+                       return FALSE;
+       }
+
+       /* Get the locale setting for this addressbook */
+       if (already_exists) {
+               success = book_backend_sqlite_exec_printf (
+                       ebsql, "SELECT lc_collate FROM folders WHERE folder_id = %Q",
+                       get_string_cb, &stored_lc_collate, error, ebsql->priv->folderid);
+
+               lc_collate = stored_lc_collate;
+       }
+
+       if (!lc_collate)
+               /* When creating a new addressbook, or upgrading from a version
+                * where we did not have any locale setting; default to system locale
+                */
+               lc_collate = setlocale (LC_COLLATE, NULL);
+
+       /* Before touching any data, make sure we have a valid ECollator */
+       if (success) {
+               success = book_backend_sqlite_set_locale_internal (ebsql, lc_collate, error);
+       }
+
+       /* Schema 8 is when we moved from EBookBackendSqliteDB */
+       if (success && previous_schema >= 1 && previous_schema < 8) {
+               gint is_populated = 0;
+
+               /* We need to hold on to the value of any previously set 'is_populated' flag */
+               success = book_backend_sqlite_exec_printf (
+                       ebsql, "SELECT is_populated FROM folders WHERE folder_id = %Q",
+                       get_int_cb, &is_populated, error, ebsql->priv->folderid);
+
+               success = e_book_backend_sqlite_set_key_value_int (
+                       ebsql, E_BOOK_SQL_IS_POPULATED_KEY, is_populated, error);
+       }
+
+       /* Need to relocalize the whole thing if the schema has been upgraded to version 7 */
+       if (success && previous_schema >= 1 && previous_schema < 7) {
+               success = book_backend_sqlite_upgrade (ebsql, current_region, lc_collate, error);
+               relocalized = TRUE;
+       }
+
+       /* We may need to relocalize for a country code change */
+       if (success && relocalized == FALSE && e_phone_number_is_supported ()) {
+               gchar *stored_region = NULL;
+
+               success = book_backend_sqlite_exec_printf (
+                       ebsql, "SELECT countrycode FROM folders WHERE folder_id = %Q",
+                       get_string_cb, &stored_region, error, ebsql->priv->folderid);
+
+               if (success && g_strcmp0 (current_region, stored_region) != 0) {
+                       success = book_backend_sqlite_upgrade (ebsql, current_region, lc_collate, error);
+                       relocalized = TRUE;
+               }
+
+               g_free (stored_region);
+       }
+
+       g_free (current_region);
+       g_free (stored_lc_collate);
+
+       return success;
+}
+
+/**********************************************************
+ *                   Inserting Contacts                   *
+ **********************************************************/
+static gchar *
+convert_phone (const gchar *normal,
+               const gchar *default_region)
+{
+       EPhoneNumber *number = NULL;
+       gchar *indexed_phone_number = NULL;
+       gchar *national_number = NULL;
+       gint country_code = 0;
+
+       /* Don't warn about erronous phone number strings, it's a perfectly normal
+        * use case for users to enter notes instead of phone numbers in the phone
+        * number contact fields, such as "Ask Jenny for Lisa's phone number"
+        */
+       if (normal && e_phone_number_is_supported ())
+               number = e_phone_number_from_string (normal, default_region, NULL);
+
+       if (number) {
+               EPhoneNumberCountrySource source;
+
+               national_number = e_phone_number_get_national_number (number);
+               country_code = e_phone_number_get_country_code (number, &source);
+               e_phone_number_free (number);
+
+               if (source == E_PHONE_NUMBER_COUNTRY_FROM_DEFAULT)
+                       country_code = 0;
+       }
+
+       if (national_number) {
+               indexed_phone_number = country_code
+                       ? g_strdup_printf ("+%d|%s", country_code, national_number)
+                       : g_strconcat ("|", national_number, NULL);
+
+               g_free (national_number);
+       }
+
+       return indexed_phone_number;
+}
+
+static sqlite3_stmt *
+book_backend_sqlite_prepare_multi_delete (EBookBackendSqlite *ebsql,
+                                         SummaryField *field,
+                                         GError **error)
+{
+       sqlite3_stmt *stmt = NULL;
+       gchar *list_table = g_strconcat (ebsql->priv->folderid, "_lists", NULL);
+       gchar *stmt_str;
+
+       stmt_str = sqlite3_mprintf ("DELETE FROM %Q WHERE uid = :uid", list_table);
+       stmt = book_backend_sqlite_prepare_statement (ebsql, stmt_str, error);
+       sqlite3_free (stmt_str);
+
+       return stmt;
+}
+
+static gboolean
+book_backend_sqlite_run_multi_delete (EBookBackendSqlite *ebsql,
+                                     EContactField field,
+                                     const gchar *uid,
+                                     GError **error)
+{
+       sqlite3_stmt *stmt;
+       gint ret;
+
+       stmt = g_hash_table_lookup (ebsql->priv->multi_deletes, GUINT_TO_POINTER (0));
+
+       /* This can return an error if a previous call to sqlite3_step() had errors,
+        * so let's just ignore any error in this case
+        */
+       sqlite3_reset (stmt);
+
+       /* Clear all previously set values */
+       ret = sqlite3_clear_bindings (stmt);
+
+       /* Set the UID host parameter statically */
+       if (ret == SQLITE_OK)
+               ret = sqlite3_bind_text (stmt, 1, uid, -1, SQLITE_STATIC);
+
+       /* Run the statement */
+       return book_backend_sqlite_complete_statement (ebsql, stmt, ret, error);
+}
+
+static sqlite3_stmt *
+book_backend_sqlite_prepare_multi_insert (EBookBackendSqlite *ebsql,
+                                         SummaryField *field,
+                                         GError **error)
+{
+       sqlite3_stmt *stmt = NULL;
+       gchar *list_table;
+       gchar *stmt_str;
+       GString *string;
+
+       string = g_string_sized_new (INSERT_MULTI_STMT_BYTES);
+
+       list_table = g_strconcat (ebsql->priv->folderid, "_lists", NULL);
+       stmt_str = sqlite3_mprintf ("INSERT INTO %Q (uid, field, value", list_table);
+
+       g_string_append (string, stmt_str);
+
+       sqlite3_free (stmt_str);
+       g_free (list_table);
+
+       if ((ebsql->priv->attr_list_indexes & INDEX_FLAG (SUFFIX)) != 0 &&
+           (field->index & INDEX_FLAG (SUFFIX)) != 0)
+               g_string_append (string, ", value_reverse");
+
+       if ((ebsql->priv->attr_list_indexes & INDEX_FLAG (PHONE)) != 0 &&
+           (field->index & INDEX_FLAG (PHONE)) != 0)
+               g_string_append (string, ", value_phone");
+
+       g_string_append (string, ") VALUES (:uid, :field, :value");
+
+       if ((ebsql->priv->attr_list_indexes & INDEX_FLAG (SUFFIX)) != 0 &&
+           (field->index & INDEX_FLAG (SUFFIX)) != 0)
+               g_string_append (string, ", :value_reverse");
+
+       if ((ebsql->priv->attr_list_indexes & INDEX_FLAG (PHONE)) != 0 &&
+           (field->index & INDEX_FLAG (PHONE)) != 0)
+               g_string_append (string, ", :value_phone");
+
+       g_string_append_c (string, ')');
+
+       stmt = book_backend_sqlite_prepare_statement (ebsql, string->str, error);
+       g_string_free (string, TRUE);
+
+       return stmt;
+}
+
+static gboolean
+book_backend_sqlite_run_multi_insert_one (EBookBackendSqlite *ebsql,
+                                           sqlite3_stmt *stmt,
+                                           SummaryField *field,
+                                           const gchar *uid,
+                                           const gchar *dbname,
+                                           const gchar *value,
+                                           const gchar *default_region,
+                                           GError **error)
+{
+       EBookBackendSqlitePrivate *priv = ebsql->priv;
+       gchar *normal = e_util_utf8_normalize (value);
+       gchar *str;
+       gint ret, param_idx = 1;
+
+       ret = sqlite3_bind_text (stmt, param_idx++, uid, -1, SQLITE_STATIC);
+
+       if (ret == SQLITE_OK)
+               ret = sqlite3_bind_text (stmt, param_idx++, dbname, -1, SQLITE_STATIC);
+
+       if (ret == SQLITE_OK)
+               ret = sqlite3_bind_text (stmt, param_idx++, normal, -1, g_free);
+
+       if (ret == SQLITE_OK &&
+           (priv->attr_list_indexes & INDEX_FLAG (SUFFIX)) != 0 &&
+           (field->index & INDEX_FLAG (SUFFIX)) != 0) {
+
+               if (normal)
+                       str = g_utf8_strreverse (normal, -1);
+               else
+                       str = NULL;
+
+               ret = sqlite3_bind_text (stmt, param_idx++, str, -1, g_free);
+       }
+
+       if (ret == SQLITE_OK &&
+           (priv->attr_list_indexes & INDEX_FLAG (PHONE)) != 0 &&
+           (field->index & INDEX_FLAG (PHONE)) != 0) {
+
+               str = convert_phone (normal, default_region);
+               sqlite3_bind_text (stmt, param_idx++, str, -1, g_free);
+       }
+
+       /* Run the statement */
+       return book_backend_sqlite_complete_statement (ebsql, stmt, ret, error);
+}
+
+static gboolean
+book_backend_sqlite_run_multi_insert (EBookBackendSqlite *ebsql,
+                                     SummaryField *field,
+                                     const gchar *uid,
+                                     EContact *contact,
+                                     const gchar *default_region,
+                                     GError **error)
+{
+       sqlite3_stmt *stmt;
+       GList *values, *l;
+       gboolean success = TRUE;
+
+       stmt = g_hash_table_lookup (ebsql->priv->multi_inserts, GUINT_TO_POINTER (field->field));
+       values = e_contact_get (contact, field->field);
+
+       for (l = values; success && l != NULL; l = l->next) {
+               gchar *value = (gchar *) l->data;
+
+               success = book_backend_sqlite_run_multi_insert_one (
+                       ebsql, stmt, field, uid, field->dbname, value,
+                       default_region, error);
+       }
+
+       /* Free the list of allocated strings */
+       e_contact_attr_list_free (values);
+
+       return success;
+}
+
+static sqlite3_stmt *
+book_backend_sqlite_prepare_insert (EBookBackendSqlite *ebsql,
+                                   gboolean replace_existing,
+                                   GError **error)
+{
+       sqlite3_stmt *stmt;
+       GString *string;
+       gchar *str;
+       gint i;
+
+       str = sqlite3_mprintf (
+               "INSERT or %s INTO %Q (",
+               replace_existing ? "REPLACE" : "FAIL",
+               ebsql->priv->folderid);
+       string = g_string_new (str);
+       sqlite3_free (str);
+
+       /*
+        * First specify the column names for the insert, since it's possible we
+        * upgraded the DB and cannot be sure the order of the columns are ordered
+        * just how we like them to be.
+        */
+       for (i = 0; i < ebsql->priv->n_summary_fields; i++) {
+               SummaryField *field = &(ebsql->priv->summary_fields[i]);
+
+               /* Multi values go into a separate table/statement */
+               if (field->type != E_TYPE_CONTACT_ATTR_LIST) {
+
+                       /* Only add a ", " before every field except the first,
+                        * this will not break because the first 2 fields (UID & REV)
+                        * are string fields.
+                        */
+                       if (i > 0)
+                               g_string_append (string, ", ");
+
+                       g_string_append (string, field->dbname);
+               }
+
+               if (field->type == G_TYPE_STRING) {
+
+                       if ((field->index & INDEX_FLAG (SORT_KEY)) != 0) {
+                               g_string_append (string, ", ");
+                               g_string_append (string, field->dbname);
+                               g_string_append (string, "_localized");
+                       }
+
+                       if ((field->index & INDEX_FLAG (SUFFIX)) != 0) {
+                               g_string_append (string, ", ");
+                               g_string_append (string, field->dbname);
+                               g_string_append (string, "_reverse");
+                       }
+
+                       if ((field->index & INDEX_FLAG (PHONE)) != 0) {
+                               g_string_append (string, ", ");
+                               g_string_append (string, field->dbname);
+                               g_string_append (string, "_phone");
+                       }
+               }
+       }
+       g_string_append (string, ", vcard)");
+
+       /*
+        * Now specify values for all of the column names we specified.
+        */
+       g_string_append (string, " VALUES (");
+       for (i = 0; i < ebsql->priv->n_summary_fields; i++) {
+               SummaryField *field = &(ebsql->priv->summary_fields[i]);
+
+               if (field->type != E_TYPE_CONTACT_ATTR_LIST) {
+                       /* Only add a ", " before every field except the first,
+                        * this will not break because the first 2 fields (UID & REV)
+                        * are string fields.
+                        */
+                       if (i > 0)
+                               g_string_append (string, ", ");
+               }
+
+               if (field->type == G_TYPE_STRING || field->type == G_TYPE_BOOLEAN) {
+
+                       g_string_append_c (string, ':');
+                       g_string_append (string, field->dbname);
+
+                       if ((field->index & INDEX_FLAG (SORT_KEY)) != 0)
+                               g_string_append_printf (string, ", :%s_localized", field->dbname);
+
+                       if ((field->index & INDEX_FLAG (SUFFIX)) != 0)
+                               g_string_append_printf (string, ", :%s_reverse", field->dbname);
+
+                       if ((field->index & INDEX_FLAG (PHONE)) != 0)
+                               g_string_append_printf (string, ", :%s_phone", field->dbname);
+
+               } else if (field->type != E_TYPE_CONTACT_ATTR_LIST)
+                       g_warn_if_reached ();
+       }
+
+       g_string_append (string, ", :vcard)");
+
+       stmt = book_backend_sqlite_prepare_statement (ebsql, string->str, error);
+       g_string_free (string, TRUE);
+
+       return stmt;
+}
+
+static gboolean
+book_backend_sqlite_run_insert (EBookBackendSqlite *ebsql,
+                               gboolean replace_existing,
+                               EContact *contact,
+                               const gchar *default_region,
+                               GError **error)
+{
+       EBookBackendSqlitePrivate *priv;
+       sqlite3_stmt *stmt;
+       gint i, param_idx;
+       gint ret;
+
+       priv = ebsql->priv;
+
+       if (replace_existing)
+               stmt = ebsql->priv->replace_stmt;
+       else
+               stmt = ebsql->priv->insert_stmt;
+
+       /* This can return an error if a previous call to sqlite3_step() had errors,
+        * so let's just ignore any error in this case
+        */
+       sqlite3_reset (stmt);
+
+       /* Clear all previously set values */
+       ret = sqlite3_clear_bindings (stmt);
+
+       for (i = 0, param_idx = 1; ret == SQLITE_OK && i < ebsql->priv->n_summary_fields; i++) {
+               SummaryField *field = &(ebsql->priv->summary_fields[i]);
+
+               if (field->type == G_TYPE_STRING) {
+                       gchar *val;
+                       gchar *normal;
+                       gchar *str;
+
+                       val = e_contact_get (contact, field->field);
+
+                       /* Special exception, never normalize/localize the UID or REV string */
+                       if (field->field != E_CONTACT_UID &&
+                           field->field != E_CONTACT_REV) {
+                               normal = e_util_utf8_normalize (val);
+                       } else
+                               normal = g_strdup (val);
+
+                       /* Takes ownership of 'normal' */
+                       ret = sqlite3_bind_text (stmt, param_idx++, normal, -1, g_free);
+
+                       if (ret == SQLITE_OK &&
+                           (field->index & INDEX_FLAG (SORT_KEY)) != 0) {
+                               if (val)
+                                       str = e_collator_generate_key (ebsql->priv->collator, val, NULL);
+                               else
+                                       str = g_strdup ("");
+
+                               ret = sqlite3_bind_text (stmt, param_idx++, str, -1, g_free);
+                       }
+
+                       if (ret == SQLITE_OK &&
+                           (field->index & INDEX_FLAG (SUFFIX)) != 0) {
+                               if (normal)
+                                       str = g_utf8_strreverse (normal, -1);
+                               else
+                                       str = NULL;
+
+                               ret = sqlite3_bind_text (stmt, param_idx++, str, -1, g_free);
+                       }
+
+                       if (ret == SQLITE_OK &&
+                           (field->index & INDEX_FLAG (PHONE)) != 0) {
+                               str = convert_phone (normal, default_region);
+                               ret = sqlite3_bind_text (stmt, param_idx++, str, -1, g_free);
+                       }
+
+                       g_free (val);
+               } else if (field->type == G_TYPE_BOOLEAN) {
+                       gboolean val;
+
+                       val = e_contact_get (contact, field->field) ? TRUE : FALSE;
+
+                       ret = sqlite3_bind_int (stmt, param_idx++, val ? 1 : 0);
+               } else if (field->type != E_TYPE_CONTACT_ATTR_LIST)
+                       g_warn_if_reached ();
+       }
+
+       if (ret == SQLITE_OK) {
+               gchar *vcard = NULL;
+
+               if (priv->store_vcard)
+                       vcard = e_vcard_to_string (E_VCARD (contact), EVC_FORMAT_VCARD_30);
+
+               ret = sqlite3_bind_text (stmt, param_idx++, vcard, -1, g_free);
+       }
+
+       /* Run the statement */
+       return book_backend_sqlite_complete_statement (ebsql, stmt, ret, error);
+}
+
+static void
+update_e164_attribute_params (EVCard *vcard,
+                              const gchar *default_region)
+{
+       GList *attr_list;
+
+       for (attr_list = e_vcard_get_attributes (vcard); attr_list; attr_list = attr_list->next) {
+               EVCardAttribute *const attr = attr_list->data;
+               EVCardAttributeParam *param = NULL;
+               gchar *e164 = NULL, *cc, *nn;
+               GList *param_list, *values;
+
+               /* We only attach E164 parameters to TEL attributes. */
+               if (strcmp (e_vcard_attribute_get_name (attr), EVC_TEL) != 0)
+                       continue;
+
+               /* Compute E164 number. */
+               values = e_vcard_attribute_get_values (attr);
+
+               e164 = values && values->data
+                       ? convert_phone (values->data, default_region)
+                       : NULL;
+
+               if (e164 == NULL) {
+                       e_vcard_attribute_remove_param (attr, EVC_X_E164);
+                       continue;
+               }
+
+               /* Find already exisiting parameter, so that we can reuse it. */
+               for (param_list = e_vcard_attribute_get_params (attr); param_list; param_list = 
param_list->next) {
+                       if (strcmp (e_vcard_attribute_param_get_name (param_list->data), EVC_X_E164) == 0) {
+                               param = param_list->data;
+                               break;
+                       }
+               }
+
+               /* Create a new parameter instance if needed. Otherwise clean
+                * the existing parameter's values: This is much cheaper than
+                * checking for modifications. */
+               if (param == NULL) {
+                       param = e_vcard_attribute_param_new (EVC_X_E164);
+                       e_vcard_attribute_add_param (attr, param);
+               } else {
+                       e_vcard_attribute_param_remove_values (param);
+               }
+
+               /* Split the phone number into country calling code and
+                * national number code. */
+               nn = strchr (e164, '|');
+
+               if (nn == NULL) {
+                       g_warn_if_reached ();
+                       continue;
+               }
+
+               *nn++ = '\0';
+               cc = e164;
+
+               /* Assign the parameter values. It seems odd that we revert
+                * the order of NN and CC, but at least EVCard's parser doesn't
+                * permit an empty first param value. Which of course could be
+                * fixed - in order to create a nice potential IOP problem with
+                ** other vCard parsers. */
+               e_vcard_attribute_param_add_values (param, nn, cc, NULL);
+
+               g_free (e164);
+       }
+}
+
+static gboolean
+book_backend_sqlite_insert_contact (EBookBackendSqlite *ebsql,
+                                   EContact *contact,
+                                   gboolean replace_existing,
+                                   const gchar *default_region,
+                                   GError **error)
+{
+       EBookBackendSqlitePrivate *priv;
+       gboolean success;
+
+       priv = ebsql->priv;
+
+       /* Update E.164 parameters in vcard if needed */
+       if (priv->store_vcard)
+               update_e164_attribute_params (E_VCARD (contact), default_region);
+
+       success = book_backend_sqlite_run_insert (ebsql,
+                                                 replace_existing,
+                                                 contact,
+                                                 default_region,
+                                                 error);
+
+       /* Update attribute list table */
+       if (success && priv->have_attr_list) {
+               gchar *uid = e_contact_get (contact, E_CONTACT_UID);
+               gint i;
+
+               success = book_backend_sqlite_run_multi_delete (ebsql,
+                                                               0 /* XXX unused for now */,
+                                                               uid,
+                                                               error);
+
+               for (i = 0; success && i < priv->n_summary_fields; i++) {
+                       SummaryField *field = &(ebsql->priv->summary_fields[i]);
+
+                       if (field->type != E_TYPE_CONTACT_ATTR_LIST)
+                               continue;
+
+                       success = book_backend_sqlite_run_multi_insert (ebsql,
+                                                                       field,
+                                                                       uid,
+                                                                       contact,
+                                                                       default_region,
+                                                                       error);
+               }
+
+               g_free (uid);
+       }
+
+       return success;
+}
+
+/**********************************************************
+ * Querying preflighting, checking if query is supported  *
+ **********************************************************/
+enum {
+       CHECK_IS_SUMMARY   = (1 << 0),
+       CHECK_IS_LIST_ATTR = (1 << 1),
+       CHECK_UNSUPPORTED  = (1 << 2),
+       CHECK_INVALID      = (1 << 3)
+};
+
+static ESExpResult *
+func_check_subset (ESExp *f,
+                   gint argc,
+                   struct _ESExpTerm **argv,
+                   gpointer data)
+{
+       ESExpResult *r, *r1;
+       gboolean one_non_summary_query = FALSE;
+       gint result = 0;
+       gint i;
+
+       for (i = 0; i < argc; i++) {
+               r1 = e_sexp_term_eval (f, argv[i]);
+
+               if (r1->type != ESEXP_RES_INT) {
+                       e_sexp_result_free (f, r1);
+                       continue;
+               }
+
+               result |= r1->value.number;
+
+               if ((r1->value.number & CHECK_IS_SUMMARY) == 0)
+                       one_non_summary_query = TRUE;
+
+               e_sexp_result_free (f, r1);
+       }
+
+       /* If at least one subset is not a summary query,
+        * then the whole query is not a summary query and
+        * thus cannot be done with an SQL statement
+        */
+       if (one_non_summary_query)
+               result &= ~CHECK_IS_SUMMARY;
+
+       r = e_sexp_result_new (f, ESEXP_RES_INT);
+       r->value.number = result;
+
+       return r;
+}
+
+static gint
+func_check_field_test (EBookBackendSqlite *ebsql,
+                        const gchar *query_name,
+                        const gchar *query_value)
+{
+       gint i;
+       gint ret_val = 0;
+
+       if (ebsql) {
+               for (i = 0; i < ebsql->priv->n_summary_fields; i++) {
+                       if (!g_ascii_strcasecmp (e_contact_field_name (ebsql->priv->summary_fields[i].field), 
query_name)) {
+                               ret_val |= CHECK_IS_SUMMARY;
+
+                               if (ebsql->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,
+            struct _ESExpResult **argv,
+            gpointer data)
+{
+       EBookBackendSqlite *ebsql = data;
+       ESExpResult *r;
+       gint ret_val = 0;
+
+       if (argc == 2
+           && argv[0]->type == ESEXP_RES_STRING
+           && argv[1]->type == ESEXP_RES_STRING) {
+               const gchar *query_name = argv[0]->value.string;
+               const gchar *query_value = argv[1]->value.string;
+
+               /* Special case, when testing the special symbolic 'any field' we can
+                * consider it a summary query (it's similar to a 'no query'). */
+               if (g_strcmp0 (query_name, "x-evolution-any-field") == 0 &&
+                   g_strcmp0 (query_value, "") == 0) {
+                       ret_val |= CHECK_IS_SUMMARY;
+                       goto check_finish;
+               }
+
+               ret_val |= func_check_field_test (ebsql, 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 (ebsql, query_name, query_value);
+       }
+
+ 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)
+{
+       ESExpResult *const r = func_check (f, argc, argv, data);
+       const gchar *const query_value = argv[1]->value.string;
+       EPhoneNumber *number;
+
+       /* Here we need to catch unsupported queries and invalid queries
+        * so we perform validity checks even if func_check() reports this
+        * as not a part of the summary.
+        */
+       if (!e_phone_number_is_supported ()) {
+               r->value.number |= CHECK_UNSUPPORTED;
+               return r;
+       }
+
+       number = e_phone_number_from_string (query_value, NULL, NULL);
+
+       if (number == NULL) {
+               /* Could not construct a phone number from the query input,
+                * an invalid query error will be propagated to the client.
+                */
+               r->value.number |= CHECK_INVALID;
+       } else {
+               e_phone_number_free (number);
+       }
+
+       return r;
+}
+
+static ESExpResult *
+func_check_regex_raw (struct _ESExp *f,
+                      gint argc,
+                      struct _ESExpResult **argv,
+                      gpointer data)
+{
+       /* Raw REGEX queries are not in the summary, we only keep
+        * normalized data in the summary
+        */
+       ESExpResult *r;
+
+       r = e_sexp_result_new (f, ESEXP_RES_INT);
+       r->value.number = 0;
+
+       return r;
+}
+
+/* 'builtin' functions */
+static const struct {
+       const gchar *name;
+       ESExpFunc *func;
+       gint type;              /* set to 1 if a function can perform shortcut evaluation, or
+                                  doesn't execute everything, 0 otherwise */
+} check_symbols[] = {
+       { "and", (ESExpFunc *) func_check_subset, 1},
+       { "or", (ESExpFunc *) func_check_subset, 1},
+
+       { "contains", func_check, 0 },
+       { "is", func_check, 0 },
+       { "beginswith", func_check, 0 },
+       { "endswith", func_check, 0 },
+       { "exists", func_check, 0 },
+       { "eqphone", func_check_phone, 0 },
+       { "eqphone_national", func_check_phone, 0 },
+       { "eqphone_short", func_check_phone, 0 },
+       { "regex_normal", func_check, 0 },
+       { "regex_raw", func_check_regex_raw, 0 },
+};
+
+static gboolean
+e_book_backend_sqlite_check_summary_query_locked (EBookBackendSqlite *ebsql,
+                                                    const gchar *query,
+                                                    gboolean *with_list_attrs,
+                                                    gboolean *unsupported_query,
+                                                    gboolean *invalid_query)
+{
+       ESExp *sexp;
+       ESExpResult *r;
+       gboolean retval = FALSE;
+       gint i;
+       gint esexp_error;
+
+       g_return_val_if_fail (query != NULL, FALSE);
+       g_return_val_if_fail (*query != '\0', FALSE);
+
+       sexp = e_sexp_new ();
+
+       for (i = 0; i < G_N_ELEMENTS (check_symbols); i++) {
+               if (check_symbols[i].type == 1) {
+                       e_sexp_add_ifunction (
+                               sexp, 0, check_symbols[i].name,
+                               (ESExpIFunc *) check_symbols[i].func, ebsql);
+               } else {
+                       e_sexp_add_function (
+                               sexp, 0, check_symbols[i].name,
+                               check_symbols[i].func, ebsql);
+               }
+       }
+
+       e_sexp_input_text (sexp, query, strlen (query));
+       esexp_error = e_sexp_parse (sexp);
+
+       if (esexp_error == -1) {
+               if (invalid_query)
+                       *invalid_query = TRUE;
+
+               return FALSE;
+       }
+
+       r = e_sexp_eval (sexp);
+       if (r && r->type == ESEXP_RES_INT) {
+               retval = (r->value.number & CHECK_IS_SUMMARY) != 0;
+
+               if (with_list_attrs)
+                       *with_list_attrs = (r->value.number & CHECK_IS_LIST_ATTR) != 0;
+
+               if (unsupported_query)
+                       *unsupported_query = (r->value.number & CHECK_UNSUPPORTED) != 0;
+
+               if (invalid_query)
+                       *invalid_query = (r->value.number & CHECK_INVALID) != 0;
+       }
+
+       e_sexp_result_free (sexp, r);
+       e_sexp_unref (sexp);
+
+       return retval;
+}
+
+/**********************************************************
+ *                   Querying Contacts                    *
+ **********************************************************/
+typedef enum {
+       MATCH_CONTAINS,
+       MATCH_IS,
+       MATCH_BEGINS_WITH,
+       MATCH_ENDS_WITH,
+       MATCH_PHONE_NUMBER,
+       MATCH_NATIONAL_PHONE_NUMBER,
+       MATCH_SHORT_PHONE_NUMBER,
+       MATCH_REGEX
+} MatchType;
+
+typedef enum {
+       CONVERT_NOTHING   =  0,
+       CONVERT_NORMALIZE = (1 << 0),
+       CONVERT_REVERSE   = (1 << 1),
+       CONVERT_PHONE     = (1 << 2)
+} ConvertFlags;
+
+/* free return value with g_free */
+static gchar *
+summary_select_stmt (GHashTable *fields_of_interest,
+                     gboolean distinct)
+{
+       GString *string;
+
+       if (distinct)
+               string = g_string_new ("SELECT DISTINCT summary.uid");
+       else
+               string = g_string_new ("SELECT summary.uid");
+
+       /* Add the E_CONTACT_REV field if they are both requested */
+       if (g_hash_table_size (fields_of_interest) == 2)
+               g_string_append (string, ", Rev");
+
+       return g_string_free (string, FALSE);
+}
+
+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 (EBookBackendSqlite *ebsql,
+                      const gchar *value,
+                      const gchar *region,
+                      ConvertFlags flags,
+                      MatchType match)
+{
+       GString *str;
+       size_t len;
+       gchar c;
+       gboolean escape_modifier_needed = FALSE;
+       const gchar *escape_modifier = " ESCAPE '^'";
+       gchar *computed = NULL;
+       gchar *normal;
+       const gchar *ptr;
+
+       g_return_val_if_fail (value != NULL, NULL);
+
+       if ((flags & CONVERT_NORMALIZE) && match != MATCH_REGEX)
+               normal = e_util_utf8_normalize (value);
+       else
+               normal = g_strdup (value);
+
+       /* Just assume each character must be escaped. The result of this function
+        * is discarded shortly after calling this function. Therefore it's
+        * acceptable to possibly allocate twice the memory needed.
+        */
+       len = strlen (normal);
+       str = g_string_sized_new (2 * len + 4 + strlen (escape_modifier) - 1);
+       g_string_append_c (str, '\'');
+
+       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:
+       case MATCH_REGEX:
+               break;
+       }
+
+       if (flags & CONVERT_REVERSE) {
+               computed = g_utf8_strreverse (normal, -1);
+               ptr = computed;
+       } else if (flags & CONVERT_PHONE) {
+               computed = convert_phone (normal, region);
+               ptr = computed;
+       } else {
+               ptr = normal;
+       }
+
+       while ((c = *ptr++)) {
+               if (c == '\'') {
+                       g_string_append_c (str, '\'');
+               } else if ((c == '%' || c == '^') && match != MATCH_REGEX) {
+                       g_string_append_c (str, '^');
+                       escape_modifier_needed = TRUE;
+               }
+
+               g_string_append_c (str, c);
+       }
+
+       switch (match) {
+       case MATCH_CONTAINS:
+       case MATCH_BEGINS_WITH:
+               g_string_append_c (str, '%');
+               break;
+
+       case MATCH_ENDS_WITH:
+       case MATCH_IS:
+       case MATCH_PHONE_NUMBER:
+       case MATCH_NATIONAL_PHONE_NUMBER:
+       case MATCH_SHORT_PHONE_NUMBER:
+       case MATCH_REGEX:
+               break;
+       }
+
+       g_string_append_c (str, '\'');
+
+       if (escape_modifier_needed)
+               g_string_append (str, escape_modifier);
+
+       g_free (computed);
+       g_free (normal);
+
+       return g_string_free (str, FALSE);
+}
+
+static gchar *
+field_name_and_query_term (EBookBackendSqlite *ebsql,
+                           const gchar *field_name_input,
+                           const gchar *query_term_input,
+                           const gchar *region,
+                           MatchType match,
+                           gboolean *is_list_attr,
+                           gchar **query_term,
+                           gchar **extra_term)
+{
+       SummaryField *field;
+       gchar *field_name = NULL;
+       gchar *value = NULL;
+       gchar *extra = NULL;
+       gboolean list_attr = FALSE;
+
+       field = summary_field_get_by_name (ebsql, field_name_input);
+
+       if (field == NULL) {
+               g_critical ("Only summary field matches should be converted to sql queries");
+               field_name = g_strconcat (ebsql->priv->folderid, ".", field_name_input, NULL);
+               value = convert_string_value (
+                       ebsql, query_term_input, region,
+                       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 && (field->index & INDEX_FLAG (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) &&
+                        (field->index & INDEX_FLAG (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 &&
+                        (field->index & INDEX_FLAG (SUFFIX)) != 0 &&
+                        (field->index & INDEX_FLAG (PREFIX)) == 0)
+                       suffix_search = TRUE;
+
+               if (suffix_search) {
+                       /* Special case for suffix matching:
+                        *  o Reverse the string
+                        *  o Check the reversed column instead
+                        *  o Make it a prefix search
+                        */
+                       if (field->type == E_TYPE_CONTACT_ATTR_LIST) {
+                               field_name = g_strdup ("multi.value_reverse");
+                               list_attr = TRUE;
+                       } else
+                               field_name = g_strconcat (
+                                       "summary.",
+                                       field->dbname,
+                                       "_reverse", NULL);
+
+                       if (field->field == E_CONTACT_UID ||
+                           field->field == E_CONTACT_REV)
+                               value = convert_string_value (
+                                       ebsql, query_term_input, region, CONVERT_REVERSE,
+                                       (match == MATCH_ENDS_WITH) ? MATCH_BEGINS_WITH : MATCH_IS);
+                       else
+                               value = convert_string_value (
+                                       ebsql, query_term_input, region,
+                                       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
+                        */
+                       const gint country_code = e_phone_number_get_country_code_for_region (region, NULL);
+
+                       if (field->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",
+                                       field->dbname);
+                       }
+
+                       if (match == MATCH_PHONE_NUMBER) {
+                               value = convert_string_value (
+                                       ebsql, query_term_input, region,
+                                       CONVERT_NORMALIZE | CONVERT_PHONE, match);
+
+                               extra = sqlite3_mprintf (" COLLATE ixphone_%d", country_code);
+                       } else {
+                               if (match == MATCH_NATIONAL_PHONE_NUMBER) {
+                                       value = convert_string_value (
+                                               ebsql, query_term_input, region,
+                                               CONVERT_PHONE, MATCH_NATIONAL_PHONE_NUMBER);
+
+                                       extra = sqlite3_mprintf (" COLLATE ixphone_national");
+                               } else {
+                                       gchar *const digits = extract_digits (query_term_input);
+                                       value = convert_string_value (
+                                               ebsql, digits, region,
+                                               CONVERT_NOTHING, MATCH_ENDS_WITH);
+                                       g_free (digits);
+
+                                       extra = sqlite3_mprintf (
+                                               " AND (%q LIKE '|%%' OR %q LIKE '+%d|%%')",
+                                               field_name, field_name, country_code);
+                               }
+
+                       }
+               } else {
+                       if (field->type == E_TYPE_CONTACT_ATTR_LIST) {
+                               field_name = g_strdup ("multi.value");
+                               list_attr = TRUE;
+                       } else
+                               field_name = g_strconcat (
+                                       "summary.",
+                                       field->dbname, NULL);
+
+                       if (field->field == E_CONTACT_UID ||
+                           field->field == E_CONTACT_REV) {
+                               value = convert_string_value (
+                                       ebsql, query_term_input, region,
+                                       CONVERT_NOTHING, match);
+                       } else {
+                               value = convert_string_value (
+                                       ebsql, query_term_input, region,
+                                       CONVERT_NORMALIZE, match);
+                       }
+               }
+       }
+
+       if (is_list_attr)
+               *is_list_attr = list_attr;
+
+       *query_term = value;
+
+       if (extra_term)
+               *extra_term = extra;
+
+       return field_name;
+}
+
+static const gchar *
+field_oper (MatchType match)
+{
+       switch (match) {
+       case MATCH_IS:
+       case MATCH_PHONE_NUMBER:
+       case MATCH_NATIONAL_PHONE_NUMBER:
+               return "=";
+
+       case MATCH_REGEX:
+               return "REGEXP";
+
+       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,
+                   MatchType match)
+{
+       EBookBackendSqlite *ebsql = (EBookBackendSqlite *)data;
+       ESExpResult *r;
+       gchar *str = NULL;
+
+       /* are we inside a match-all? */
+       if (argc > 1 && argv[0]->type == ESEXP_RES_STRING) {
+               const gchar *field;
+
+               /* only a subset of headers are supported .. */
+               field = argv[0]->value.string;
+
+               if (argv[1]->type == ESEXP_RES_STRING && argv[1]->value.string[0] != 0) {
+                       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);
+
+                               field_name = field_name_and_query_term (
+                                       ebsql, "full_name",
+                                       argv[1]->value.string, NULL,
+                                       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);
+                               g_free (query_term);
+
+                               if (summary_field_get (ebsql, E_CONTACT_FAMILY_NAME)) {
+                                       field_name = field_name_and_query_term (
+                                               ebsql, "family_name",
+                                               argv[1]->value.string, NULL,
+                                               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);
+                                       g_free (field_name);
+                                       g_free (query_term);
+                               }
+
+                               if (summary_field_get (ebsql, E_CONTACT_GIVEN_NAME)) {
+                                       field_name = field_name_and_query_term (
+                                               ebsql, "given_name",
+                                               argv[1]->value.string, NULL,
+                                               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);
+                                       g_free (field_name);
+                                       g_free (query_term);
+                               }
+
+                               if (summary_field_get (ebsql, E_CONTACT_NICKNAME)) {
+                                       field_name = field_name_and_query_term (
+                                               ebsql, "nickname",
+                                               argv[1]->value.string, NULL,
+                                               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);
+                                       g_free (field_name);
+                                       g_free (query_term);
+                               }
+
+                               str = names->str;
+                               g_string_free (names, FALSE);
+
+                       } else {
+                               const gchar *const region =
+                                       argc > 2 && argv[2]->type == ESEXP_RES_STRING ?
+                                       argv[2]->value.string : 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 (
+                                       ebsql, field,
+                                       argv[1]->value.string, region,
+                                       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 ("multi.field = %Q", field);
+                                       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%s))",
+                                               field_name, field_name, oper, query_term,
+                                               extra_term ? extra_term : "");
+
+                               g_free (field_name);
+                               g_free (query_term);
+
+                               sqlite3_free (extra_term);
+                       }
+               }
+       }
+
+       r = e_sexp_result_new (f, ESEXP_RES_STRING);
+       r->value.string = str;
+
+       return r;
+}
+
+static ESExpResult *
+func_and (ESExp *f,
+          gint argc,
+          struct _ESExpTerm **argv,
+          gpointer data)
+{
+       ESExpResult *r, *r1;
+       GString *string;
+       gint i;
+
+       string = g_string_new ("( ");
+       for (i = 0; i < argc; i++) {
+               r1 = e_sexp_term_eval (f, argv[i]);
+
+               if (r1->type != ESEXP_RES_STRING) {
+                       e_sexp_result_free (f, r1);
+                       continue;
+               }
+               if (r1->value.string && *r1->value.string)
+                       g_string_append_printf (string, "%s%s", r1->value.string, ((argc > 1) && (i != argc - 
1)) ? " AND ":"");
+               e_sexp_result_free (f, r1);
+       }
+       g_string_append (string, " )");
+       r = e_sexp_result_new (f, ESEXP_RES_STRING);
+
+       if (strlen (string->str) == 4) {
+               r->value.string = g_strdup ("");
+               g_string_free (string, TRUE);
+       } else {
+               r->value.string = g_string_free (string, FALSE);
+       }
+
+       return r;
+}
+
+static ESExpResult *
+func_or (ESExp *f,
+         gint argc,
+         struct _ESExpTerm **argv,
+         gpointer data)
+{
+       ESExpResult *r, *r1;
+       GString *string;
+       gint i;
+
+       string = g_string_new ("( ");
+       for (i = 0; i < argc; i++) {
+               r1 = e_sexp_term_eval (f, argv[i]);
+
+               if (r1->type != ESEXP_RES_STRING) {
+                       e_sexp_result_free (f, r1);
+                       continue;
+               }
+               if (r1->value.string && *r1->value.string)
+                       g_string_append_printf (string, "%s%s", r1->value.string, ((argc > 1) && (i != argc - 
1)) ? " OR ":"");
+               e_sexp_result_free (f, r1);
+       }
+       g_string_append (string, " )");
+
+       r = e_sexp_result_new (f, ESEXP_RES_STRING);
+       if (strlen (string->str) == 4) {
+               r->value.string = g_strdup ("");
+               g_string_free (string, TRUE);
+       } else {
+               r->value.string = g_string_free (string, FALSE);
+       }
+
+       return r;
+}
+
+static ESExpResult *
+func_contains (struct _ESExp *f,
+               gint argc,
+               struct _ESExpResult **argv,
+               gpointer data)
+{
+       return convert_match_exp (f, argc, argv, data, MATCH_CONTAINS);
+}
+
+static ESExpResult *
+func_is (struct _ESExp *f,
+         gint argc,
+         struct _ESExpResult **argv,
+         gpointer data)
+{
+       return convert_match_exp (f, argc, argv, data, MATCH_IS);
+}
+
+static ESExpResult *
+func_beginswith (struct _ESExp *f,
+                 gint argc,
+                 struct _ESExpResult **argv,
+                 gpointer data)
+{
+       return convert_match_exp (f, argc, argv, data, MATCH_BEGINS_WITH);
+}
+
+static ESExpResult *
+func_endswith (struct _ESExp *f,
+               gint argc,
+               struct _ESExpResult **argv,
+               gpointer data)
+{
+       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);
+}
+
+static ESExpResult *
+func_regex (struct _ESExp *f,
+            gint argc,
+            struct _ESExpResult **argv,
+            gpointer data)
+{
+       return convert_match_exp (f, argc, argv, data, MATCH_REGEX);
+}
+
+/* 'builtin' functions */
+static struct {
+       const gchar *name;
+       ESExpFunc *func;
+       guint immediate :1;
+} symbols[] = {
+       { "and", (ESExpFunc *) func_and, 1},
+       { "or", (ESExpFunc *) func_or, 1},
+
+       { "contains", func_contains, 0 },
+       { "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 },
+       { "regex_normal", func_regex, 0 }
+};
+
+static gchar *
+sexp_to_sql_query (EBookBackendSqlite *ebsql,
+                   const gchar *query)
+{
+       ESExp *sexp;
+       ESExpResult *r;
+       gint i;
+       gchar *res;
+
+       sexp = e_sexp_new ();
+
+       for (i = 0; i < G_N_ELEMENTS (symbols); i++) {
+               if (symbols[i].immediate)
+                       e_sexp_add_ifunction (
+                               sexp, 0, symbols[i].name,
+                               (ESExpIFunc *) symbols[i].func, ebsql);
+               else
+                       e_sexp_add_function (
+                               sexp, 0, symbols[i].name,
+                               symbols[i].func, ebsql);
+       }
+
+       e_sexp_input_text (sexp, query, strlen (query));
+       e_sexp_parse (sexp);
+
+       r = e_sexp_eval (sexp);
+       if (!r)
+               return NULL;
+       if (r->type == ESEXP_RES_STRING) {
+               if (r->value.string && *r->value.string)
+                       res = g_strdup (r->value.string);
+               else
+                       res = NULL;
+       } else {
+               g_warn_if_reached ();
+               res = NULL;
+       }
+
+       e_sexp_result_free (sexp, r);
+       e_sexp_unref (sexp);
+
+       return res;
+}
+
+static gboolean
+uid_rev_fields (GHashTable *fields_of_interest)
+{
+       GHashTableIter iter;
+       gpointer key, value;
+
+       if (!fields_of_interest || g_hash_table_size (fields_of_interest) > 2)
+               return FALSE;
+
+       g_hash_table_iter_init (&iter, fields_of_interest);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               const gchar *field_name = key;
+               EContactField field = e_contact_field_id (field_name);
+
+               if (field != E_CONTACT_UID &&
+                   field != E_CONTACT_REV)
+                       return FALSE;
+       }
+
+       return TRUE;
+}
+
+static GSList *
+book_backend_sqlite_search_query (EBookBackendSqlite *ebsql,
+                                 const gchar *sql,
+                                 GHashTable *fields_of_interest,
+                                 gboolean *with_all_required_fields,
+                                 gboolean query_with_list_attrs,
+                                 GError **error)
+{
+       GSList *vcard_data = NULL;
+       gboolean local_with_all_required_fields = FALSE;
+       gboolean success = TRUE;
+
+       /* Try constructing contacts from only UID/REV first if that's requested */
+       if (uid_rev_fields (fields_of_interest)) {
+               gchar *select_portion;
+
+               select_portion = summary_select_stmt (
+                       fields_of_interest, query_with_list_attrs);
+
+               if (sql && sql[0]) {
+
+                       if (query_with_list_attrs) {
+                               gchar *list_table = g_strconcat (ebsql->priv->folderid, "_lists", NULL);
+
+                               success = book_backend_sqlite_exec_printf (
+                                       ebsql,
+                                       "%s FROM %Q AS summary "
+                                       "LEFT OUTER JOIN %Q AS multi ON summary.uid = multi.uid WHERE %s",
+                                       collect_lean_results_cb, &vcard_data, error,
+                                       select_portion, ebsql->priv->folderid, list_table, sql);
+
+                               g_free (list_table);
+                       } else {
+
+                               success = book_backend_sqlite_exec_printf (
+                                       ebsql, "%s FROM %Q AS summary WHERE %s",
+                                       collect_lean_results_cb, &vcard_data, error,
+                                       select_portion, ebsql->priv->folderid, sql);
+                       }
+
+               } else {
+                       success = book_backend_sqlite_exec_printf (
+                               ebsql, "%s FROM %Q AS summary",
+                               collect_lean_results_cb, &vcard_data, error,
+                               select_portion, ebsql->priv->folderid);
+               }
+
+               local_with_all_required_fields = TRUE;
+               g_free (select_portion);
+
+       } else if (ebsql->priv->store_vcard) {
+
+               if (sql && sql[0]) {
+
+                       if (query_with_list_attrs) {
+                               gchar *list_table = g_strconcat (ebsql->priv->folderid, "_lists", NULL);
+
+                               success = book_backend_sqlite_exec_printf (
+                                       ebsql,
+                                       "SELECT DISTINCT summary.uid, vcard FROM %Q AS summary "
+                                       "LEFT OUTER JOIN %Q AS multi ON summary.uid = multi.uid WHERE %s",
+                                       collect_full_results_cb, &vcard_data, error,
+                                       ebsql->priv->folderid, list_table, sql);
+
+                               g_free (list_table);
+
+                       } else {
+
+                               success = book_backend_sqlite_exec_printf (
+                                       ebsql,
+                                       "SELECT uid, vcard FROM %Q as summary WHERE %s",
+                                       collect_full_results_cb, &vcard_data, error,
+                                       ebsql->priv->folderid, sql);
+                       }
+
+               } else {
+
+                       success = book_backend_sqlite_exec_printf (
+                               ebsql, "SELECT uid, vcard FROM %Q",
+                               collect_full_results_cb, &vcard_data, error, ebsql->priv->folderid);
+               }
+
+               local_with_all_required_fields = TRUE;
+       } else {
+               g_set_error (
+                       error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_OTHER,
+                       _("Full search_contacts are not stored in cache. vcards cannot be returned."));
+       }
+
+       if (!success) {
+               g_warn_if_fail (vcard_data == NULL);
+               return NULL;
+       }
+
+       if (with_all_required_fields)
+               *with_all_required_fields = local_with_all_required_fields;
+
+       return g_slist_reverse (vcard_data);
+}
+
+static GSList *
+book_backend_sqlite_search_full (EBookBackendSqlite *ebsql,
+                                const gchar *sexp,
+                                gboolean return_uids,
+                                GError **error)
+{
+       GSList *r_list = NULL, *all = NULL, *l;
+       EBookBackendSExp *bsexp = NULL;
+       gboolean success;
+
+       success = book_backend_sqlite_exec_printf (
+               ebsql, "SELECT uid, vcard FROM %Q",
+               collect_full_results_cb, &all, error, ebsql->priv->folderid);
+
+       if (!success) {
+               g_warn_if_fail (all == NULL);
+               return NULL;
+       }
+
+       bsexp = e_book_backend_sexp_new (sexp);
+
+       for (l = all; l != NULL; l = g_slist_next (l)) {
+               EbSqlSearchData *s_data = (EbSqlSearchData *) l->data;
+
+               if (e_book_backend_sexp_match_vcard (bsexp, s_data->vcard)) {
+                       if (!return_uids)
+                               r_list = g_slist_prepend (r_list, s_data);
+                       else {
+                               r_list = g_slist_prepend (r_list, g_strdup (s_data->uid));
+                               e_book_backend_sqlite_search_data_free (s_data);
+                       }
+               } else
+                       e_book_backend_sqlite_search_data_free (s_data);
+       }
+
+       g_object_unref (bsexp);
+
+       g_slist_free (all);
+
+       return r_list;
+}
+
+/**********************************************************
+ *                     GObjectClass                       *
+ **********************************************************/
+static void
+e_book_backend_sqlite_dispose (GObject *object)
+{
+       EBookBackendSqlite *ebsql = E_BOOK_BACKEND_SQLITE (object);
+
+       book_backend_sqlite_unregister_from_hash (ebsql);
+
+       /* Chain up to parent's dispose() method. */
+       G_OBJECT_CLASS (e_book_backend_sqlite_parent_class)->dispose (object);
+}
+
+static void
+e_book_backend_sqlite_finalize (GObject *object)
+{
+       EBookBackendSqlite *ebsql = E_BOOK_BACKEND_SQLITE (object);
+       EBookBackendSqlitePrivate *priv = ebsql->priv;
+
+       sqlite3_close (priv->db);
+
+       g_free (priv->summary_fields);
+       g_free (priv->locale);
+       g_free (priv->path);
+
+       if (priv->collator)
+               e_collator_unref (priv->collator);
+
+       g_mutex_clear (&priv->lock);
+       g_mutex_clear (&priv->updates_lock);
+
+       sqlite3_finalize (priv->insert_stmt);
+       sqlite3_finalize (priv->replace_stmt);
+
+       if (priv->multi_deletes)
+               g_hash_table_destroy (priv->multi_deletes);
+
+       if (priv->multi_inserts)
+               g_hash_table_destroy (priv->multi_inserts);
+
+       /* Chain up to parent's finalize() method. */
+       G_OBJECT_CLASS (e_book_backend_sqlite_parent_class)->finalize (object);
+}
+
+static void
+e_book_backend_sqlite_class_init (EBookBackendSqliteClass *class)
+{
+       GObjectClass *object_class;
+
+       g_type_class_add_private (class, sizeof (EBookBackendSqlitePrivate));
+
+       object_class = G_OBJECT_CLASS (class);
+       object_class->dispose = e_book_backend_sqlite_dispose;
+       object_class->finalize = e_book_backend_sqlite_finalize;
+}
+
+static void
+e_book_backend_sqlite_init (EBookBackendSqlite *ebsql)
+{
+       ebsql->priv = 
+               G_TYPE_INSTANCE_GET_PRIVATE (ebsql,
+                                            E_TYPE_BOOK_BACKEND_SQLITE,
+                                            EBookBackendSqlitePrivate);
+
+       ebsql->priv->store_vcard = TRUE;
+
+       ebsql->priv->in_transaction = 0;
+       g_mutex_init (&ebsql->priv->lock);
+       g_mutex_init (&ebsql->priv->updates_lock);
+}
+
+static EBookBackendSqlite *
+e_book_backend_sqlite_new_internal (const gchar *path,
+                                   const gchar *folderid,
+                                   gboolean store_vcard,
+                                   SummaryField *fields,
+                                   gint n_fields,
+                                   gboolean have_attr_list,
+                                   gint attr_list_indexes,
+                                   GError **error)
+{
+       EBookBackendSqlite *ebsql;
+       gchar *dirname;
+       gint previous_schema = 0;
+       gboolean already_exists = FALSE;
+       GSList *introspected_columns = NULL;
+
+       g_return_val_if_fail (path != NULL, NULL);
+
+       if (folderid == NULL)
+               folderid = "folder_id";
+
+       g_mutex_lock (&dbcon_lock);
+
+       ebsql = book_backend_sqlite_ref_from_hash (path);
+       if (ebsql)
+               goto exit;
+
+       ebsql = g_object_new (E_TYPE_BOOK_BACKEND_SQLITE, NULL);
+       ebsql->priv->path = g_strdup (path);
+       ebsql->priv->folderid = g_strdup (folderid);
+       ebsql->priv->summary_fields = fields;
+       ebsql->priv->n_summary_fields = n_fields;
+       ebsql->priv->have_attr_list = have_attr_list;
+       ebsql->priv->attr_list_indexes = attr_list_indexes;
+       ebsql->priv->store_vcard = store_vcard;
+
+       /* Ensure existance of the directories leading up to 'path' */
+       dirname = g_path_get_dirname (path);
+       if (g_mkdir_with_parents (dirname, 0777) < 0) {
+               g_mutex_unlock (&dbcon_lock);
+               g_object_unref (ebsql);
+               g_free (dirname);
+               g_set_error (
+                       error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_OTHER,
+                       "Can not make parent directory: errno %d", errno);
+               return NULL;
+       }
+       g_free (dirname);
+
+       /* Open and configure DB handle */
+       if (!book_backend_sqlite_init_sqlite (ebsql, path, error)) {
+               g_mutex_unlock (&dbcon_lock);
+               g_object_unref (ebsql);
+               return NULL;
+       }
+
+       /* Initialize main folders table */
+       if (!book_backend_sqlite_init_folders (ebsql, &previous_schema, error)) {
+               g_mutex_unlock (&dbcon_lock);
+               g_object_unref (ebsql);
+               return NULL;
+       }
+
+       /* Initialize key/value table */
+       if (!book_backend_sqlite_init_keys (ebsql, error)) {
+               g_mutex_unlock (&dbcon_lock);
+               g_object_unref (ebsql);
+               return NULL;
+       }
+
+       book_backend_sqlite_register_to_hash (ebsql, path);
+
+ exit:
+       /* While the global dbcon_lock is held, hold the ebsql specific lock and
+        * prolong the locking on that instance until the folders are all set up
+        */
+       LOCK_MUTEX (&ebsql->priv->lock);
+       g_mutex_unlock (&dbcon_lock);
+
+       if (!book_backend_sqlite_add_folder (ebsql, &already_exists, error)) {
+               UNLOCK_MUTEX (&ebsql->priv->lock);
+               g_object_unref (ebsql);
+               return NULL;
+       }
+
+       if (already_exists &&
+           !book_backend_sqlite_introspect_summary (ebsql,
+                                                    previous_schema,
+                                                    &introspected_columns,
+                                                    error)) {
+               UNLOCK_MUTEX (&ebsql->priv->lock);
+               g_object_unref (ebsql);
+               return NULL;
+       }
+
+       if (!book_backend_sqlite_init_contacts (ebsql,
+                                               introspected_columns,
+                                               error)) {
+               UNLOCK_MUTEX (&ebsql->priv->lock);
+               g_slist_free_full (introspected_columns, (GDestroyNotify) g_free);
+               g_object_unref (ebsql);
+               return NULL;
+       }
+       g_slist_free_full (introspected_columns, (GDestroyNotify) g_free);
+
+       if (!book_backend_sqlite_init_aux_tables (ebsql, error)) {
+               UNLOCK_MUTEX (&ebsql->priv->lock);
+               g_object_unref (ebsql);
+               return NULL;
+       }
+
+       /* At this point we have resolved our schema, let's build our
+        * precompiled statements, we might use them to re-insert contacts
+        * in the next step
+        */
+       if (!book_backend_sqlite_init_statements (ebsql, error)) {
+               UNLOCK_MUTEX (&ebsql->priv->lock);
+               g_object_unref (ebsql);
+               return NULL;
+       }
+
+       if (!book_backend_sqlite_init_locale (ebsql,
+                                             previous_schema,
+                                             already_exists,
+                                             error)) {
+               UNLOCK_MUTEX (&ebsql->priv->lock);
+               g_object_unref (ebsql);
+               return NULL;
+       }
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       return ebsql;
+}
+
+static EBookBackendSqlite *
+e_book_backend_sqlite_new_with_folderid (const gchar *path,
+                                        const gchar *folderid,
+                                        gboolean store_vcard,
+                                        GError **error)
+{
+       EBookBackendSqlite *ebsql;
+       GArray *summary_fields;
+       gboolean have_attr_list = FALSE;
+       gint attr_list_indexes = 0;
+       gint i;
+
+       /* Create the default summary structs */
+       summary_fields = g_array_new (FALSE, FALSE, sizeof (SummaryField));
+       for (i = 0; i < G_N_ELEMENTS (default_summary_fields); i++)
+               summary_field_append (summary_fields, default_summary_fields[i], &have_attr_list, NULL);
+
+       /* Add the default index flags */
+       summary_fields_add_indexes (
+               summary_fields,
+               default_indexed_fields,
+               default_index_types,
+               G_N_ELEMENTS (default_indexed_fields),
+               &attr_list_indexes);
+
+       ebsql = e_book_backend_sqlite_new_internal (
+               path, folderid,
+               store_vcard,
+               (SummaryField *) summary_fields->data,
+               summary_fields->len,
+               have_attr_list,
+               attr_list_indexes,
+               error);
+
+       g_array_free (summary_fields, FALSE);
+
+       return ebsql;
+}
+
+/**
+ * e_book_backend_sqlite_new
+ * @path: location where the db would be created
+ * @store_vcard: True if the vcard should be stored inside db, if FALSE only the summary fields would be 
stored inside db.
+ * @error:
+ *
+ * If the path for multiple addressbooks are same, the contacts from all addressbooks
+ * would be stored in same db in different tables.
+ *
+ * Returns:
+ *
+ * Since: 3.2
+ **/
+EBookBackendSqlite *
+e_book_backend_sqlite_new (const gchar *path,
+                          gboolean store_vcard,
+                          GError **error)
+{
+       return e_book_backend_sqlite_new_with_folderid (path, NULL, store_vcard, error);
+}
+
+/**
+ * e_book_backend_sqlite_new_full:
+ * @path: location where the db would be created
+ * @folderid: folder id of the address-book
+ * @store_vcard: True if the vcard should be stored inside db, if FALSE only the summary fields would be 
stored inside db.
+ * @setup: an #ESourceBackendSummarySetup describing how the summary should be setup
+ * @error: A location to store any error that may have occurred
+ *
+ * Like e_book_backend_sqlite_new(), but allows configuration of which contact fields
+ * will be stored for quick reference in the summary. The configuration indicated by
+ * @setup will only be taken into account when initially creating the underlying table,
+ * further configurations will be ignored.
+ *
+ * The fields %E_CONTACT_UID and %E_CONTACT_REV are not optional,
+ * they will be stored in the summary regardless of this function's parameters
+ *
+ * <note><para>Only #EContactFields with the type #G_TYPE_STRING, #G_TYPE_BOOLEAN or
+ * #E_TYPE_CONTACT_ATTR_LIST are currently supported.</para></note>
+ *
+ * Returns: (transfer full): The newly created #EBookBackendSqlite
+ *
+ * Since: 3.8
+ **/
+EBookBackendSqlite *
+e_book_backend_sqlite_new_full (const gchar *path,
+                               const gchar *folderid,
+                               gboolean store_vcard,
+                               ESourceBackendSummarySetup *setup,
+                               GError **error)
+{
+       EBookBackendSqlite *ebsql = NULL;
+       EContactField *fields;
+       EContactField *indexed_fields;
+       EBookIndexType *index_types = NULL;
+       gboolean have_attr_list = FALSE;
+       gint attr_list_indexes = 0;
+       gboolean had_error = FALSE;
+       GArray *summary_fields;
+       gint n_fields = 0, n_indexed_fields = 0, i;
+
+       fields         = e_source_backend_summary_setup_get_summary_fields (setup, &n_fields);
+       indexed_fields = e_source_backend_summary_setup_get_indexed_fields (setup, &index_types, 
&n_indexed_fields);
+
+       /* No specified summary fields indicates the default summary configuration should be used */
+       if (n_fields <= 0) {
+               ebsql = e_book_backend_sqlite_new_with_folderid (path, folderid, store_vcard, error);
+               g_free (fields);
+               g_free (index_types);
+               g_free (indexed_fields);
+
+               return ebsql;
+       }
+
+       summary_fields = g_array_new (FALSE, FALSE, sizeof (SummaryField));
+
+       /* Ensure the non-optional fields first */
+       summary_field_append (summary_fields, E_CONTACT_UID, &have_attr_list, error);
+       summary_field_append (summary_fields, E_CONTACT_REV, &have_attr_list, error);
+
+       for (i = 0; i < n_fields; i++) {
+               if (!summary_field_append (summary_fields, fields[i], &have_attr_list, error)) {
+                       had_error = TRUE;
+                       break;
+               }
+       }
+
+       if (had_error) {
+               g_array_free (summary_fields, TRUE);
+               g_free (fields);
+               g_free (index_types);
+               g_free (indexed_fields);
+               return NULL;
+       }
+
+       /* Add the 'indexed' flag to the SummaryField structs */
+       summary_fields_add_indexes (
+               summary_fields, indexed_fields, index_types, n_indexed_fields,
+               &attr_list_indexes);
+
+       ebsql = e_book_backend_sqlite_new_internal (
+               path, folderid,
+               store_vcard,
+               (SummaryField *) summary_fields->data,
+               summary_fields->len,
+               have_attr_list,
+               attr_list_indexes,
+               error);
+
+       g_free (fields);
+       g_free (index_types);
+       g_free (indexed_fields);
+       g_array_free (summary_fields, FALSE);
+
+       return ebsql;
+}
+
+gboolean
+e_book_backend_sqlite_lock_updates (EBookBackendSqlite *ebsql,
+                                      GError **error)
+{
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+
+       LOCK_MUTEX (&ebsql->priv->updates_lock);
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+       success = book_backend_sqlite_start_transaction (ebsql, error);
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       return success;
+}
+
+gboolean
+e_book_backend_sqlite_unlock_updates (EBookBackendSqlite *ebsql,
+                                        gboolean do_commit,
+                                        GError **error)
+{
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+       success = do_commit ?
+               book_backend_sqlite_commit_transaction (ebsql, error) :
+               book_backend_sqlite_rollback_transaction (ebsql, error);
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       UNLOCK_MUTEX (&ebsql->priv->updates_lock);
+
+       return success;
+}
+
+/**
+ * e_book_backend_sqlite_ref_collator:
+ * @ebsql: An #EBookBackendSqlite
+ *
+ * References the currently active #ECollator for @ebsql,
+ * use e_collator_unref() when finished using the returned collator.
+ *
+ * Note that the active collator will change with the active locale setting.
+ *
+ * Returns: (transfer full): A reference to the active collator.
+ */
+ECollator *
+e_book_backend_sqlite_ref_collator (EBookBackendSqlite *ebsql)
+{
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), NULL);
+
+       return e_collator_ref (ebsql->priv->collator);
+}
+
+/**
+ * e_book_backend_sqlite_add_contact
+ * @ebsql: An #EBookBackendSqlite
+ * @contact: EContact to be added
+ * @replace_existing: Whether this contact should replace another contact with the same UID.
+ * @error: A location to store any error that may have occurred.
+ *
+ * This is a convenience wrapper for e_book_backend_sqlite_add_contacts,
+ * which is the preferred means to add or modify multiple contacts when possible.
+ *
+ * Returns: TRUE on success.
+ *
+ * Since: 3.8
+ **/
+gboolean
+e_book_backend_sqlite_add_contact (EBookBackendSqlite *ebsql,
+                                  EContact *contact,
+                                  gboolean replace_existing,
+                                  GError **error)
+{
+       GSList l;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+       g_return_val_if_fail (E_IS_CONTACT (contact), FALSE);
+
+       l.data = contact;
+       l.next = NULL;
+
+       return e_book_backend_sqlite_add_contacts (
+               ebsql, &l,
+               replace_existing, error);
+}
+
+/**
+ * e_book_backend_sqlite_add_contacts
+ * @ebsql: An #EBookBackendSqlite
+ * @contacts: list of EContacts
+ * @replace_existing: Whether this contact should replace another contact with the same UID.
+ * @error: A location to store any error that may have occurred.
+ *
+ * Adds or replaces contacts in @ebsql. If @replace_existing is specified then existing
+ * contacts with the same UID will be replaced, otherwise adding an existing contact
+ * will return an error.
+ *
+ * Returns: TRUE on success.
+ *
+ * Since: 3.8
+ **/
+gboolean
+e_book_backend_sqlite_add_contacts (EBookBackendSqlite *ebsql,
+                                   GSList *contacts,
+                                   gboolean replace_existing,
+                                   GError **error)
+{
+       GSList *l;
+       gboolean success = TRUE;
+       gchar *default_region = NULL;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+       g_return_val_if_fail (contacts != NULL, FALSE);
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+
+       if (!book_backend_sqlite_start_transaction (ebsql, error)) {
+               UNLOCK_MUTEX (&ebsql->priv->lock);
+               return FALSE;
+       }
+
+       if (e_phone_number_is_supported ()) {
+               default_region = e_phone_number_get_default_region (error);
+
+               if (default_region == NULL)
+                       success = FALSE;
+       }
+
+       for (l = contacts; success && l != NULL; l = g_slist_next (l)) {
+               EContact *contact = (EContact *) l->data;
+
+               success = book_backend_sqlite_insert_contact (
+                       ebsql, contact, replace_existing,
+                       default_region, error);
+       }
+
+       g_free (default_region);
+
+       if (success)
+               success = book_backend_sqlite_commit_transaction (ebsql, error);
+       else
+               /* The GError is already set. */
+               book_backend_sqlite_rollback_transaction (ebsql, NULL);
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       return success;
+}
+
+/**
+ * e_book_backend_sqlite_remove_contact:
+ *
+ * FIXME: Document me.
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_book_backend_sqlite_remove_contact (EBookBackendSqlite *ebsql,
+                                     const gchar *uid,
+                                     GError **error)
+{
+       GSList l;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+       g_return_val_if_fail (uid != NULL, FALSE);
+
+       l.data = (gchar *) uid; /* Won't modify it, I promise :) */
+       l.next = NULL;
+
+       return e_book_backend_sqlite_remove_contacts (
+               ebsql, &l, error);
+}
+
+static gchar *
+generate_uid_list_for_stmt (GSList *uids)
+{
+       GString *str = g_string_new (NULL);
+       GSList  *l;
+       gboolean first = TRUE;
+
+       for (l = uids; l; l = l->next) {
+               gchar *uid = (gchar *) l->data;
+               gchar *tmp;
+
+               /* First uid with no comma */
+               if (!first)
+                       g_string_append_printf (str, ", ");
+               else
+                       first = FALSE;
+
+               tmp = sqlite3_mprintf ("%Q", uid);
+               g_string_append (str, tmp);
+               sqlite3_free (tmp);
+       }
+
+       return g_string_free (str, FALSE);
+}
+
+static gchar *
+generate_delete_stmt (const gchar *table,
+                      GSList *uids)
+{
+       GString *str = g_string_new (NULL);
+       gchar *tmp;
+
+       tmp = sqlite3_mprintf ("DELETE FROM %Q WHERE uid IN (", table);
+       g_string_append (str, tmp);
+       sqlite3_free (tmp);
+
+       tmp = generate_uid_list_for_stmt (uids);
+       g_string_append (str, tmp);
+       g_free (tmp);
+       g_string_append_c (str, ')');
+
+       return g_string_free (str, FALSE);
+}
+
+/**
+ * e_book_backend_sqlite_remove_contacts:
+ *
+ * FIXME: Document me.
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_book_backend_sqlite_remove_contacts (EBookBackendSqlite *ebsql,
+                                      GSList *uids,
+                                      GError **error)
+{
+       gboolean success = TRUE;
+       gchar *stmt;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+       g_return_val_if_fail (uids != NULL, FALSE);
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+
+       if (!book_backend_sqlite_start_transaction (ebsql, error)) {
+               UNLOCK_MUTEX (&ebsql->priv->lock);
+               return FALSE;
+       }
+
+       /* Delete the auxillary contact infos first */
+       if (success && ebsql->priv->have_attr_list) {
+               gchar *lists_folder = g_strdup_printf ("%s_lists", ebsql->priv->folderid);
+
+               stmt = generate_delete_stmt (lists_folder, uids);
+               g_free (lists_folder);
+
+               success = book_backend_sqlite_exec (ebsql, stmt, NULL, NULL, error);
+               g_free (stmt);
+       }
+
+       if (success) {
+               stmt = generate_delete_stmt (ebsql->priv->folderid, uids);
+               success = book_backend_sqlite_exec (ebsql, stmt, NULL, NULL, error);
+               g_free (stmt);
+       }
+
+       if (success)
+               success = book_backend_sqlite_commit_transaction (ebsql, error);
+       else
+               /* The GError is already set. */
+               book_backend_sqlite_rollback_transaction (ebsql, NULL);
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       return success;
+}
+
+/**
+ * e_book_backend_sqlite_has_contact:
+ *
+ * FIXME: Document me.
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_book_backend_sqlite_has_contact (EBookBackendSqlite *ebsql,
+                                  const gchar *uid,
+                                  gboolean *exists,
+                                  GError **error)
+{
+       gboolean local_exists = FALSE;
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+       g_return_val_if_fail (uid != NULL, FALSE);
+       g_return_val_if_fail (exists != NULL, FALSE);
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+
+       success = book_backend_sqlite_exec_printf (
+               ebsql, "SELECT uid FROM %Q WHERE uid = %Q",
+               get_exists_cb, &local_exists, error, ebsql->priv->folderid, uid);
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       *exists = local_exists;
+
+       return success;
+}
+
+/**
+ * e_book_backend_sqlite_get_contact:
+ *
+ * FIXME: Document me.
+ *
+ * Since: 3.2
+ **/
+EContact *
+e_book_backend_sqlite_get_contact (EBookBackendSqlite *ebsql,
+                                  const gchar *uid,
+                                  GHashTable *fields_of_interest,
+                                  gboolean *with_all_required_fields,
+                                  GError **error)
+{
+       EContact *contact = NULL;
+       gchar *vcard;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), NULL);
+       g_return_val_if_fail (uid != NULL, NULL);
+
+       vcard = e_book_backend_sqlite_get_vcard_string (
+               ebsql, uid, fields_of_interest, with_all_required_fields, error);
+
+       if (vcard != NULL) {
+               contact = e_contact_new_from_vcard_with_uid (vcard, uid);
+               g_free (vcard);
+       }
+
+       return contact;
+}
+
+/**
+ * e_book_backend_sqlite_check_summary_fields:
+ * @ebsql: An #EBookBackendSqlite
+ * @fields_of_interest: A hash table containing the fields of interest
+ * 
+ * Checks if all the specified fields are part of the configured summary
+ * fields for @ebsql
+ *
+ * Since: 3.8
+ **/
+gboolean
+e_book_backend_sqlite_check_summary_fields (EBookBackendSqlite *ebsql,
+                                              GHashTable *fields_of_interest)
+{
+       gboolean summary_fields = TRUE;
+       GHashTableIter iter;
+       gpointer key, value;
+
+       if (!fields_of_interest)
+               return FALSE;
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+
+       g_hash_table_iter_init (&iter, fields_of_interest);
+       while (g_hash_table_iter_next (&iter, &key, &value)) {
+               const gchar  *field_name = key;
+               
+               if (!summary_field_get_by_name (ebsql, field_name)) {
+                       summary_fields = FALSE;
+                       break;
+               }
+       }
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       return summary_fields;
+}
+
+/**
+ * e_book_backend_sqlite_get_vcard_string:
+ * @ebsql: An #EBookBackendSqlite
+ * @uid: The uid to fetch a vcard for
+ * @fields_of_interest: The required fields for this vcard, or %NULL to require all fields.
+ * @with_all_required_fields: (allow none) (out): Whether all the required fields are present in the 
returned vcard.
+ * @error: A location to store any error that may have occurred.
+ *
+ * Searches @ebsql in the context of @folderid for @uid.
+ *
+ * If @ebsql is configured to store the whole vcards, the whole vcard will be returned.
+ * Otherwise the summary cache will be searched and the virtual vcard will be built
+ * from the summary cache.
+ *
+ * In either case, @with_all_required_fields if specified, will be updated to reflect whether
+ * the returned vcard string satisfies the passed 'fields_of_interest' parameter.
+ * 
+ * Returns: (transfer full): The vcard string for @uid or %NULL if @uid was not found.
+ *
+ * Since: 3.2
+ */
+gchar *
+e_book_backend_sqlite_get_vcard_string (EBookBackendSqlite *ebsql,
+                                       const gchar *uid,
+                                       GHashTable *fields_of_interest,
+                                       gboolean *with_all_required_fields,
+                                       GError **error)
+{
+       gchar *vcard_str = NULL;
+       gboolean local_with_all_required_fields = FALSE;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), NULL);
+       g_return_val_if_fail (uid != NULL, NULL);
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+
+       /* Try constructing contacts from only UID/REV first if that's requested */
+       if (uid_rev_fields (fields_of_interest)) {
+               GSList *vcards = NULL;
+               gchar *select_portion;
+
+               select_portion = summary_select_stmt (fields_of_interest, FALSE);
+
+               book_backend_sqlite_exec_printf (
+                       ebsql, "%s FROM %Q AS summary WHERE summary.uid = %Q",
+                       collect_lean_results_cb, &vcards, error,
+                       select_portion, ebsql->priv->folderid, uid);
+
+               g_free (select_portion);
+
+               if (vcards) {
+                       EbSqlSearchData *s_data = (EbSqlSearchData *) vcards->data;
+
+                       vcard_str = s_data->vcard;
+                       s_data->vcard = NULL;
+
+                       g_slist_free_full (vcards, (GDestroyNotify)e_book_backend_sqlite_search_data_free);
+                       vcards = NULL;
+               }
+
+               local_with_all_required_fields = TRUE;
+       } else if (ebsql->priv->store_vcard) {
+
+               book_backend_sqlite_exec_printf (
+                       ebsql, "SELECT vcard FROM %Q WHERE uid = %Q",
+                       get_string_cb, &vcard_str, error, ebsql->priv->folderid, uid);
+
+               local_with_all_required_fields = TRUE;
+       } else {
+               g_set_error (
+                       error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_OTHER,
+                       _("Full search_contacts are not stored in cache. vcards cannot be returned."));
+
+       }
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       if (with_all_required_fields)
+               *with_all_required_fields = local_with_all_required_fields;
+       if (!vcard_str && error && !*error)
+               g_set_error (
+                       error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_CONTACT_NOT_FOUND,
+                       _("Contact '%s' not found"), uid ? uid : "NULL");
+
+       return vcard_str;
+}
+
+/**
+ * e_book_backend_sqlite_check_summary_query:
+ * @ebsql: an #EBookBackendSqlite
+ * @query: the query to check
+ * @with_list_attrs: Return location to store whether the query touches upon list attributes
+ *
+ * Checks whether @query contains only checks for the summary fields
+ * configured in @ebsql
+ *
+ * Since: 3.8
+ **/
+gboolean
+e_book_backend_sqlite_check_summary_query (EBookBackendSqlite *ebsql,
+                                             const gchar *query,
+                                             gboolean *with_list_attrs)
+{
+       gboolean is_summary;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+       is_summary = e_book_backend_sqlite_check_summary_query_locked (ebsql, query, with_list_attrs, NULL, 
NULL);
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       return is_summary;
+}
+
+/**
+ * e_book_backend_sqlite_search 
+ * @ebsql: 
+ * @sexp: search expression; use NULL or an empty string to get all stored
+ * contacts.
+ * @fields_of_interest: a #GHashTable containing the names of fields to return,
+ * or NULL for all.  At the moment if this is non-null, the vcard will be
+ * populated with summary fields, else it would return the whole vcard if
+ * its stored in the db. [not implemented fully]
+ * @searched: (allow none) (out): Whether @ebsql was capable of searching
+ * for the provided query @sexp.
+ * @with_all_required_fields: (allow none) (out): Whether all the required
+ * fields are present in the returned vcards.
+ * @error: 
+ *
+ * Searching with summary fields is always supported. Search expressions
+ * containing any other field is supported only if backend chooses to store
+ * the vcard inside the db.
+ *
+ * Summary fields - uid, rev, nickname, given_name, family_name, file_as
+ * email_1, email_2, email_3, email_4, is_list, list_show_addresses, wants_html
+ *
+ * If @ebsql was incapable of returning vcards with results that satisfy
+ * @fields_of_interest, then @with_all_required_fields will be updated to
+ * @FALSE and only uid fields will be present in the returned vcards. This
+ * can be useful when a summary query succeeds and the returned list can be
+ * used to iterate and fetch for full required data from another persistance.
+ *
+ * Returns: List of EbSqlSearchData.
+ *
+ * Since: 3.2
+ **/
+GSList *
+e_book_backend_sqlite_search (EBookBackendSqlite *ebsql,
+                             const gchar *sexp,
+                             GHashTable *fields_of_interest,
+                             gboolean *searched,
+                             gboolean *with_all_required_fields,
+                             GError **error)
+{
+       GSList *search_contacts = NULL;
+       gboolean local_searched = FALSE;
+       gboolean local_with_all_required_fields = FALSE;
+       gboolean query_with_list_attrs = FALSE;
+       gboolean query_unsupported = FALSE;
+       gboolean query_invalid = FALSE;
+       gboolean summary_query = FALSE;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), NULL);
+
+       if (sexp && !*sexp)
+               sexp = NULL;
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+
+       if (sexp)
+               summary_query = e_book_backend_sqlite_check_summary_query_locked (
+                       ebsql, sexp,
+                       &query_with_list_attrs,
+                       &query_unsupported, &query_invalid);
+
+       if (query_unsupported)
+               g_set_error (
+                       error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_NOT_SUPPORTED,
+                       _("Query contained unsupported elements"));
+       else if (query_invalid)
+               g_set_error (
+                       error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_INVALID_QUERY,
+                       _("Invalid Query"));
+       else if (!sexp || summary_query) {
+               gchar *sql_query;
+
+               sql_query = sexp ? sexp_to_sql_query (ebsql, sexp) : NULL;
+               search_contacts = book_backend_sqlite_search_query (
+                       ebsql, sql_query,
+                       fields_of_interest,
+                       &local_with_all_required_fields,
+                       query_with_list_attrs, error);
+               g_free (sql_query);
+
+               local_searched = TRUE;
+
+       } else if (ebsql->priv->store_vcard) {
+               search_contacts = book_backend_sqlite_search_full (
+                       ebsql, sexp, FALSE, error);
+
+               local_searched = TRUE;
+               local_with_all_required_fields = TRUE;
+
+       } else {
+               g_set_error (
+                       error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_OTHER,
+                       _("Full search_contacts are not stored in cache. "
+                       "Hence only summary query is supported."));
+       }
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       if (searched)
+               *searched = local_searched;
+       if (with_all_required_fields)
+               *with_all_required_fields = local_with_all_required_fields;
+
+       return search_contacts;
+}
+
+/**
+ * e_book_backend_sqlite_search_uids:
+ *
+ * FIXME: Document me.
+ *
+ * Since: 3.2
+ **/
+GSList *
+e_book_backend_sqlite_search_uids (EBookBackendSqlite *ebsql,
+                                  const gchar *sexp,
+                                  gboolean *searched,
+                                  GError **error)
+{
+       GSList *uids = NULL;
+       gboolean local_searched = FALSE;
+       gboolean query_with_list_attrs = FALSE;
+       gboolean query_unsupported = FALSE;
+       gboolean summary_query = FALSE;
+       gboolean query_invalid = FALSE;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), NULL);
+
+       if (sexp && !*sexp)
+               sexp = NULL;
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+
+       if (sexp)
+               summary_query = e_book_backend_sqlite_check_summary_query_locked (
+                       ebsql, sexp,
+                       &query_with_list_attrs,
+                       &query_unsupported,
+                       &query_invalid);
+
+       if (query_unsupported)
+               g_set_error (
+                       error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_NOT_SUPPORTED,
+                       _("Query contained unsupported elements"));
+       else if (query_invalid)
+               g_set_error (
+                       error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_INVALID_QUERY,
+                       _("Invalid query"));
+       else if (!sexp || summary_query) {
+               gchar *sql_query = sexp ? sexp_to_sql_query (ebsql, sexp) : NULL;
+
+               if (sql_query && sql_query[0]) {
+
+                       if (query_with_list_attrs) {
+                               gchar *list_table = g_strconcat (ebsql->priv->folderid, "_lists", NULL);
+
+                               book_backend_sqlite_exec_printf (
+                                       ebsql,
+                                       "SELECT DISTINCT summary.uid FROM %Q AS summary "
+                                       "LEFT OUTER JOIN %Q AS multi ON summary.uid = multi.uid WHERE %s",
+                                       collect_uid_results_cb, &uids, error,
+                                       ebsql->priv->folderid, list_table, sql_query);
+
+                               g_free (list_table);
+                       } else {
+                               book_backend_sqlite_exec_printf (
+                                       ebsql, "SELECT summary.uid FROM %Q AS summary WHERE %s",
+                                       collect_uid_results_cb, &uids, error,
+                                       ebsql->priv->folderid, sql_query);
+                       }
+
+               } else {
+                       book_backend_sqlite_exec_printf (
+                               ebsql, "SELECT uid FROM %Q",
+                               collect_uid_results_cb, &uids, error,
+                               ebsql->priv->folderid);
+               }
+
+               local_searched = TRUE;
+
+               g_free (sql_query);
+
+       } else if (ebsql->priv->store_vcard) {
+               uids = book_backend_sqlite_search_full (
+                       ebsql, sexp, TRUE, error);
+
+               local_searched = TRUE;
+
+       } else {
+               g_set_error (
+                       error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_OTHER,
+                       _("Full vcards are not stored in cache. "
+                       "Hence only summary query is supported."));
+       }
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       if (searched)
+               *searched = local_searched;
+
+       return uids;
+}
+
+/**
+ * e_book_backend_sqlite_get_uids_and_rev:
+ *
+ * Gets hash table of all uids (key) and rev (value) pairs stored
+ * for each contact in the cache. The hash table should be freed
+ * with g_hash_table_destroy(), if not needed anymore. Each key
+ * and value is a newly allocated string.
+ *
+ * Since: 3.4
+ **/
+GHashTable *
+e_book_backend_sqlite_get_uids_and_rev (EBookBackendSqlite *ebsql,
+                                       GError **error)
+{
+       GHashTable *uids_and_rev;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), NULL);
+
+       uids_and_rev = g_hash_table_new_full (g_str_hash, g_str_equal, g_free, g_free);
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+
+       book_backend_sqlite_exec_printf (
+               ebsql, "SELECT uid, rev FROM %Q",
+               collect_uids_and_rev_cb, uids_and_rev, error, ebsql->priv->folderid);
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       return uids_and_rev;
+}
+
+/**
+ * e_book_backend_sqlite_get_key_value:
+ *
+ * FIXME: Document me.
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_book_backend_sqlite_get_key_value (EBookBackendSqlite *ebsql,
+                                    const gchar *key,
+                                    gchar **value,
+                                    GError **error)
+{
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+       g_return_val_if_fail (key != NULL, FALSE);
+       g_return_val_if_fail (value != NULL && *value == NULL, FALSE);
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+
+       success = book_backend_sqlite_exec_printf (
+               ebsql, "SELECT value FROM keys WHERE folder_id = %Q AND key = %Q",
+               get_string_cb, value, error, ebsql->priv->folderid, key);
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       return success;
+}
+
+/**
+ * e_book_backend_sqlite_set_key_value:
+ *
+ * FIXME: Document me.
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_book_backend_sqlite_set_key_value (EBookBackendSqlite *ebsql,
+                                    const gchar *key,
+                                    const gchar *value,
+                                    GError **error)
+{
+       gboolean success;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+       g_return_val_if_fail (key != NULL, FALSE);
+       g_return_val_if_fail (value != NULL, FALSE);
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+
+       if (!book_backend_sqlite_start_transaction (ebsql, error)) {
+               UNLOCK_MUTEX (&ebsql->priv->lock);
+               return FALSE;
+       }
+
+       success = book_backend_sqlite_exec_printf (
+               ebsql, "INSERT or REPLACE INTO keys (key, value, folder_id) values (%Q, %Q, %Q)",
+               NULL, NULL, error, key, value, ebsql->priv->folderid);
+
+       if (success)
+               success = book_backend_sqlite_commit_transaction (ebsql, error);
+       else
+               /* The GError is already set. */
+               book_backend_sqlite_rollback_transaction (ebsql, NULL);
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       return success;
+}
+
+/**
+ * e_book_backend_sqlite_get_key_value_int:
+ *
+ * FIXME: Document me.
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_book_backend_sqlite_get_key_value_int (EBookBackendSqlite *ebsql,
+                                        const gchar *key,
+                                        gint *value,
+                                        GError **error)
+{
+       gboolean success;
+       gchar *str_value = NULL;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+       g_return_val_if_fail (key != NULL, FALSE);
+       g_return_val_if_fail (value != NULL, FALSE);
+
+       success = e_book_backend_sqlite_get_key_value (ebsql, key, &str_value, error);
+
+       if (success) {
+
+               if (str_value)
+                       *value = g_ascii_strtoll (str_value, NULL, 10);
+               else
+                       *value = 0;
+
+               g_free (str_value);
+       }
+
+       return success;
+}
+
+/**
+ * e_book_backend_sqlite_set_key_value_int:
+ *
+ * FIXME: Document me.
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_book_backend_sqlite_set_key_value_int (EBookBackendSqlite *ebsql,
+                                        const gchar *key,
+                                        gint value,
+                                        GError **error)
+{
+       gboolean success;
+       gchar *str_value = NULL;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+       g_return_val_if_fail (key != NULL, FALSE);
+
+       str_value = g_strdup_printf ("%d", value);
+       success = e_book_backend_sqlite_set_key_value (ebsql,
+                                                      key,
+                                                      str_value,
+                                                      error);
+       g_free (str_value);
+
+       return success;
+}
+
+/**
+ * e_book_backend_sqlite_search_data_free:
+ *
+ * FIXME: Document me.
+ *
+ * Since: 3.2
+ **/
+void
+e_book_backend_sqlite_search_data_free (EbSqlSearchData *s_data)
+{
+       if (s_data) {
+               g_free (s_data->uid);
+               g_free (s_data->vcard);
+               g_slice_free (EbSqlSearchData, s_data);
+       }
+}
+
+static gboolean
+book_backend_sqlite_set_locale_internal (EBookBackendSqlite *ebsql,
+                                          const gchar          *locale,
+                                          GError              **error)
+{
+       EBookBackendSqlitePrivate *priv = ebsql->priv;
+       ECollator *collator;
+
+       if (g_strcmp0 (priv->locale, locale) != 0) {
+
+               collator = e_collator_new (locale, error);
+               if (!collator)
+                       return FALSE;
+
+               g_free (priv->locale);
+               priv->locale = g_strdup (locale);
+
+               if (ebsql->priv->collator)
+                       e_collator_unref (ebsql->priv->collator);
+
+               ebsql->priv->collator = collator;
+       }
+
+       return TRUE;
+}
+
+/**
+ * e_book_backend_sqlite_set_locale:
+ * @ebsql: An #EBookBackendSqlite
+ * @lc_collate: The new locale for the addressbook
+ * @error: A location to store any error that may have occurred
+ *
+ * Relocalizes any locale specific data in the specified
+ * new @lc_collate locale.
+ *
+ * The @lc_collate locale setting is stored and remembered on
+ * subsequent accesses of the addressbook, changing the locale
+ * will store the new locale and will modify sort keys and any
+ * locale specific data in the addressbook.
+ *
+ * Returns: Whether the new locale was successfully set.
+ *
+ * Since: 3.12
+ */
+gboolean
+e_book_backend_sqlite_set_locale (EBookBackendSqlite *ebsql,
+                                 const gchar        *lc_collate,
+                                 GError            **error)
+{
+       gboolean success;
+       gchar *stored_lc_collate;
+       gchar *current_region = NULL;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+
+       if (e_phone_number_is_supported ()) {
+               current_region = e_phone_number_get_default_region (error);
+
+               if (current_region == NULL) {
+                       UNLOCK_MUTEX (&ebsql->priv->lock);
+                       return FALSE;
+               }
+       }
+
+       if (!book_backend_sqlite_set_locale_internal (ebsql, lc_collate, error)) {
+               UNLOCK_MUTEX (&ebsql->priv->lock);
+               g_free (current_region);
+               return FALSE;
+       }
+
+       if (!book_backend_sqlite_start_transaction (ebsql, error)) {
+               UNLOCK_MUTEX (&ebsql->priv->lock);
+               g_free (current_region);
+               return FALSE;
+       }
+
+       success = book_backend_sqlite_exec_printf (
+               ebsql, "SELECT lc_collate FROM folders WHERE folder_id = %Q",
+               get_string_cb, &stored_lc_collate, error, ebsql->priv->folderid);
+
+       if (success && g_strcmp0 (stored_lc_collate, lc_collate) != 0)
+               success = book_backend_sqlite_upgrade (ebsql, current_region, lc_collate, error);
+
+       /* If for some reason we failed, then reset the collator to use the old locale */
+       if (!success)
+               book_backend_sqlite_set_locale_internal (ebsql, stored_lc_collate, NULL);
+
+       g_free (stored_lc_collate);
+       g_free (current_region);
+
+       if (!success)
+               goto rollback;
+
+       success = book_backend_sqlite_commit_transaction (ebsql, error);
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       return success;
+
+ rollback:
+       /* The GError is already set. */
+       book_backend_sqlite_rollback_transaction (ebsql, NULL);
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       return FALSE;
+}
+
+/**
+ * e_book_backend_sqlite_get_locale:
+ * @ebsql: An #EBookBackendSqlite
+ * @locale_out: (out) (transfer full): The location to return the current locale
+ * @error: A location to store any error that may have occurred
+ *
+ * Fetches the current locale setting for the address-book.
+ *
+ * Upon success, @lc_collate_out will hold the returned locale setting,
+ * otherwise %FALSE will be returned and @error will be updated accordingly.
+ *
+ * Returns: Whether the locale was successfully fetched.
+ *
+ * Since: 3.12
+ */
+gboolean
+e_book_backend_sqlite_get_locale (EBookBackendSqlite *ebsql,
+                                 gchar             **locale_out,
+                                 GError            **error)
+{
+       gboolean success;
+       GError *local_error = NULL;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+       g_return_val_if_fail (locale_out != NULL && *locale_out == NULL, FALSE);
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+
+       success = book_backend_sqlite_exec_printf (
+               ebsql, "SELECT lc_collate FROM folders WHERE folder_id = %Q",
+               get_string_cb, locale_out, error, ebsql->priv->folderid);
+
+       if (!book_backend_sqlite_set_locale_internal (ebsql, *locale_out, &local_error)) {
+               g_warning ("Error loading new locale: %s", local_error->message);
+               g_clear_error (&local_error);
+       }
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       return success;
+}
+
+/******************************************************************
+ *                          EbSqlCursor apis                      *
+ ******************************************************************/
+typedef struct _CursorState CursorState;
+
+struct _CursorState {
+       gchar            **values;    /* The current cursor position, results will be returned after this 
position */
+       gchar             *last_uid;  /* The current cursor contact UID position, used as a tie breaker */
+       EbSqlCursorOrigin  position;  /* The position is updated with the cursor state and is used to 
distinguish
+                                      * between the beginning and the ending of the cursor's contact list.
+                                      * While the cursor is in a non-null state, the position will be 
+                                      * EBSQL_CURSOR_ORIGIN_CURRENT.
+                                      */
+};
+
+struct _EbSqlCursor {
+       EBookBackendSExp *sexp;       /* An EBookBackendSExp based on the query, used by 
e_book_backend_sqlite_cursor_compare() */
+       gchar         *select_vcards; /* The first fragment when querying results */
+       gchar         *select_count;  /* The first fragment when querying contact counts */
+       gchar         *query;         /* The SQL query expression derived from the passed search expression */
+       gchar         *order;         /* The normal order SQL query fragment to append at the end, containing 
ORDER BY etc */
+       gchar         *reverse_order; /* The reverse order SQL query fragment to append at the end, 
containing ORDER BY etc */
+
+       EContactField       *sort_fields;   /* The fields to sort in a query in the order or sort priority */
+       EBookCursorSortType *sort_types;    /* The sort method to use for each field */
+       gint                 n_sort_fields; /* The amound of sort fields */
+
+       CursorState          state;
+};
+
+static CursorState *cursor_state_copy             (EbSqlCursor          *cursor,
+                                                  CursorState          *state);
+static void         cursor_state_free             (EbSqlCursor          *cursor,
+                                                  CursorState          *state);
+static void         cursor_state_clear            (EbSqlCursor          *cursor,
+                                                  CursorState          *state,
+                                                  EbSqlCursorOrigin     position);
+static void         cursor_state_set_from_contact (EBookBackendSqlite *ebsql,
+                                                  EbSqlCursor          *cursor,
+                                                  CursorState          *state,
+                                                  EContact             *contact);
+static void         cursor_state_set_from_vcard   (EBookBackendSqlite *ebsql,
+                                                  EbSqlCursor          *cursor,
+                                                  CursorState          *state,
+                                                  const gchar          *vcard);
+
+static CursorState *
+cursor_state_copy (EbSqlCursor        *cursor,
+                  CursorState        *state)
+{
+       CursorState *copy;
+       gint i;
+
+       copy = g_slice_new0 (CursorState);
+       copy->values = g_new0 (gchar *, cursor->n_sort_fields);
+
+       for (i = 0; i < cursor->n_sort_fields; i++)
+               copy->values[i] = g_strdup (state->values[i]);
+
+       copy->last_uid = g_strdup (state->last_uid);
+       copy->position = state->position;
+
+       return copy;
+}
+
+static void
+cursor_state_free (EbSqlCursor  *cursor,
+                  CursorState  *state)
+{
+       if (state) {
+               cursor_state_clear (cursor, state, EBSQL_CURSOR_ORIGIN_BEGIN);
+               g_free (state->values);
+               g_slice_free (CursorState, state);
+       }
+}
+
+static void
+cursor_state_clear (EbSqlCursor        *cursor,
+                   CursorState        *state,
+                   EbSqlCursorOrigin   position)
+{
+       gint i;
+
+       for (i = 0; i < cursor->n_sort_fields; i++) {
+               g_free (state->values[i]);
+               state->values[i] = NULL;
+       }
+
+       g_free (state->last_uid);
+       state->last_uid = NULL;
+       state->position = position;
+}
+
+static void
+cursor_state_set_from_contact (EBookBackendSqlite *ebsql,
+                              EbSqlCursor          *cursor,
+                              CursorState          *state,
+                              EContact             *contact)
+{
+       gint i;
+
+       cursor_state_clear (cursor, state, EBSQL_CURSOR_ORIGIN_BEGIN);
+
+       for (i = 0; i < cursor->n_sort_fields; i++) {
+               const gchar *string = e_contact_get_const (contact, cursor->sort_fields[i]);
+
+               if (string)
+                       state->values[i] =
+                               e_collator_generate_key (ebsql->priv->collator,
+                                                        string, NULL);
+               else
+                       state->values[i] = g_strdup ("");
+       }
+
+       state->last_uid = e_contact_get (contact, E_CONTACT_UID);
+       state->position = EBSQL_CURSOR_ORIGIN_CURRENT;
+}
+
+static void
+cursor_state_set_from_vcard (EBookBackendSqlite *ebsql,
+                            EbSqlCursor          *cursor,
+                            CursorState          *state,
+                            const gchar          *vcard)
+{
+       EContact *contact;
+
+       contact = e_contact_new_from_vcard (vcard);
+       cursor_state_set_from_contact (ebsql, cursor, state, contact);
+       g_object_unref (contact);
+}
+
+static void
+ebsql_cursor_setup_query (EBookBackendSqlite *ebsql,
+                         EbSqlCursor        *cursor,
+                         const gchar        *sexp,
+                         gboolean            query_with_list_attrs)
+{
+       gchar *stmt;
+       gchar *count_stmt;
+
+       g_free (cursor->select_vcards);
+       g_free (cursor->select_count);
+       g_free (cursor->query);
+       g_clear_object (&(cursor->sexp));
+
+       if (query_with_list_attrs) {
+               gchar *list_table = g_strconcat (ebsql->priv->folderid, "_lists", NULL);
+
+               stmt = sqlite3_mprintf ("SELECT DISTINCT summary.uid, vcard FROM %Q AS summary "
+                                       "LEFT OUTER JOIN %Q AS multi ON summary.uid = multi.uid",
+                                       ebsql->priv->folderid, list_table);
+
+               count_stmt = sqlite3_mprintf ("SELECT count(DISTINCT summary.uid), vcard FROM %Q AS summary "
+                                             "LEFT OUTER JOIN %Q AS multi ON summary.uid = multi.uid",
+                                             ebsql->priv->folderid, list_table);
+               g_free (list_table);
+       } else {
+               stmt = sqlite3_mprintf ("SELECT uid, vcard FROM %Q AS summary", ebsql->priv->folderid);
+               count_stmt = sqlite3_mprintf ("SELECT count(*) FROM %Q AS summary", ebsql->priv->folderid);
+       }
+
+       cursor->select_vcards = g_strdup (stmt);
+       cursor->select_count = g_strdup (count_stmt);
+       sqlite3_free (stmt);
+       sqlite3_free (count_stmt);
+
+       if (sexp) {
+               cursor->query = sexp_to_sql_query (ebsql, sexp);
+               cursor->sexp  = e_book_backend_sexp_new (sexp);
+       } else {
+               cursor->query = NULL;
+               cursor->sexp  = NULL;
+       }
+}
+
+static gchar *
+ebsql_cursor_order_by_fragment (EBookBackendSqlite        *ebsql,
+                               const EContactField       *sort_fields,
+                               const EBookCursorSortType *sort_types,
+                               guint                      n_sort_fields,
+                               gboolean                   reverse)
+{
+       GString *string;
+       gint i;
+
+       string = g_string_new ("ORDER BY ");
+
+       for (i = 0; i < n_sort_fields; i++) {
+               SummaryField *field = summary_field_get (ebsql, sort_fields[i]);
+
+               if (i > 0)
+                       g_string_append (string, ", ");
+
+               g_string_append_printf (string, "summary.%s_localized %s", field->dbname,
+                                       reverse ?
+                                       (sort_types[i] == E_BOOK_CURSOR_SORT_ASCENDING ? "DESC" : "ASC") :
+                                       (sort_types[i] == E_BOOK_CURSOR_SORT_ASCENDING ? "ASC"  : "DESC"));
+       }
+
+       /* Also order the UID, since it's our tie breaker, we must also order the UID field */
+       if (n_sort_fields > 0)
+               g_string_append (string, ", ");
+       g_string_append_printf (string, "summary.uid %s", reverse ? "DESC" : "ASC");
+
+       return g_string_free (string, FALSE);
+}
+
+static EbSqlCursor *
+ebsql_cursor_new (EBookBackendSqlite        *ebsql,
+                 const gchar               *sexp,
+                 gboolean                   query_with_list_attrs,
+                 const EContactField       *sort_fields,
+                 const EBookCursorSortType *sort_types,
+                 guint                      n_sort_fields)
+{
+       EbSqlCursor *cursor = g_slice_new0 (EbSqlCursor);
+
+       /* Setup the initial query fragments */
+       ebsql_cursor_setup_query (ebsql, cursor, sexp, query_with_list_attrs);
+
+       cursor->order = ebsql_cursor_order_by_fragment (ebsql,
+                                                       sort_fields,
+                                                       sort_types,
+                                                       n_sort_fields,
+                                                       FALSE);
+       cursor->reverse_order = ebsql_cursor_order_by_fragment (ebsql,
+                                                               sort_fields,
+                                                               sort_types,
+                                                               n_sort_fields,
+                                                               TRUE);
+
+       /* Sort parameters */
+       cursor->n_sort_fields  = n_sort_fields;
+       cursor->sort_fields    = g_memdup (sort_fields, sizeof (EContactField) * n_sort_fields);
+       cursor->sort_types     = g_memdup (sort_types,  sizeof (EBookCursorSortType) * n_sort_fields);
+
+       /* Cursor state */
+       cursor->state.values   = g_new0 (gchar *, n_sort_fields);
+       cursor->state.last_uid = NULL;
+       cursor->state.position = EBSQL_CURSOR_ORIGIN_BEGIN;
+
+       return cursor;
+}
+
+static void
+ebsql_cursor_free (EbSqlCursor *cursor)
+{
+       if (cursor) {
+               cursor_state_clear (cursor, &(cursor->state), EBSQL_CURSOR_ORIGIN_BEGIN);
+               g_free (cursor->state.values);
+
+               g_clear_object (&(cursor->sexp));
+               g_free (cursor->select_vcards);
+               g_free (cursor->select_count);
+               g_free (cursor->query);
+               g_free (cursor->order);
+               g_free (cursor->reverse_order);
+               g_free (cursor->sort_fields);
+               g_free (cursor->sort_types);
+
+               g_slice_free (EbSqlCursor, cursor);
+       }
+}
+
+#define GREATER_OR_LESS(cursor, index, reverse)                                \
+       (reverse ?                                                      \
+        (((EbSqlCursor *)cursor)->sort_types[index] == E_BOOK_CURSOR_SORT_ASCENDING ? '<' : '>') : \
+        (((EbSqlCursor *)cursor)->sort_types[index] == E_BOOK_CURSOR_SORT_ASCENDING ? '>' : '<'))
+
+static gchar *
+ebsql_cursor_constraints (EBookBackendSqlite *ebsql,
+                         EbSqlCursor        *cursor,
+                         CursorState        *state,
+                         gboolean            reverse,
+                         gboolean            include_current_uid)
+{
+       GString *string;
+       SummaryField *field;
+       gint i, j;
+
+       /* Example for:
+        *    ORDER BY family_name ASC, given_name DESC
+        *
+        * Where current cursor values are:
+        *    family_name = Jackson
+        *    given_name  = Micheal
+        *
+        * With reverse = FALSE
+        *
+        *    (summary.family_name > 'Jackson') OR
+        *    (summary.family_name = 'Jackson' AND summary.given_name < 'Micheal') OR
+        *    (summary.family_name = 'Jackson' AND summary.given_name = 'Micheal' AND summary.uid > 
'last-uid')
+        *
+        * With reverse = TRUE (needed for moving the cursor backwards through results)
+        *
+        *    (summary.family_name < 'Jackson') OR
+        *    (summary.family_name = 'Jackson' AND summary.given_name > 'Micheal') OR
+        *    (summary.family_name = 'Jackson' AND summary.given_name = 'Micheal' AND summary.uid < 
'last-uid')
+        *
+        */
+
+       string = g_string_new (NULL);
+
+       for (i = 0; i <= cursor->n_sort_fields; i++) {
+               gchar   *stmt;
+
+               /* Break once we hit a NULL value */
+               if ((i  < cursor->n_sort_fields && state->values[i] == NULL) ||
+                   (i == cursor->n_sort_fields && state->last_uid  == NULL))
+                       break;
+
+               /* Between each qualifier, add an 'OR' */
+               if (i > 0)
+                       g_string_append (string, " OR ");
+
+               /* Begin qualifier */
+               g_string_append_c (string, '(');
+
+               /* Create the '=' statements leading up to the current tie breaker */
+               for (j = 0; j < i; j++) {
+                       field = summary_field_get (ebsql, cursor->sort_fields[j]);
+
+                       stmt = sqlite3_mprintf ("summary.%s_localized = %Q",
+                                               field->dbname, state->values[j]);
+
+                       g_string_append (string, stmt);
+                       g_string_append (string, " AND ");
+
+                       sqlite3_free (stmt);
+               }
+
+               if (i == cursor->n_sort_fields) {
+
+                       /* The 'include_current_uid' clause is used for calculating
+                        * the current position of the cursor, inclusive of the
+                        * current position.
+                        */
+                       if (include_current_uid)
+                               g_string_append_c (string, '(');
+
+                       /* Append the UID tie breaker */
+                       stmt = sqlite3_mprintf ("summary.uid %c %Q",
+                                               reverse ? '<' : '>',
+                                               state->last_uid);
+                       g_string_append (string, stmt);
+                       sqlite3_free (stmt);
+
+                       if (include_current_uid) {
+                               stmt = sqlite3_mprintf (" OR summary.uid = %Q",
+                                                       state->last_uid);
+                               g_string_append (string, stmt);
+                               g_string_append_c (string, ')');
+                               sqlite3_free (stmt);
+                       }
+
+               } else {
+
+                       /* SPECIAL CASE: If we have a parially set cursor state, then we must
+                        * report next results that are inclusive of the final qualifier.
+                        *
+                        * This allows one to set the cursor with the family name set to 'J'
+                        * and include the results for contact's Mr & Miss 'J'.
+                        */
+                       gboolean include_exact_match =
+                               (reverse == FALSE &&
+                                ((i + 1 < cursor->n_sort_fields && state->values[i + 1] == NULL) ||
+                                 (i + 1 == cursor->n_sort_fields && state->last_uid == NULL)));
+
+                       if (include_exact_match)
+                               g_string_append_c (string, '(');
+
+                       /* Append the final qualifier for this field */
+                       field = summary_field_get (ebsql, cursor->sort_fields[i]);
+                       stmt = sqlite3_mprintf ("summary.%s_localized %c %Q",
+                                               field->dbname,
+                                               GREATER_OR_LESS (cursor, i, reverse),
+                                               state->values[i]);
+
+                       g_string_append (string, stmt);
+                       sqlite3_free (stmt);
+
+                       if (include_exact_match) {
+
+                               stmt = sqlite3_mprintf (" OR summary.%s_localized = %Q",
+                                                       field->dbname, state->values[i]);
+
+                               g_string_append (string, stmt);
+                               g_string_append_c (string, ')');
+                               sqlite3_free (stmt);
+                       }
+               }
+
+               /* End qualifier */
+               g_string_append_c (string, ')');
+       }
+
+       return g_string_free (string, FALSE);
+}
+
+static gboolean
+cursor_count_total_locked (EBookBackendSqlite *ebsql,
+                          EbSqlCursor          *cursor,
+                          gint                 *total,
+                          GError              **error)
+{
+       GString *query;
+       gboolean success;
+
+       query = g_string_new (cursor->select_count);
+
+       /* Add the filter constraints (if any) */
+       if (cursor->query) {
+               g_string_append (query, " WHERE ");
+
+               g_string_append_c (query, '(');
+               g_string_append (query, cursor->query);
+               g_string_append_c (query, ')');
+       }
+
+       /* Execute the query */
+       success = book_backend_sqlite_exec (ebsql, query->str,
+                                             get_count_cb, total, error);
+
+       g_string_free (query, TRUE);
+
+       return success;
+}
+
+static gboolean
+cursor_count_position_locked (EBookBackendSqlite *ebsql,
+                             EbSqlCursor          *cursor,
+                             gint                 *position,
+                             GError              **error)
+{
+       GString *query;
+       gboolean success;
+
+       query = g_string_new (cursor->select_count);
+
+       /* Add the filter constraints (if any) */
+       if (cursor->query) {
+               g_string_append (query, " WHERE ");
+
+               g_string_append_c (query, '(');
+               g_string_append (query, cursor->query);
+               g_string_append_c (query, ')');
+       }
+
+       /* Add the cursor constraints (if any) */
+       if (cursor->state.values[0] != NULL) {
+               gchar *constraints = NULL;
+
+               if (!cursor->query)
+                       g_string_append (query, " WHERE ");
+               else
+                       g_string_append (query, " AND ");
+
+               /* Here we do a reverse query, we're looking for all the
+                * results leading up to the current cursor value, including
+                * the cursor value
+                */
+               constraints = ebsql_cursor_constraints (ebsql, cursor,
+                                                       &(cursor->state),
+                                                       TRUE, TRUE);
+
+               g_string_append_c (query, '(');
+               g_string_append (query, constraints);
+               g_string_append_c (query, ')');
+
+               g_free (constraints);
+       }
+
+       /* Execute the query */
+       success = book_backend_sqlite_exec (ebsql, query->str,
+                                             get_count_cb, position, error);
+
+       g_string_free (query, TRUE);
+
+       return success;
+}
+
+/**
+ * e_book_backend_sqlite_cursor_new:
+ * @ebsql: An #EBookBackendSqlite
+ * @folderid: folder id of the address-book
+ * @sexp: search expression; use NULL or an empty string to get all stored contacts.
+ * @sort_fields: (array length=n_sort_fields): An array of #EContactFields as sort keys in order of priority
+ * @sort_types: (array length=n_sort_fields): An array of #EBookCursorSortTypes, one for each field in 
@sort_fields
+ * @n_sort_fields: The number of fields to sort results by.
+ * @error: A return location to store any error that might be reported.
+ *
+ * Creates a new #EbSqlCursor.
+ *
+ * The cursor should be freed with e_book_backend_sqlite_cursor_free().
+ *
+ * Returns: (transfer full): A newly created #EbSqlCursor
+ *
+ * Since: 3.12
+ */
+EbSqlCursor *
+e_book_backend_sqlite_cursor_new (EBookBackendSqlite        *ebsql,
+                                 const gchar               *sexp,
+                                 const EContactField       *sort_fields,
+                                 const EBookCursorSortType *sort_types,
+                                 guint                      n_sort_fields,
+                                 GError                   **error)
+{
+       gboolean query_with_list_attrs = FALSE;
+       gint i;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), NULL);
+
+       /* We don't like '\0' sexps, prefer NULL */
+       if (sexp && !sexp[0])
+               sexp = NULL;
+
+       /* We only support cursors for summary fields in the query */
+       if (sexp && !e_book_backend_sqlite_check_summary_query (ebsql, sexp, &query_with_list_attrs)) {
+               g_set_error (error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_INVALID_QUERY,
+                            _("Only summary queries are supported by EbSqlCursor"));
+               return NULL;
+       }
+
+       if (n_sort_fields == 0) {
+               g_set_error (error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_INVALID_QUERY,
+                            _("At least one sort field must be specified to use an EbSqlCursor"));
+               return NULL;
+       }
+
+       /* We only support summarized sort keys which are not multi value fields */
+       for (i = 0; i < n_sort_fields; i++) {
+
+               gint support;
+
+               support = func_check_field_test (ebsql, e_contact_field_name (sort_fields[i]), NULL);
+
+               if ((support & CHECK_IS_SUMMARY) == 0) {
+                       g_set_error (error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_INVALID_QUERY,
+                                    _("Cannot sort by a field that is not in the summary"));
+                       return NULL;
+               }
+
+               if ((support & CHECK_IS_LIST_ATTR) != 0) {
+                       g_set_error (error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_INVALID_QUERY,
+                                    _("Cannot sort by a field which may have multiple values"));
+                       return NULL;
+               }
+       }
+
+       return ebsql_cursor_new (ebsql, sexp, query_with_list_attrs,
+                                sort_fields, sort_types, n_sort_fields);
+}
+
+/**
+ * e_book_backend_sqlite_cursor_free:
+ * @ebsql: An #EBookBackendSqlite
+ * @cursor: The #EbSqlCursor to free
+ *
+ * Frees @cursor.
+ *
+ * Since: 3.12
+ */
+void
+e_book_backend_sqlite_cursor_free (EBookBackendSqlite *ebsql,
+                                    EbSqlCursor          *cursor)
+{
+       g_return_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql));
+
+       ebsql_cursor_free (cursor);
+}
+
+typedef struct {
+       GSList *results;
+       gchar *alloc_vcard;
+       const gchar *last_vcard;
+
+       gboolean collect_results;
+       gint n_results;
+} CursorCollectData;
+
+static gint
+collect_results_for_cursor_cb (gpointer ref,
+                              gint col,
+                              gchar **cols,
+                              gchar **name)
+{
+       CursorCollectData *data = ref;
+
+       if (data->collect_results) {
+               EbSqlSearchData *search_data;
+
+               search_data = search_data_from_results (cols);
+
+               data->results = g_slist_prepend (data->results, search_data);
+
+               data->last_vcard = search_data->vcard;
+       } else {
+               g_free (data->alloc_vcard);
+               data->alloc_vcard = g_strdup (cols[1]);
+
+               data->last_vcard = data->alloc_vcard;
+       }
+
+       data->n_results++;
+
+       return 0;
+}
+
+/**
+ * e_book_backend_sqlite_cursor_step:
+ * @ebsql: An #EBookBackendSqlite
+ * @cursor: The #EbSqlCursor to use
+ * @flags: The #EbSqlCursorStepFlags for this step
+ * @origin: The #EbSqlCursorOrigin from whence to step
+ * @count: A positive or negative amount of contacts to try and fetch
+ * @results: (out) (allow-none) (element-type EbSqlSearchData) (transfer full):
+ *   A return location to store the results, or %NULL if %EBSQL_CURSOR_STEP_FETCH is not specified in %flags.
+ * @error: A return location to store any error that might be reported.
+ *
+ * Steps @cursor through it's sorted query by a maximum of @count contacts
+ * starting from @origin.
+ *
+ * If @count is negative, then the cursor will move through the list in reverse.
+ *
+ * If @cursor reaches the beginning or end of the query results, then the
+ * returned list might not contain the amount of desired contacts, or might
+ * return no results if the cursor currently points to the last contact. 
+ * Reaching the end of the list is not considered an error condition. Attempts
+ * to step beyond the end of the list after having reached the end of the list
+ * will however trigger an %E_BOOK_SQL_ERROR_END_OF_LIST error.
+ *
+ * If %EBSQL_CURSOR_STEP_FETCH is specified in %flags, a pointer to 
+ * a %NULL #GSList pointer should be provided for the @results parameter.
+ *
+ * The result list will be stored to @results and should be freed with g_slist_free()
+ * and all elements freed with e_book_backend_sqlite_search_data_free().
+ *
+ * Returns: The number of contacts traversed if successful, otherwise -1 is
+ * returned and @error is set.
+ *
+ * Since: 3.12
+ */
+gint
+e_book_backend_sqlite_cursor_step (EBookBackendSqlite *ebsql,
+                                    EbSqlCursor          *cursor,
+                                    EbSqlCursorStepFlags  flags,
+                                    EbSqlCursorOrigin     origin,
+                                    gint                  count,
+                                    GSList              **results,
+                                    GError              **error)
+{
+       CursorCollectData data = { NULL, NULL, NULL, FALSE, 0 };
+       CursorState *state;
+       GString *query;
+       gboolean success;
+       EbSqlCursorOrigin try_position;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), -1);
+       g_return_val_if_fail (cursor != NULL, -1);
+       g_return_val_if_fail ((flags & EBSQL_CURSOR_STEP_FETCH) == 0 ||
+                             (results != NULL && *results == NULL), -1);
+
+
+       /* Check if this step should result in an end of list error first */
+       try_position = cursor->state.position;
+       if (origin != EBSQL_CURSOR_ORIGIN_CURRENT)
+               try_position = origin;
+
+       /* Report errors for requests to run off the end of the list */
+       if (try_position == EBSQL_CURSOR_ORIGIN_BEGIN && count < 0) {
+               g_set_error (error, E_BOOK_SQL_ERROR,
+                            E_BOOK_SQL_ERROR_END_OF_LIST,
+                            _("Tried to step a cursor in reverse, "
+                              "but cursor is already at the beginning of the contact list"));
+
+               return -1;
+       } else if (try_position == EBSQL_CURSOR_ORIGIN_END && count > 0) {
+               g_set_error (error, E_BOOK_SQL_ERROR,
+                            E_BOOK_SQL_ERROR_END_OF_LIST,
+                            _("Tried to step a cursor forwards, "
+                              "but cursor is already at the end of the contact list"));
+
+               return -1;
+       }
+
+       /* Nothing to do, silently return */
+       if (count == 0 && try_position == EBSQL_CURSOR_ORIGIN_CURRENT)
+               return 0;
+
+       /* If we're not going to modify the position, just use
+        * a copy of the current cursor state.
+        */
+       if ((flags & EBSQL_CURSOR_STEP_MOVE) != 0)
+               state = &(cursor->state);
+       else
+               state = cursor_state_copy (cursor, &(cursor->state));
+
+       /* Every query starts with the STATE_CURRENT position, first
+        * fix up the cursor state according to 'origin'
+        */
+       switch (origin) {
+       case EBSQL_CURSOR_ORIGIN_CURRENT:
+               /* Do nothing, normal operation */
+               break;
+
+       case EBSQL_CURSOR_ORIGIN_BEGIN:
+       case EBSQL_CURSOR_ORIGIN_END:
+
+               /* Prepare the state before executing the query */
+               cursor_state_clear (cursor, state, origin);
+               break;
+       }
+
+       /* If count is 0 then there is no need to run any
+        * query, however it can be useful if you just want
+        * to move the cursor to the beginning or ending of
+        * the list.
+        */
+       if (count == 0) {
+
+               /* Free the state copy if need be */
+               if ((flags & EBSQL_CURSOR_STEP_MOVE) == 0)
+                       cursor_state_free (cursor, state);
+
+               return 0;
+       }
+
+       query = g_string_new (cursor->select_vcards);
+
+       /* Add the filter constraints (if any) */
+       if (cursor->query) {
+               g_string_append (query, " WHERE ");
+
+               g_string_append_c (query, '(');
+               g_string_append (query, cursor->query);
+               g_string_append_c (query, ')');
+       }
+
+       /* Add the cursor constraints (if any) */
+       if (state->values[0] != NULL) {
+               gchar *constraints = NULL;
+
+               if (!cursor->query)
+                       g_string_append (query, " WHERE ");
+               else
+                       g_string_append (query, " AND ");
+
+               constraints = ebsql_cursor_constraints (ebsql,
+                                                       cursor,
+                                                       state,
+                                                       count < 0,
+                                                       FALSE);
+
+               g_string_append_c (query, '(');
+               g_string_append (query, constraints);
+               g_string_append_c (query, ')');
+
+               g_free (constraints);
+       }
+
+       /* Add the sort order */
+       g_string_append_c (query, ' ');
+       if (count > 0)
+               g_string_append (query, cursor->order);
+       else
+               g_string_append (query, cursor->reverse_order);
+
+       /* Add the limit */
+       g_string_append_printf (query, " LIMIT %d", ABS (count));
+
+       /* Specify whether we really want results or not */
+       data.collect_results = (flags & EBSQL_CURSOR_STEP_FETCH) != 0;
+
+       /* Execute the query */
+       LOCK_MUTEX (&ebsql->priv->lock);
+       success = book_backend_sqlite_exec (ebsql, query->str,
+                                             collect_results_for_cursor_cb, &data,
+                                             error);
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       g_string_free (query, TRUE);
+
+       /* If there was no error, update the internal cursor state */
+       if (success) {
+
+               if (data.n_results < ABS (count)) {
+
+                       /* We've reached the end, clear the current state */
+                       if (count < 0)
+                               cursor_state_clear (cursor, state, EBSQL_CURSOR_ORIGIN_BEGIN);
+                       else
+                               cursor_state_clear (cursor, state, EBSQL_CURSOR_ORIGIN_END);
+
+               } else if (data.last_vcard) {
+
+                       /* Set the cursor state to the last result */
+                       cursor_state_set_from_vcard (ebsql, cursor, state, data.last_vcard);
+               } else
+                       /* Should never get here */
+                       g_warn_if_reached ();
+
+               /* Assign the results to return (if any) */
+               if (results) {
+                       /* Correct the order of results at the last minute */
+                       *results = g_slist_reverse (data.results);
+                       data.results = NULL;
+               }
+       }
+
+       /* Cleanup what was allocated by collect_results_for_cursor_cb() */
+       if (data.results)
+               g_slist_free_full (data.results,
+                                  (GDestroyNotify)e_book_backend_sqlite_search_data_free);
+       g_free (data.alloc_vcard);
+
+       /* Free the copy state if we were working with a copy */
+       if ((flags & EBSQL_CURSOR_STEP_MOVE) == 0)
+               cursor_state_free (cursor, state);
+
+       if (success)
+               return data.n_results;
+
+       return -1;
+}
+
+/**
+ * e_book_backend_sqlite_cursor_set_target_alphabetic_index:
+ * @ebsql: An #EBookBackendSqlite
+ * @cursor: The #EbSqlCursor to modify
+ * @index: The alphabetic index
+ *
+ * Sets the @cursor position to an
+ * <link linkend="cursor-alphabet">Alphabetic Index</link>
+ * into the alphabet active in @ebsql's locale.
+ *
+ * After setting the target to an alphabetic index, for example the
+ * index for letter 'E', then further calls to e_book_backend_sqlite_cursor_step()
+ * will return results starting with the letter 'E' (or results starting
+ * with the last result in 'D', if moving in a negative direction).
+ *
+ * The passed index must be a valid index in the active locale, knowledge
+ * on the currently active alphabet index must be obtained using #ECollator
+ * APIs.
+ *
+ * Use e_book_backend_sqlite_ref_collator() to obtain the active collator for @ebsql.
+ *
+ * Since: 3.12
+ */
+void
+e_book_backend_sqlite_cursor_set_target_alphabetic_index (EBookBackendSqlite *ebsql,
+                                                           EbSqlCursor          *cursor,
+                                                           gint                  index)
+{
+       gint n_labels = 0;
+
+       g_return_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql));
+       g_return_if_fail (cursor != NULL);
+       g_return_if_fail (index >= 0);
+
+       e_collator_get_index_labels (ebsql->priv->collator, &n_labels,
+                                    NULL, NULL, NULL);
+       g_return_if_fail (index < n_labels);
+
+       cursor_state_clear (cursor, &(cursor->state), EBSQL_CURSOR_ORIGIN_CURRENT);
+       if (cursor->n_sort_fields > 0) {
+               cursor->state.values[0] =
+                       e_collator_generate_key_for_index (ebsql->priv->collator,
+                                                          index);
+       }
+}
+
+/**
+ * e_book_backend_sqlite_cursor_set_sexp:
+ * @ebsql: An #EBookBackendSqlite
+ * @cursor: The #EbSqlCursor
+ * @sexp: The new query expression for @cursor
+ * @error: A return location to store any error that might be reported.
+ *
+ * Modifies the current query expression for @cursor. This will not
+ * modify @cursor's state, but will change the outcome of any further
+ * calls to e_book_backend_sqlite_cursor_calculate() or
+ * e_book_backend_sqlite_cursor_step().
+ *
+ * Returns: %TRUE if the expression was valid and accepted by @ebsql
+ *
+ * Since: 3.12
+ */
+gboolean
+e_book_backend_sqlite_cursor_set_sexp (EBookBackendSqlite *ebsql,
+                                        EbSqlCursor          *cursor,
+                                        const gchar          *sexp,
+                                        GError              **error)
+{
+       gboolean query_with_list_attrs = FALSE;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+       g_return_val_if_fail (cursor != NULL, FALSE);
+
+       /* We don't like '\0' sexps, prefer NULL */
+       if (sexp && !sexp[0])
+               sexp = NULL;
+
+       /* We only support cursors for summary fields in the query */
+       if (sexp && !e_book_backend_sqlite_check_summary_query (ebsql, sexp, &query_with_list_attrs)) {
+               g_set_error (error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_INVALID_QUERY,
+                            _("Only summary queries are supported by EbSqlCursor"));
+               return FALSE;
+       }
+
+       ebsql_cursor_setup_query (ebsql, cursor, sexp, query_with_list_attrs);
+
+       return TRUE;
+}
+
+/**
+ * e_book_backend_sqlite_cursor_calculate:
+ * @ebsql: An #EBookBackendSqlite
+ * @cursor: The #EbSqlCursor
+ * @total: (out) (allow-none): A return location to store the total result set for this cursor
+ * @position: (out) (allow-none): A return location to store the total results before the cursor value
+ * @error: (allow-none): A return location to store any error that might be reported.
+ *
+ * Calculates the @total amount of results for the @cursor's query expression,
+ * as well as the current @position of @cursor in the results. @position is
+ * represented as the amount of results which lead up to the current value
+ * of @cursor, if @cursor currently points to an exact contact, the position
+ * also includes the cursor contact.
+ *
+ * Returns: Whether @total and @position were successfully calculated.
+ *
+ * Since: 3.12
+ */
+gboolean
+e_book_backend_sqlite_cursor_calculate (EBookBackendSqlite *ebsql,
+                                         EbSqlCursor          *cursor,
+                                         gint                 *total,
+                                         gint                 *position,
+                                         GError              **error)
+{
+       gboolean success = TRUE;
+       gint local_total = 0;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+       g_return_val_if_fail (cursor != NULL, FALSE);
+
+       /* If we're in a clear cursor state, then the position is 0 */
+       if (position && cursor->state.values[0] == NULL) {
+
+               if (cursor->state.position == EBSQL_CURSOR_ORIGIN_BEGIN) {
+                       /* Mark the local pointer NULL, no need to calculate this anymore */
+                       *position = 0;
+                       position = NULL;
+               } else if (cursor->state.position == EBSQL_CURSOR_ORIGIN_END) {
+
+                       /* Make sure that we look up the total so we can
+                        * set the position to 'total + 1'
+                        */
+                       if (!total)
+                               total = &local_total;
+               }
+       }
+
+       /* Early return if there is nothing to do */
+       if (!total && !position)
+               return TRUE;
+
+       LOCK_MUTEX (&ebsql->priv->lock);
+
+       if (!book_backend_sqlite_start_transaction (ebsql, error)) {
+               UNLOCK_MUTEX (&ebsql->priv->lock);
+               return FALSE;
+       }
+
+       if (total)
+               success = cursor_count_total_locked (ebsql, cursor, total, error);
+
+       if (success && position)
+               success = cursor_count_position_locked (ebsql, cursor, position, error);
+
+       if (success)
+               success = book_backend_sqlite_commit_transaction (ebsql, error);
+       else
+               /* The GError is already set. */
+               book_backend_sqlite_rollback_transaction (ebsql, NULL);
+
+
+       UNLOCK_MUTEX (&ebsql->priv->lock);
+
+       /* In the case we're at the end, we just set the position
+        * to be the total + 1
+        */
+       if (success && position && total &&
+           cursor->state.position == EBSQL_CURSOR_ORIGIN_END)
+               *position = *total + 1;
+
+       return success;
+}
+
+/**
+ * e_book_backend_sqlite_cursor_compare_contact:
+ * @ebsql: An #EBookBackendSqlite
+ * @cursor: The #EbSqlCursor
+ * @contact: The #EContact to compare
+ * @matches_sexp: (out) (allow-none): Whether the contact matches the cursor's search expression
+ *
+ * Compares @contact with @cursor and returns whether @contact is less than, equal to, or greater
+ * than @cursor.
+ *
+ * Returns: A value that is less than, equal to, or greater than zero if @contact is found,
+ * respectively, to be less than, to match, or be greater than the current value of @cursor.
+ *
+ * Since: 3.12
+ */
+gint
+e_book_backend_sqlite_cursor_compare_contact (EBookBackendSqlite *ebsql,
+                                             EbSqlCursor        *cursor,
+                                             EContact           *contact,
+                                             gboolean           *matches_sexp)
+{
+       EBookBackendSqlitePrivate *priv;
+       gint i;
+       gint comparison = 0;
+
+       g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), -1);
+       g_return_val_if_fail (E_IS_CONTACT (contact), -1);
+       g_return_val_if_fail (cursor != NULL, -1);
+
+       priv = ebsql->priv;
+
+       if (matches_sexp) {
+               if (cursor->sexp == NULL)
+                       *matches_sexp = TRUE;
+               else
+                       *matches_sexp =
+                               e_book_backend_sexp_match_contact (cursor->sexp, contact);
+       }
+
+       for (i = 0; i < cursor->n_sort_fields && comparison == 0; i++) {
+
+               /* Empty state sorts below any contact value, which means the contact sorts above cursor */
+               if (cursor->state.values[i] == NULL) {
+                       comparison = 1;
+               } else {
+                       const gchar *field_value;
+
+                       field_value = (const gchar *)
+                               e_contact_get_const (contact, cursor->sort_fields[i]);
+
+                       /* Empty contact state sorts below any cursor value */
+                       if (field_value == NULL)
+                               comparison = -1;
+                       else {
+                               gchar *collation_key;
+
+                               /* Check of contact sorts below, equal to, or above the cursor */
+                               collation_key = e_collator_generate_key (priv->collator, field_value, NULL);
+                               comparison = strcmp (collation_key, cursor->state.values[i]);
+                               g_free (collation_key);
+                       }
+               }
+       }
+
+       /* UID tie-breaker */
+       if (comparison == 0) {
+               const gchar *uid;
+
+               uid = (const gchar *)e_contact_get_const (contact, E_CONTACT_UID);
+
+               if (cursor->state.last_uid == NULL)
+                       comparison = 1;
+               else if (uid == NULL)
+                       comparison = -1;
+               else
+                       comparison = strcmp (uid, cursor->state.last_uid);
+       }
+
+       return comparison;
+}
diff --git a/addressbook/libedata-book/e-book-backend-sqlite.h 
b/addressbook/libedata-book/e-book-backend-sqlite.h
new file mode 100644
index 0000000..61535fc
--- /dev/null
+++ b/addressbook/libedata-book/e-book-backend-sqlite.h
@@ -0,0 +1,317 @@
+/*-*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/* e-book-backend-sqlitedb.h
+ *
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * Authors:
+ *     Tristan Van Berkom <tristanvb openismus com>
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#if !defined (__LIBEDATA_BOOK_H_INSIDE__) && !defined (LIBEDATA_BOOK_COMPILATION)
+#error "Only <libedata-book/libedata-book.h> should be included directly."
+#endif
+
+#ifndef E_BOOK_BACKEND_SQLITE_H
+#define E_BOOK_BACKEND_SQLITE_H
+
+#include <libebook-contacts/libebook-contacts.h>
+
+/* Standard GObject macros */
+#define E_TYPE_BOOK_BACKEND_SQLITE \
+       (e_book_backend_sqlite_get_type ())
+#define E_BOOK_BACKEND_SQLITE(obj) \
+       (G_TYPE_CHECK_INSTANCE_CAST \
+       ((obj), E_TYPE_BOOK_BACKEND_SQLITE, EBookBackendSqlite))
+#define E_BOOK_BACKEND_SQLITE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_CAST \
+       ((cls), E_TYPE_BOOK_BACKEND_SQLITE, EBookBackendSqliteClass))
+#define E_IS_BOOK_BACKEND_SQLITE(obj) \
+       (G_TYPE_CHECK_INSTANCE_TYPE \
+       ((obj), E_TYPE_BOOK_BACKEND_SQLITE))
+#define E_IS_BOOK_BACKEND_SQLITE_CLASS(cls) \
+       (G_TYPE_CHECK_CLASS_TYPE \
+       ((cls), E_TYPE_BOOK_BACKEND_SQLITE))
+#define E_BOOK_BACKEND_SQLITE_GET_CLASS(obj) \
+       (G_TYPE_INSTANCE_GET_CLASS \
+       ((obj), E_TYPE_BOOK_BACKEND_SQLITE, EBookBackendSqliteClass))
+
+/**
+ * E_BOOK_SQL_ERROR:
+ *
+ * Error domain for #EBookBackendSqlite operations.
+ *
+ * Since: 3.12
+ **/
+#define E_BOOK_SQL_ERROR (e_book_backend_sqlite_error_quark ())
+
+/**
+ * E_BOOK_SQL_IS_POPULATED_KEY:
+ *
+ * This key can be used with e_book_backend_sqlite_get_key_value().
+ *
+ * In the case of a migration from an older SQLite, any value which
+ * was previously stored with e_book_backend_sqlitedb_set_is_populated()
+ * can be retrieved with this key.
+ *
+ * Since: 3.12
+ **/
+#define E_BOOK_SQL_IS_POPULATED_KEY "eds-reserved-namespace-is-populated"
+
+G_BEGIN_DECLS
+
+typedef struct _EBookBackendSqlite EBookBackendSqlite;
+typedef struct _EBookBackendSqliteClass EBookBackendSqliteClass;
+typedef struct _EBookBackendSqlitePrivate EBookBackendSqlitePrivate;
+
+/**
+ * EBookSqlError:
+ * @E_BOOK_SQL_ERROR_CONSTRAINT: The error occurred due to an explicit constraint
+ * @E_BOOK_SQL_ERROR_CONTACT_NOT_FOUND: A contact was not found by UID (this is different
+ *                                      from a query that returns no results, which is not an error).
+ * @E_BOOK_SQL_ERROR_OTHER: Another error occurred
+ * @E_BOOK_SQL_ERROR_NOT_SUPPORTED: A query was not supported
+ * @E_BOOK_SQL_ERROR_INVALID_QUERY: A query was invalid. This can happen if the sexp could not be parsed
+ *                                  or if a phone number query contained non-phonenumber input.
+ * @E_BOOK_SQL_ERROR_END_OF_LIST: An attempt was made to fetch results past the end of a contact list
+ *
+ * Defines the types of possible errors reported by the #EBookBackendSqlite
+ */
+typedef enum {
+       E_BOOK_SQL_ERROR_CONSTRAINT,
+       E_BOOK_SQL_ERROR_CONTACT_NOT_FOUND,
+       E_BOOK_SQL_ERROR_OTHER,
+       E_BOOK_SQL_ERROR_NOT_SUPPORTED,
+       E_BOOK_SQL_ERROR_INVALID_QUERY,
+       E_BOOK_SQL_ERROR_END_OF_LIST
+} EBookSqlError;
+
+/**
+ * EBookBackendSqlite:
+ *
+ * Contains only private data that should be read and manipulated using the
+ * functions below.
+ *
+ * Since: 3.2
+ **/
+struct _EBookBackendSqlite {
+       GObject parent;
+       EBookBackendSqlitePrivate *priv;
+};
+
+struct _EBookBackendSqliteClass {
+       GObjectClass parent_class;
+};
+
+/**
+ * EbSqlSearchData:
+ *
+ * FIXME: Document me.
+ *
+ * Since: 3.2
+ **/
+typedef struct {
+       gchar *vcard;
+       gchar *uid;
+} EbSqlSearchData;
+
+/**
+ * EbSqlCuror:
+ *
+ * An opaque cursor pointer
+ *
+ * Since: 3.12
+ */
+typedef struct _EbSqlCursor EbSqlCursor;
+
+/**
+ * EbSqlCursorOrigin:
+ * @EBSQL_CURSOR_ORIGIN_CURRENT:  The current cursor position
+ * @EBSQL_CURSOR_ORIGIN_BEGIN:    The beginning of the cursor results.
+ * @EBSQL_CURSOR_ORIGIN_END:      The ending of the cursor results.
+ *
+ * Specifies the start position to in the list of traversed contacts
+ * in calls to e_book_backend_sqlite_cursor_step().
+ *
+ * When an #EbSqlCuror is created, the current position implied by %EBSQL_CURSOR_ORIGIN_CURRENT
+ * is the same as %EBSQL_CURSOR_ORIGIN_BEGIN.
+ *
+ * Since: 3.12
+ */
+typedef enum {
+       EBSQL_CURSOR_ORIGIN_CURRENT = 0,
+       EBSQL_CURSOR_ORIGIN_BEGIN,
+       EBSQL_CURSOR_ORIGIN_END
+} EbSqlCursorOrigin;
+
+/**
+ * EbSqlCursorStepFlags:
+ * @EBSQL_CURSOR_STEP_MOVE:  The cursor position should be modified while stepping
+ * @EBSQL_CURSOR_STEP_FETCH: Traversed contacts should be listed and returned while stepping.
+ *
+ * Defines the behaviour of e_book_backend_sqlite_cursor_step().
+ *
+ * Since: 3.12
+ */
+typedef enum {
+       EBSQL_CURSOR_STEP_MOVE  = (1 << 0),
+       EBSQL_CURSOR_STEP_FETCH = (1 << 1)
+} EbSqlCursorStepFlags;
+
+GType      e_book_backend_sqlite_get_type      (void) G_GNUC_CONST;
+GQuark      e_book_backend_sqlite_error_quark   (void);
+void       e_book_backend_sqlite_search_data_free
+                                               (EbSqlSearchData *s_data);
+
+EBookBackendSqlite *
+           e_book_backend_sqlite_new           (const gchar *path,
+                                                gboolean store_vcard,
+                                                GError **error);
+EBookBackendSqlite *
+           e_book_backend_sqlite_new_full      (const gchar *path,
+                                                const gchar *folderid,
+                                                gboolean store_vcard,
+                                                ESourceBackendSummarySetup *setup,
+                                                GError **error);
+
+gboolean    e_book_backend_sqlite_lock_updates  (EBookBackendSqlite *ebsql,
+                                                GError **error);
+gboolean    e_book_backend_sqlite_unlock_updates(EBookBackendSqlite *ebsql,
+                                                gboolean do_commit,
+                                                GError **error);
+
+gboolean    e_book_backend_sqlite_set_locale    (EBookBackendSqlite *ebsql,
+                                                const gchar *lc_collate,
+                                                GError **error);
+gboolean    e_book_backend_sqlite_get_locale    (EBookBackendSqlite *ebsql,
+                                                gchar **locale_out,
+                                                GError **error);
+
+GHashTable* e_book_backend_sqlite_get_uids_and_rev
+                                               (EBookBackendSqlite *ebsql,
+                                                GError **error);
+
+ECollator  *e_book_backend_sqlite_ref_collator  (EBookBackendSqlite *ebsql);
+
+gboolean    e_book_backend_sqlite_check_summary_query
+                                                (EBookBackendSqlite *ebsql,
+                                                const gchar *query,
+                                                gboolean *with_list_attrs);
+gboolean    e_book_backend_sqlite_check_summary_fields
+                                                (EBookBackendSqlite *ebsql,
+                                                GHashTable *fields_of_interest);
+
+/* Adding / Removing / Searching contacts */
+gboolean    e_book_backend_sqlite_add_contact   (EBookBackendSqlite *ebsql,
+                                                EContact *contact,
+                                                gboolean replace_existing,
+                                                GError **error);
+gboolean    e_book_backend_sqlite_add_contacts  (EBookBackendSqlite *ebsql,
+                                                GSList *contacts,
+                                                gboolean replace_existing,
+                                                GError **error);
+gboolean    e_book_backend_sqlite_remove_contact(EBookBackendSqlite *ebsql,
+                                                const gchar *uid,
+                                                GError **error);
+gboolean    e_book_backend_sqlite_remove_contacts
+                                               (EBookBackendSqlite *ebsql,
+                                                GSList *uids,
+                                                GError **error);
+gboolean    e_book_backend_sqlite_has_contact  (EBookBackendSqlite *ebsql,
+                                                const gchar *uid,
+                                                gboolean *partial_content,
+                                                GError **error);
+EContact *  e_book_backend_sqlite_get_contact  (EBookBackendSqlite *ebsql,
+                                                const gchar *uid,
+                                                GHashTable *fields_of_interest,
+                                                gboolean *with_all_required_fields,
+                                                GError **error);
+gchar *            e_book_backend_sqlite_get_vcard_string
+                                               (EBookBackendSqlite *ebsql,
+                                                const gchar *uid,
+                                                GHashTable *fields_of_interest,
+                                                gboolean *with_all_required_fields,
+                                                GError **error);
+GSList *    e_book_backend_sqlite_search       (EBookBackendSqlite *ebsql,
+                                                const gchar *sexp,
+                                                GHashTable *fields_of_interest,
+                                                gboolean *searched,
+                                                gboolean *with_all_required_fields,
+                                                GError **error);
+GSList *    e_book_backend_sqlite_search_uids  (EBookBackendSqlite *ebsql,
+                                                const gchar *sexp,
+                                                gboolean *searched,
+                                                GError **error);
+
+/* Key / Value convenience API */
+gboolean    e_book_backend_sqlite_get_key_value        (EBookBackendSqlite *ebsql,
+                                                const gchar *key,
+                                                gchar **value,
+                                                GError **error);
+gboolean    e_book_backend_sqlite_set_key_value        (EBookBackendSqlite *ebsql,
+                                                const gchar *key,
+                                                const gchar *value,
+                                                GError **error);
+gboolean    e_book_backend_sqlite_get_key_value_int
+                                                (EBookBackendSqlite *ebsql,
+                                                 const gchar *key,
+                                                 gint *value,
+                                                 GError **error);
+gboolean    e_book_backend_sqlite_set_key_value_int
+                                                (EBookBackendSqlite *ebsql,
+                                                 const gchar *key,
+                                                 gint value,
+                                                 GError **error);
+
+/* Cursor API */
+EbSqlCursor *e_book_backend_sqlite_cursor_new   (EBookBackendSqlite *ebsql,
+                                                const gchar *sexp,
+                                                const EContactField *sort_fields,
+                                                const EBookCursorSortType *sort_types,
+                                                guint n_sort_fields,
+                                                GError **error);
+void        e_book_backend_sqlite_cursor_free   (EBookBackendSqlite *ebsql,
+                                                EbSqlCursor *cursor);
+gint        e_book_backend_sqlite_cursor_step   (EBookBackendSqlite *ebsql,
+                                                EbSqlCursor *cursor,
+                                                EbSqlCursorStepFlags flags,
+                                                EbSqlCursorOrigin origin,
+                                                gint count,
+                                                GSList **results,
+                                                GError **error);
+void        e_book_backend_sqlite_cursor_set_target_alphabetic_index
+                                                (EBookBackendSqlite *ebsql,
+                                                EbSqlCursor *cursor,
+                                                gint index);
+gboolean    e_book_backend_sqlite_cursor_set_sexp
+                                                (EBookBackendSqlite *ebsql,
+                                                EbSqlCursor *cursor,
+                                                const gchar *sexp,
+                                                GError **error);
+gboolean    e_book_backend_sqlite_cursor_calculate
+                                                (EBookBackendSqlite *ebsql,
+                                                EbSqlCursor *cursor,
+                                                gint *total,
+                                                gint *position,
+                                                GError **error);
+gint        e_book_backend_sqlite_cursor_compare_contact
+                                                (EBookBackendSqlite *ebsql,
+                                                EbSqlCursor *cursor,
+                                                EContact *contact,
+                                                gboolean *matches_sexp);
+
+G_END_DECLS
+
+#endif /* E_BOOK_BACKEND_SQLITE_H */



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