[evolution-data-server/sqlite-refactor: 5/9] Adding new EBookBackendSqlite (refactor in progress)
- From: Tristan Van Berkom <tvb src gnome org>
- To: commits-list gnome org
- Cc:
- Subject: [evolution-data-server/sqlite-refactor: 5/9] Adding new EBookBackendSqlite (refactor in progress)
- Date: Thu, 21 Nov 2013 16:35:59 +0000 (UTC)
commit 2389868175a1f5d6d5c18448dec4b4d5dfa61f22
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 | 6272 +++++++++++++++++++++
addressbook/libedata-book/e-book-backend-sqlite.h | 310 +
addressbook/libedata-book/libedata-book.h | 1 +
4 files changed, 6585 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..05518af
--- /dev/null
+++ b/addressbook/libedata-book/e-book-backend-sqlite.c
@@ -0,0 +1,6272 @@
+/*-*- 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 COLUMN_DEFINITION_BYTES 32
+#define GENERATED_QUERY_BYTES 2048
+
+#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)
*/
+ gchar *aux_table; /* Name of auxiliary table for this field, for multivalued fields
only */
+ gchar *aux_table_symbolic; /* Symolic name of auxiliary table used in queries */
+};
+
+struct _EBookBackendSqlitePrivate {
+
+ /* Parameters and settings */
+ gchar *path; /* Full file name of the file we're operating on (used for hash
table entries) */
+ gboolean store_vcard; /* Whether vcards should be stored */
+ gchar *locale; /* The current locale */
+ gchar *folderid; /* The summary table name (configurable, for support of legacy
+ * databases created by EBookBackendSqliteDB) */
+
+ /* Summary configuration */
+ SummaryField *summary_fields;
+ gint n_summary_fields;
+
+ GMutex lock; /* Main API lock */
+ GMutex updates_lock; /* Lock used for calls to e_book_backend_sqlite_lock_updates () */
+ guint32 in_transaction; /* Nested transaction counter */
+ ECollator *collator; /* The ECollator to create sort keys for any sortable fields */
+
+ /* SQLite resources */
+ sqlite3 *db;
+ sqlite3_stmt *insert_stmt; /* Insert statement for main summary table */
+ sqlite3_stmt *replace_stmt; /* Replace statement for main summary table */
+ GHashTable *multi_deletes; /* Delete statement for each auxiliary table */
+ GHashTable *multi_inserts; /* Insert statement for each auxiliary table */
+};
+
+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 void
+ebsql_string_append_vprintf (GString *string,
+ const gchar *fmt,
+ va_list args)
+{
+ gchar *stmt;
+
+ stmt = sqlite3_vmprintf (fmt, args);
+ g_string_append (string, stmt);
+ sqlite3_free (stmt);
+}
+
+static void
+ebsql_string_append_printf (GString *string,
+ const gchar *fmt,
+ ...)
+{
+ va_list args;
+
+ va_start (args, fmt);
+ ebsql_string_append_vprintf (string, fmt, args);
+ va_end (args);
+}
+
+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,
+ const gchar *folderid,
+ EContactField field,
+ 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) {
+ new_field.aux_table = g_strconcat (folderid, "_", dbname, "_list", NULL);
+ new_field.aux_table_symbolic = g_strconcat (dbname, "_list", NULL);
+ }
+
+ 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 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]);
+
+ }
+ }
+}
+
+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 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 GSList *
+summary_field_list_aux_columns (SummaryField *field,
+ const gchar *folderid)
+{
+ GSList *columns = NULL;
+ ColumnInfo *info;
+
+ if (field->type != E_TYPE_CONTACT_ATTR_LIST)
+ return NULL;
+
+ /* The UID column of any auxiliary table is implied and references
+ * the UID in the main summary table
+ */
+
+ /* Normalized value column, for prefix and regular searches */
+ info = g_slice_new0 (ColumnInfo);
+ info->name = g_strdup ("value");
+ info->type = "TEXT";
+ if ((field->index & INDEX_FLAG (PREFIX)) != 0)
+ info->index = g_strconcat ("INDEX_", field->dbname, "_", folderid, NULL);
+ columns = g_slist_prepend (columns, info);
+
+ /* Suffix match column */
+ if ((field->index & INDEX_FLAG (SUFFIX)) != 0) {
+ info = g_slice_new0 (ColumnInfo);
+ info->name = g_strdup ("value_reverse");
+ info->type = "TEXT";
+ info->index = g_strconcat ("RINDEX_", field->dbname, "_", folderid, NULL);
+ columns = g_slist_prepend (columns, info);
+ }
+
+ /* Phone match column */
+ if ((field->index & INDEX_FLAG (PHONE)) != 0) {
+ info = g_slice_new0 (ColumnInfo);
+ info->name = g_strdup ("value_phone");
+ 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
+summary_fields_array_free (SummaryField *fields,
+ gint n_fields)
+{
+ gint i;
+
+ for (i = 0; i < n_fields; i++) {
+ g_free (fields[i].aux_table);
+ g_free (fields[i].aux_table_symbolic);
+ }
+
+ g_free (fields);
+}
+
+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 inline void
+format_column_declaration (GString *string,
+ ColumnInfo *info)
+{
+ 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);
+ }
+}
+
+static inline gboolean
+ensure_column_index (EBookBackendSqlite *ebsql,
+ const gchar *table,
+ ColumnInfo *info,
+ GError **error)
+{
+ if (!info->index)
+ return TRUE;
+
+ return book_backend_sqlite_exec_printf (
+ ebsql, "CREATE INDEX IF NOT EXISTS %Q ON %Q (%s)",
+ NULL, NULL, error,
+ info->index, table, info->name);
+}
+
+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 (COLUMN_DEFINITION_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, ", ");
+
+ format_column_declaration (string, &(main_table_columns[i]));
+ }
+ 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;
+ 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, ebsql->priv->folderid,
+ field, 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;
+
+
+ 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,
+ ebsql->priv->folderid,
+ field, 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);
+ }
+ }
+ }
+
+ g_strfreev (params);
+ }
+
+ 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;
+
+ 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,
+ ebsql->priv->folderid,
+ E_CONTACT_EMAIL, NULL);
+ summary_field->index |= INDEX_FLAG (PREFIX);
+ }
+
+ /* Regardless of whether it was a default summary or not, add the sort
+ * keys to anything less than Schema 8 (as long as those fields are at least
+ * in the summary)
+ */
+ if ((i = summary_field_array_index (summary_fields, E_CONTACT_FULL_NAME)) >= 0) {
+ summary_field = &g_array_index (summary_fields, SummaryField, i);
+ summary_field->index |= INDEX_FLAG (SORT_KEY);
+ }
+
+ if ((i = summary_field_array_index (summary_fields, E_CONTACT_GIVEN_NAME)) >= 0) {
+ summary_field = &g_array_index (summary_fields, SummaryField, i);
+ summary_field->index |= INDEX_FLAG (SORT_KEY);
+ }
+
+ if ((i = summary_field_array_index (summary_fields, E_CONTACT_FAMILY_NAME)) >= 0) {
+ summary_field = &g_array_index (summary_fields, SummaryField, i);
+ summary_field->index |= INDEX_FLAG (SORT_KEY);
+ }
+ }
+ }
+
+ introspect_summary_finish:
+
+ /* Apply the introspected summary fields */
+ if (success) {
+ summary_fields_array_free (ebsql->priv->summary_fields,
+ ebsql->priv->n_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) {
+ gint n_fields;
+ SummaryField *fields;
+
+ /* Properly free the array */
+ n_fields = summary_fields->len;
+ fields = (SummaryField *)g_array_free (summary_fields, FALSE);
+ summary_fields_array_free (fields, n_fields);
+
+ 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, ", ");
+
+ format_column_declaration (string, info);
+ }
+ 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;
+
+ success = ensure_column_index (ebsql, ebsql->priv->folderid, info, error);
+ }
+
+ g_slist_free_full (summary_columns, (GDestroyNotify)column_info_free);
+
+ return success;
+}
+
+static gboolean
+book_backend_sqlite_init_aux_tables (EBookBackendSqlite *ebsql,
+ gint previous_schema,
+ GError **error)
+{
+ GString *string;
+ gboolean success = TRUE;
+ GSList *aux_columns = NULL, *l;
+ gchar *tmp;
+ gint i;
+
+ /* Drop the general 'folder_id_lists' table which was used prior to
+ * version 8 of the schema
+ */
+ if (previous_schema >= 1 && previous_schema < 8) {
+ tmp = g_strconcat (ebsql->priv->folderid, "_lists", NULL);
+ success = book_backend_sqlite_exec_printf (
+ ebsql, "DROP TABLE IF EXISTS %Q",
+ NULL, NULL, error, tmp);
+ g_free (tmp);
+ }
+
+ for (i = 0; success && i < ebsql->priv->n_summary_fields; i++) {
+ SummaryField *field = &(ebsql->priv->summary_fields[i]);
+
+ if (field->type != E_TYPE_CONTACT_ATTR_LIST)
+ continue;
+
+ aux_columns = summary_field_list_aux_columns (field, ebsql->priv->folderid);
+
+ /* Create the auxiliary table for this multi valued field */
+ string = g_string_sized_new (COLUMN_DEFINITION_BYTES * 3 +
+ COLUMN_DEFINITION_BYTES * g_slist_length (aux_columns));
+
+ g_string_append (string, "CREATE TABLE IF NOT EXISTS %Q (uid TEXT NOT NULL REFERENCES %Q
(uid)");
+ for (l = aux_columns; l; l = l->next) {
+ ColumnInfo *info = l->data;
+
+ g_string_append (string, ", ");
+ format_column_declaration (string, info);
+ }
+ g_string_append_c (string, ')');
+
+ success = book_backend_sqlite_exec_printf (
+ ebsql, string->str, NULL, NULL, error,
+ field->aux_table, ebsql->priv->folderid);
+ g_string_free (string, TRUE);
+
+ /* Add indexes to columns in this auxiliary table
+ */
+ for (l = aux_columns; success && l; l = l->next) {
+ ColumnInfo *info = l->data;
+
+ success = ensure_column_index (ebsql, field->aux_table, info, error);
+ }
+
+ g_slist_free_full (aux_columns, (GDestroyNotify)column_info_free);
+ }
+
+ 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);
+
+ stmt = book_backend_sqlite_prepare_multi_delete (ebsql, field, error);
+ if (!stmt)
+ goto preparation_failed;
+
+ g_hash_table_insert (ebsql->priv->multi_deletes,
+ GUINT_TO_POINTER (field->field),
+ 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 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);
+
+ if (success) {
+ /* We can't use e_book_backend_sqlite_set_key_value_int() at this
+ * point as that would hold the access locks
+ */
+ success = book_backend_sqlite_exec_printf (
+ ebsql, "INSERT or REPLACE INTO keys (key, value, folder_id) values (%Q, %Q,
%Q)",
+ NULL, NULL, error,
+ E_BOOK_SQL_IS_POPULATED_KEY,
+ is_populated ? "1" : "0",
+ ebsql->priv->folderid);
+ }
+ }
+
+ /* 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 *stmt_str;
+
+ stmt_str = sqlite3_mprintf ("DELETE FROM %Q WHERE uid = :uid", field->aux_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,
+ SummaryField *field,
+ const gchar *uid,
+ GError **error)
+{
+ sqlite3_stmt *stmt;
+ gint ret;
+
+ stmt = g_hash_table_lookup (ebsql->priv->multi_deletes, GUINT_TO_POINTER (field->field));
+
+ /* 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 *stmt_str;
+ GString *string;
+
+ string = g_string_sized_new (INSERT_MULTI_STMT_BYTES);
+
+ stmt_str = sqlite3_mprintf ("INSERT INTO %Q (uid, value", field->aux_table);
+ g_string_append (string, stmt_str);
+ sqlite3_free (stmt_str);
+
+ if ((field->index & INDEX_FLAG (SUFFIX)) != 0)
+ g_string_append (string, ", value_reverse");
+
+ if ((field->index & INDEX_FLAG (PHONE)) != 0)
+ g_string_append (string, ", value_phone");
+
+ g_string_append (string, ") VALUES (:uid, :value");
+
+ if ((field->index & INDEX_FLAG (SUFFIX)) != 0)
+ g_string_append (string, ", :value_reverse");
+
+ if ((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 *value,
+ const gchar *default_region,
+ GError **error)
+{
+ gchar *normal = e_util_utf8_normalize (value);
+ gchar *str;
+ gint ret, param_idx = 1;
+
+ /* :uid */
+ ret = sqlite3_bind_text (stmt, param_idx++, uid, -1, SQLITE_STATIC);
+
+ if (ret == SQLITE_OK) /* :value */
+ ret = sqlite3_bind_text (stmt, param_idx++, normal, -1, g_free);
+
+ if (ret == SQLITE_OK && (field->index & INDEX_FLAG (SUFFIX)) != 0) {
+ if (normal)
+ str = g_utf8_strreverse (normal, -1);
+ else
+ str = NULL;
+
+ /* :value_reverse */
+ 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);
+
+ /* :value_phone */
+ 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, 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) {
+ gchar *uid = e_contact_get (contact, E_CONTACT_UID);
+ gint i;
+
+ 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_delete (
+ ebsql, field, uid, error);
+
+ if (success)
+ success = book_backend_sqlite_run_multi_insert (
+ ebsql, field, uid, contact, default_region, error);
+ }
+
+ g_free (uid);
+ }
+
+ return success;
+}
+
+/**********************************************************
+ * Querying preflighting *
+ **********************************************************
+ *
+ * The preflight checks are performed before a query might
+ * take place in order to evaluate whether the given query
+ * can be performed with the current summary configuration.
+ *
+ * After preflighting, all relevant data has been extracted
+ * from the search expression and the search expression need
+ * not be parsed again.
+ */
+
+/* Internal extension of the EBookQueryTest enumeration */
+enum {
+ BOOK_QUERY_SUB_AND = E_BOOK_QUERY_LAST,
+ BOOK_QUERY_SUB_OR,
+ BOOK_QUERY_SUB_NOT,
+ BOOK_QUERY_SUB_END
+};
+
+/* Preflighting status
+ *
+ * This enumeration is ordered by severity, higher values
+ * of PreflightStatus take precedence in error reporting.
+ */
+typedef enum {
+ PREFLIGHT_OK = 0,
+ PREFLIGHT_NOT_SUMMARIZED,
+ PREFLIGHT_INVALID,
+ PREFLIGHT_UNSUPPORTED,
+} PreflightStatus;
+
+typedef struct {
+ guint query; /* EBookQueryTest (extended) */
+} QueryElement;
+
+typedef struct {
+ guint query; /* EBookQueryTest (extended) */
+} QueryDelimiter;
+
+typedef struct {
+ guint query; /* EBookQueryTest (extended) */
+
+ EContactField contact_field; /* The EContactField to compare */
+ SummaryField *field; /* The summary field for 'field' */
+ gchar *value; /* The value to compare with */
+ gchar *extra; /* Some queries have an extra parameter (locales for phone queries) */
+} QueryFieldTest;
+
+/* Table used in ESExp parsing */
+static const struct {
+ const gchar *name; /* Name of the symbol to match for this parse phase */
+ gboolean subset; /* TRUE for the subset ESExpIFunc, otherwise the field check ESExpFunc */
+ guint test; /* Extended EBookQueryTest value */
+} check_symbols[] = {
+ { "and", TRUE, BOOK_QUERY_SUB_AND },
+ { "or", TRUE, BOOK_QUERY_SUB_OR },
+ { "not", TRUE, BOOK_QUERY_SUB_NOT },
+
+ { "contains", FALSE, E_BOOK_QUERY_CONTAINS },
+ { "is", FALSE, E_BOOK_QUERY_IS },
+ { "beginswith", FALSE, E_BOOK_QUERY_BEGINS_WITH },
+ { "endswith", FALSE, E_BOOK_QUERY_ENDS_WITH },
+ { "exists", FALSE, E_BOOK_QUERY_IS /* No specify query type for 'exists' */ },
+ { "eqphone", FALSE, E_BOOK_QUERY_EQUALS_PHONE_NUMBER },
+ { "eqphone_national", FALSE, E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER },
+ { "eqphone_short", FALSE, E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER },
+ { "regex_normal", FALSE, E_BOOK_QUERY_REGEX_NORMAL },
+ { "regex_raw", FALSE, E_BOOK_QUERY_REGEX_RAW },
+};
+
+static QueryElement *
+query_delimiter_new (guint query)
+{
+ QueryDelimiter *delim = g_slice_new (QueryDelimiter);
+
+ delim->query = query;
+
+ return (QueryElement *)delim;
+}
+
+static QueryElement *
+query_field_test_new (guint query,
+ EContactField field,
+ const gchar *value,
+ const gchar *extra)
+{
+ QueryFieldTest *test = g_slice_new (QueryFieldTest);
+
+ test->query = query;
+ test->contact_field = field;
+ test->field = NULL;
+ test->value = g_strdup (value);
+ test->extra = g_strdup (extra);
+
+ return (QueryElement *)test;
+}
+
+static void
+query_element_free (QueryElement *element)
+{
+ if (element) {
+
+ if (element->query >= E_BOOK_QUERY_LAST) {
+ QueryDelimiter *delim = (QueryDelimiter *)element;
+
+ g_slice_free (QueryDelimiter, delim);
+ } else {
+ QueryFieldTest *test = (QueryFieldTest *)element;
+
+ g_free (test->value);
+ g_slice_free (QueryFieldTest, test);
+ }
+ }
+}
+
+static void
+query_element_free_array (QueryElement **elements,
+ gint n_elements)
+{
+ gint i;
+
+ for (i = 0; i < n_elements; i++)
+ query_element_free (elements[i]);
+
+ g_free (elements);
+}
+
+static ESExpResult *
+func_check_subset (ESExp *f,
+ gint argc,
+ struct _ESExpTerm **argv,
+ gpointer data)
+{
+ ESExpResult *result, *sub_result;
+ GPtrArray *result_array;
+ QueryElement *element, **sub_elements;
+ guint query_type = GPOINTER_TO_UINT (data);
+ gint i, j, len;
+
+ result_array = g_ptr_array_new_with_free_func ((GDestroyNotify)query_element_free);
+
+ /* The compound query delimiter is the first element in this return array */
+ element = query_delimiter_new (query_type);
+ g_ptr_array_add (result_array, element);
+
+ for (i = 0; i < argc; i++) {
+ sub_result = e_sexp_term_eval (f, argv[i]);
+
+ if (sub_result->type == ESEXP_RES_ARRAY_PTR) {
+ /* Steal the elements directly from the sub result */
+ sub_elements = (QueryElement **)sub_result->value.ptrarray->pdata;
+ len = sub_result->value.ptrarray->len;
+
+ for (j = 0; j < len; j++) {
+ element = sub_elements[j];
+ sub_elements[j] = NULL;
+
+ g_ptr_array_add (result_array, element);
+ }
+ }
+ e_sexp_result_free (f, sub_result);
+ }
+
+ /* The last element in this return array is the sub end delimiter */
+ element = query_delimiter_new (BOOK_QUERY_SUB_END);
+ g_ptr_array_add (result_array, element);
+
+ result = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
+ result->value.ptrarray = result_array;
+
+ return result;
+}
+
+static ESExpResult *
+func_check (struct _ESExp *f,
+ gint argc,
+ struct _ESExpResult **argv,
+ gpointer data)
+{
+ ESExpResult *result;
+ GPtrArray *result_array;
+ QueryElement *element;
+ guint query_type = GPOINTER_TO_UINT (data);
+ EContactField field = 0;
+ const gchar *query_name = NULL;
+ const gchar *query_value = NULL;
+ const gchar *query_extra = NULL;
+
+ if (argc == 2 &&
+ argv[0]->type == ESEXP_RES_STRING &&
+ argv[1]->type == ESEXP_RES_STRING) {
+
+ query_name = argv[0]->value.string;
+ query_value = argv[1]->value.string;
+
+ /* We use E_CONTACT_FIELD_LAST to hold the special case of "x-evolution-any-field" */
+ if (g_strcmp0 (query_name, "x-evolution-any-field") == 0)
+ field = E_CONTACT_FIELD_LAST;
+ else
+ field = e_contact_field_id (query_name);
+
+ } else if (argc == 3 &&
+ argv[0]->type == ESEXP_RES_STRING &&
+ argv[1]->type == ESEXP_RES_STRING &&
+ argv[2]->type == ESEXP_RES_STRING) {
+ query_name = argv[0]->value.string;
+ query_value = argv[1]->value.string;
+ query_extra = argv[2]->value.string;
+
+ field = e_contact_field_id (query_name);
+ }
+
+ /* Return an array with only one element, for lack of a pointer type ESExpResult */
+ result_array = g_ptr_array_new_with_free_func ((GDestroyNotify)query_element_free);
+ element = query_field_test_new (query_type, field, query_value, query_extra);
+ g_ptr_array_add (result_array, element);
+
+ result = e_sexp_result_new (f, ESEXP_RES_ARRAY_PTR);
+ result->value.ptrarray = result_array;
+
+ return result;
+}
+
+/* Initial stage of preflighting, parse the search
+ * expression and generate our array of QueryElements
+ */
+static QueryElement **
+query_preflight_init (const gchar *sexp,
+ gint *n_elements)
+{
+ ESExp *sexp_parser;
+ ESExpResult *result;
+ gint i;
+ gint esexp_error;
+ QueryElement **element_array = NULL;
+
+ g_return_val_if_fail (sexp != NULL, NULL);
+ g_return_val_if_fail (*sexp != '\0', NULL);
+ g_return_val_if_fail (n_elements != NULL, NULL);
+
+ sexp_parser = e_sexp_new ();
+
+ for (i = 0; i < G_N_ELEMENTS (check_symbols); i++) {
+ if (check_symbols[i].subset) {
+ e_sexp_add_ifunction (
+ sexp_parser, 0, check_symbols[i].name,
+ func_check_subset,
+ GUINT_TO_POINTER (check_symbols[i].test));
+ } else {
+ e_sexp_add_function (
+ sexp_parser, 0, check_symbols[i].name,
+ func_check,
+ GUINT_TO_POINTER (check_symbols[i].test));
+ }
+ }
+
+ e_sexp_input_text (sexp_parser, sexp, strlen (sexp));
+ esexp_error = e_sexp_parse (sexp_parser);
+
+ if (esexp_error == -1) {
+ return NULL;
+ }
+
+ result = e_sexp_eval (sexp_parser);
+ if (result && result->type == ESEXP_RES_ARRAY_PTR) {
+ QueryElement **result_array = (QueryElement **)result->value.ptrarray->pdata;
+ gint i, len = result->value.ptrarray->len;
+
+ /* Copy the whole array in one shot */
+ element_array = g_memdup (result_array, sizeof (QueryElement *) * len);
+
+ /* Clear all array members, avoiding the GDestroyNotify */
+ for (i = 0; i < len; i++)
+ result_array[i] = NULL;
+
+ /* Output the length of the array we'll return */
+ *n_elements = len;
+ }
+
+ e_sexp_result_free (sexp_parser, result);
+ e_sexp_unref (sexp_parser);
+
+ return element_array;
+}
+
+/* This pass resolves values on the QueryElements useful
+ * for actually performing the query, furthermore it resolves
+ * whether the query can be performed on the SQLite tables or not.
+ */
+static PreflightStatus
+query_preflight_check (EBookBackendSqlite *ebsql,
+ QueryElement **elements,
+ gint n_elements,
+ GSList **ret_aux_table_ids)
+{
+ PreflightStatus status = PREFLIGHT_OK;
+ GSList *aux_table_ids = NULL;
+ gint i;
+
+ for (i = 0; i < n_elements; i++) {
+ QueryFieldTest *test;
+ EBookQueryTest field_test;
+
+ /* We don't care about the subquery delimiters at this point */
+ if (elements[i]->query >= E_BOOK_QUERY_LAST)
+ continue;
+
+ test = (QueryFieldTest *) elements[i];
+ field_test = (EBookQueryTest)test->query;
+
+ /* Even if the field is not in the summary, we need to
+ * retport unsupported errors if phone number queries are
+ * issued while libphonenumber is unavailable
+ */
+ test->field = summary_field_get (ebsql, test->contact_field);
+ if (!test->field) {
+
+ /* Special case:
+ *
+ * We interpret the special field 'x-evolution-any-field' as
+ * E_CONTACT_FIELD_LAST. If we search for the 'any field'
+ * with the non-value "", then that is considered to be a
+ * summary query.
+ */
+ if (!(test->contact_field == E_CONTACT_FIELD_LAST &&
+ test->value && test->value[0] == '\0')) {
+
+ status = MAX (status, PREFLIGHT_NOT_SUMMARIZED);
+ }
+ }
+
+ switch (field_test) {
+ /* E_BOOK_QUERY_LAST won't happen, just listed here
+ * to complete the switch without adding any 'default' clause
+ */
+ case E_BOOK_QUERY_LAST:
+ case E_BOOK_QUERY_IS:
+ case E_BOOK_QUERY_CONTAINS:
+ case E_BOOK_QUERY_BEGINS_WITH:
+ case E_BOOK_QUERY_ENDS_WITH:
+ case E_BOOK_QUERY_REGEX_NORMAL:
+ /* All OK, some indexes will allow these to run faster,
+ * but nothing to check for invalidity here
+ */
+ break;
+
+ case E_BOOK_QUERY_REGEX_RAW:
+ /* Raw regex queries only supported in the fallback */
+ status = MAX (status, PREFLIGHT_NOT_SUMMARIZED);
+ break;
+
+ case E_BOOK_QUERY_EQUALS_PHONE_NUMBER:
+ case E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER:
+ case E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER:
+
+ if (!e_phone_number_is_supported ()) {
+
+ status = MAX (status, PREFLIGHT_UNSUPPORTED);
+
+ } else {
+ EPhoneNumber *number;
+
+ number = e_phone_number_from_string (test->value, NULL, NULL);
+ if (number == NULL)
+ status = MAX (status, PREFLIGHT_INVALID);
+ else
+ e_phone_number_free (number);
+ }
+
+ /* Additionally, phone number queries can only be performed
+ * on fields which have the special phone index configured
+ */
+ if (test->field &&
+ (test->field->index & INDEX_FLAG (PHONE)) == 0)
+ status = MAX (status, PREFLIGHT_NOT_SUMMARIZED);
+ break;
+ }
+
+ if (ret_aux_table_ids &&
+ test->field &&
+ test->field->type == E_TYPE_CONTACT_ATTR_LIST) {
+ if (!g_slist_find (aux_table_ids, GUINT_TO_POINTER (test->contact_field)))
+ aux_table_ids = g_slist_prepend (aux_table_ids,
+ GUINT_TO_POINTER (test->contact_field));
+ }
+
+ }
+
+ /* If we cannot satisfy this query with the summary, there is no point
+ * to return the allocated list */
+ if (status > PREFLIGHT_OK) {
+ g_slist_free (aux_table_ids);
+ aux_table_ids = NULL;
+ }
+
+ if (ret_aux_table_ids)
+ *ret_aux_table_ids = g_slist_reverse (aux_table_ids);
+
+ return status;
+}
+
+static gboolean
+query_preflight_for_summary_check (EBookBackendSqlite *ebsql,
+ const gchar *sexp)
+{
+ QueryElement **elements;
+ gint n_elements;
+ PreflightStatus status = PREFLIGHT_OK;
+
+ elements = query_preflight_init (sexp, &n_elements);
+ if (!elements)
+ return FALSE;
+
+ status = query_preflight_check (ebsql, elements, n_elements, NULL);
+ query_element_free_array (elements, n_elements);
+
+ return (status == PREFLIGHT_OK);
+}
+
+static QueryElement **
+query_preflight_for_sql_query (EBookBackendSqlite *ebsql,
+ const gchar *sexp,
+ gint *n_query_elements,
+ PreflightStatus *ret_status,
+ GSList **ret_aux_table_ids)
+{
+ GSList *aux_table_ids = NULL;
+ PreflightStatus status = PREFLIGHT_OK;
+ QueryElement **elements;
+ gint n_elements;
+
+ elements = query_preflight_init (sexp, &n_elements);
+ if (!elements) {
+ if (ret_aux_table_ids)
+ ret_aux_table_ids = NULL;
+
+ if (ret_status)
+ *ret_status = PREFLIGHT_INVALID;
+
+ if (n_query_elements)
+ *n_query_elements = 0;
+
+ return NULL;
+ }
+
+ status = query_preflight_check (ebsql,
+ elements,
+ n_elements,
+ &aux_table_ids);
+
+ if (ret_aux_table_ids)
+ *ret_aux_table_ids = aux_table_ids;
+
+ if (ret_status)
+ *ret_status = status;
+
+ if (n_query_elements)
+ *n_query_elements = n_elements;
+
+ return elements;
+}
+
+/**********************************************************
+ * Field Test Generators *
+ **********************************************************/
+#define EBSQL_ESCAPE_SEQUENCE "ESCAPE '^'"
+
+static gchar *
+ebsql_normalize_and_escape (const gchar *value,
+ gboolean *escape_needed)
+{
+ GString *str;
+ size_t len;
+ gchar c;
+ gboolean escape_modifier_needed = FALSE;
+ gchar *normal;
+ const gchar *ptr;
+
+ normal = e_util_utf8_normalize (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 (EBSQL_ESCAPE_SEQUENCE) - 1);
+
+ ptr = normal;
+ while ((c = *ptr++)) {
+ if (c == '\'') {
+ g_string_append_c (str, '\'');
+ } else if (c == '%' || c == '_' || c == '^') {
+ g_string_append_c (str, '^');
+ escape_modifier_needed = TRUE;
+ }
+
+ g_string_append_c (str, c);
+ }
+
+ if (escape_needed)
+ *escape_needed = escape_modifier_needed;
+
+ g_free (normal);
+
+ return g_string_free (str, 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 void
+field_test_query_is (EBookBackendSqlite *ebsql,
+ GString *string,
+ QueryFieldTest *test)
+{
+ SummaryField *field = test->field;
+ gchar *normal;
+
+ normal = e_util_utf8_normalize (test->value);
+
+ if (field->aux_table)
+ ebsql_string_append_printf (string, "%s.value = %Q",
+ field->aux_table_symbolic,
+ normal);
+ else
+ ebsql_string_append_printf (string, "summary.%s = %Q",
+ field->dbname,
+ normal);
+
+ g_free (normal);
+}
+
+static void
+field_test_query_contains (EBookBackendSqlite *ebsql,
+ GString *string,
+ QueryFieldTest *test)
+{
+ SummaryField *field = test->field;
+ gboolean need_escape;
+ gchar *escaped;
+
+ escaped = ebsql_normalize_and_escape (test->value, &need_escape);
+
+ if (need_escape)
+ g_string_append_c (string, '(');
+
+ if (field->aux_table)
+ g_string_append_printf (string, "%s.value LIKE ",
+ field->aux_table_symbolic);
+ else
+ g_string_append_printf (string, "summary.%s LIKE ",
+ field->dbname);
+
+ g_string_append (string, "\'%");
+ g_string_append (string, escaped);
+ g_string_append (string, "%\'");
+
+ if (need_escape)
+ g_string_append (string, EBSQL_ESCAPE_SEQUENCE ")");
+
+ g_free (escaped);
+}
+
+static void
+field_test_query_begins_with (EBookBackendSqlite *ebsql,
+ GString *string,
+ QueryFieldTest *test)
+{
+ SummaryField *field = test->field;
+ gboolean need_escape;
+ gchar *escaped;
+
+ escaped = ebsql_normalize_and_escape (test->value, &need_escape);
+
+ if (need_escape)
+ g_string_append_c (string, '(');
+
+ if (field->aux_table)
+ g_string_append_printf (string, "%s.value LIKE ",
+ field->aux_table_symbolic);
+ else
+ g_string_append_printf (string, "summary.%s LIKE ",
+ field->dbname);
+
+ g_string_append (string, "\'");
+ g_string_append (string, escaped);
+ g_string_append (string, "%\'");
+
+ if (need_escape)
+ g_string_append (string, EBSQL_ESCAPE_SEQUENCE ")");
+
+ g_free (escaped);
+}
+
+static void
+field_test_query_ends_with (EBookBackendSqlite *ebsql,
+ GString *string,
+ QueryFieldTest *test)
+{
+ SummaryField *field = test->field;
+ gboolean need_escape;
+ gchar *escaped;
+
+ escaped = ebsql_normalize_and_escape (test->value, &need_escape);
+
+ if (need_escape)
+ g_string_append_c (string, '(');
+
+ if (field->aux_table)
+ g_string_append_printf (string, "%s.value LIKE ",
+ field->aux_table_symbolic);
+ else
+ g_string_append_printf (string, "summary.%s LIKE ",
+ field->dbname);
+
+ g_string_append (string, "\'%");
+ g_string_append (string, escaped);
+ g_string_append (string, "\'");
+
+ if (need_escape)
+ g_string_append (string, EBSQL_ESCAPE_SEQUENCE ")");
+
+ g_free (escaped);
+}
+
+static void
+field_test_query_eqphone (EBookBackendSqlite *ebsql,
+ GString *string,
+ QueryFieldTest *test)
+{
+ SummaryField *field = test->field;
+ gchar *converted;
+ gint country_code;
+
+ country_code = e_phone_number_get_country_code_for_region (test->extra, NULL);
+ converted = convert_phone (test->value, test->extra);
+
+ if (field->aux_table)
+ ebsql_string_append_printf (string, "(%s.value_phone = %Q COLLATE ixphone_%d)",
+ field->aux_table_symbolic,
+ converted, country_code);
+ else
+ ebsql_string_append_printf (string, "summary.%s_phone = %Q COLLATE ixphone_%d",
+ field->dbname,
+ converted, country_code);
+
+ g_free (converted);
+}
+
+static void
+field_test_query_eqphone_national (EBookBackendSqlite *ebsql,
+ GString *string,
+ QueryFieldTest *test)
+{
+ SummaryField *field = test->field;
+ gchar *converted;
+
+ converted = convert_phone (test->value, test->extra);
+
+ if (field->aux_table)
+ ebsql_string_append_printf (string, "(%s.value_phone = %Q COLLATE ixphone_national)",
+ field->aux_table_symbolic,
+ converted);
+ else
+ ebsql_string_append_printf (string, "summary.%s_phone = %Q COLLATE ixphone_national",
+ field->dbname,
+ converted);
+
+ g_free (converted);
+}
+
+static void
+field_test_query_eqphone_short (EBookBackendSqlite *ebsql,
+ GString *string,
+ QueryFieldTest *test)
+{
+ SummaryField *field = test->field;
+ gboolean need_escape;
+ gchar *digits;
+ gchar *escaped;
+ gint country_code;
+
+ country_code = e_phone_number_get_country_code_for_region (test->extra, NULL);
+ digits = extract_digits (test->value);
+ escaped = ebsql_normalize_and_escape (digits, &need_escape);
+
+ g_string_append (string, "((");
+
+ if (field->aux_table)
+ g_string_append_printf (string, "%s.value_phone LIKE ",
+ field->aux_table_symbolic);
+ else
+ g_string_append_printf (string, "summary.%s_phone LIKE ",
+ field->dbname);
+
+ g_string_append (string, "\'%");
+ g_string_append (string, escaped);
+ g_string_append (string, "\'");
+
+ if (need_escape)
+ g_string_append (string, EBSQL_ESCAPE_SEQUENCE);
+
+ g_string_append_c (string, ')');
+
+ if (field->aux_table)
+ g_string_append_printf (string,
+ " AND (%s.value_phone LIKE '|%%' OR %s.value_phone LIKE '+%d|%%')",
+ field->aux_table_symbolic,
+ field->aux_table_symbolic,
+ country_code);
+ else
+ g_string_append_printf (string,
+ " AND (summary.%s_phone LIKE '|%%' OR summary.%s_phone LIKE
'+%d|%%')",
+ field->dbname, field->dbname,
+ country_code);
+ g_string_append_c (string, ')');
+
+ g_free (escaped);
+ g_free (digits);
+}
+
+static void
+field_test_query_regex_normal (EBookBackendSqlite *ebsql,
+ GString *string,
+ QueryFieldTest *test)
+{
+ SummaryField *field = test->field;
+ gchar *normal;
+
+ normal = e_util_utf8_normalize (test->value);
+
+ if (field->aux_table)
+ ebsql_string_append_printf (string, "%s.value REGEXP %Q",
+ field->aux_table_symbolic,
+ normal);
+ else
+ ebsql_string_append_printf (string, "summary.%s REGEXP %Q",
+ field->dbname,
+ normal);
+
+ g_free (normal);
+}
+
+/**********************************************************
+ * Querying Contacts *
+ **********************************************************/
+
+/* The various search types indicate what should be fetched
+ */
+typedef enum {
+ SEARCH_UID_AND_VCARD, /* Get a list of EbSqlSearchData */
+ SEARCH_UID_AND_REV, /* Get a list of EbSqlSearchData, with shallow vcards only containing UID & REV
*/
+ SEARCH_UID, /* Get a list of UID strings */
+} SearchType;
+
+
+/* Lookup table for field test generators per EBookQueryTest,
+ *
+ * THIS MUST stay in line with the EBookQueryTest definition.
+ */
+typedef void (* GenerateFieldTest) (EBookBackendSqlite *ebsql,
+ GString *string,
+ QueryFieldTest *test);
+
+static const GenerateFieldTest field_test_func_table[] = {
+ field_test_query_is, /* E_BOOK_QUERY_IS */
+ field_test_query_contains, /* E_BOOK_QUERY_CONTAINS */
+ field_test_query_begins_with, /* E_BOOK_QUERY_BEGINS_WITH */
+ field_test_query_ends_with, /* E_BOOK_QUERY_ENDS_WITH */
+ field_test_query_eqphone, /* E_BOOK_QUERY_EQUALS_PHONE_NUMBER */
+ field_test_query_eqphone_national, /* E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER */
+ field_test_query_eqphone_short, /* E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER */
+ field_test_query_regex_normal, /* E_BOOK_QUERY_REGEX_NORMAL */
+ NULL/* Unsupported */, /* E_BOOK_QUERY_REGEX_RAW */
+};
+
+/* A small API to track the current sub-query context.
+ *
+ * I.e. sub contexts can be OR, AND, or NOT, in which
+ * field tests or other sub contexts are nested.
+ */
+typedef GQueue SubQueryContext;
+
+typedef struct {
+ guint sub_type; /* The type of this sub context */
+ guint count; /* The number of field tests so far in this context */
+} SubQueryData;
+
+#define sub_query_context_new g_queue_new
+#define sub_query_context_free(ctx) g_queue_free (ctx)
+
+static inline void
+sub_query_context_push (SubQueryContext *ctx,
+ guint sub_type)
+{
+ SubQueryData *data;
+
+ data = g_slice_new (SubQueryData);
+ data->sub_type = sub_type;
+ data->count = 0;
+
+ g_queue_push_tail (ctx, data);
+}
+
+static inline void
+sub_query_context_pop (SubQueryContext *ctx)
+{
+ SubQueryData *data;
+
+ data = g_queue_pop_tail (ctx);
+ g_slice_free (SubQueryData, data);
+}
+
+static inline guint
+sub_query_context_peek_type (SubQueryContext *ctx)
+{
+ SubQueryData *data;
+
+ data = g_queue_peek_tail (ctx);
+
+ return data->sub_type;
+}
+
+/* Returns the context field test count before incrementing */
+static inline guint
+sub_query_context_increment (SubQueryContext *ctx)
+{
+ SubQueryData *data;
+
+ data = g_queue_peek_tail (ctx);
+
+ if (data) {
+ data->count++;
+
+ return (data->count - 1);
+ }
+
+ /* If we're not in a sub context, just return 0 */
+ return 0;
+}
+
+/*
+ * Used for any search which does not specify a search expression
+ */
+static gboolean
+book_backend_sqlite_list_all_contacts (EBookBackendSqlite *ebsql,
+ SearchType search_type,
+ GSList **return_data,
+ GError **error)
+{
+ gboolean success = FALSE;
+
+ switch (search_type) {
+ case SEARCH_UID_AND_VCARD:
+
+ if (!ebsql->priv->store_vcard)
+ g_set_error (error,
+ E_BOOK_SQL_ERROR,
+ E_BOOK_SQL_ERROR_OTHER,
+ _("Full vcards are not stored in cache, "
+ "vcards cannot be returned"));
+ else
+ success = book_backend_sqlite_exec_printf (
+ ebsql, "SELECT summary.uid, summary.vcard FROM %Q AS summary",
+ collect_full_results_cb, return_data, error,
+ ebsql->priv->folderid);
+ break;
+
+ case SEARCH_UID_AND_REV:
+
+ success = book_backend_sqlite_exec_printf (
+ ebsql, "SELECT summary.uid, summary.Rev FROM %Q AS summary",
+ collect_lean_results_cb, return_data, error,
+ ebsql->priv->folderid);
+ break;
+
+ case SEARCH_UID:
+
+ success = book_backend_sqlite_exec_printf (
+ ebsql, "SELECT summary.uid FROM %Q AS summary",
+ collect_uid_results_cb, return_data, error,
+ ebsql->priv->folderid);
+ break;
+ }
+
+ return success;
+}
+
+static gboolean
+book_backend_sqlite_search_fallback (EBookBackendSqlite *ebsql,
+ const gchar *sexp,
+ SearchType search_type,
+ GSList **return_data,
+ GError **error)
+{
+ GSList *all = NULL, *ret_list = NULL, *l;
+ EBookBackendSExp *bsexp = NULL;
+
+ if (!book_backend_sqlite_list_all_contacts (ebsql,
+ SEARCH_UID_AND_VCARD,
+ &all, error))
+ return FALSE;
+
+ bsexp = e_book_backend_sexp_new (sexp);
+
+ for (l = all; l != NULL; l = g_slist_next (l)) {
+ EbSqlSearchData *search_data = (EbSqlSearchData *) l->data;
+
+ if (e_book_backend_sexp_match_vcard (bsexp, search_data->vcard)) {
+
+ switch (search_type) {
+ case SEARCH_UID_AND_VCARD:
+ case SEARCH_UID_AND_REV:
+ ret_list = g_slist_prepend (ret_list, search_data);
+ break;
+ case SEARCH_UID:
+ ret_list = g_slist_prepend (ret_list, g_strdup (search_data->uid));
+ e_book_backend_sqlite_search_data_free (search_data);
+ break;
+ }
+
+ } else {
+ e_book_backend_sqlite_search_data_free (search_data);
+ }
+ }
+
+ g_object_unref (bsexp);
+ g_slist_free (all);
+
+ /* Output the list and return successfully */
+ *return_data = ret_list;
+
+ return TRUE;
+}
+
+static void
+book_backend_sqlite_generate_select (EBookBackendSqlite *ebsql,
+ GString *string,
+ SearchType search_type,
+ GSList *aux_table_ids)
+{
+ GSList *l;
+
+ g_string_append (string, "SELECT ");
+ if (aux_table_ids)
+ g_string_append (string, "DISTINCT ");
+
+ switch (search_type) {
+ case SEARCH_UID_AND_VCARD:
+ g_string_append (string, "summary.uid, summary.vcard ");
+ break;
+ case SEARCH_UID_AND_REV:
+ g_string_append (string, "summary.uid, summary.Rev ");
+ break;
+ case SEARCH_UID:
+ g_string_append (string, "summary.uid ");
+ break;
+ }
+
+ ebsql_string_append_printf (string, "FROM %Q AS summary ",
+ ebsql->priv->folderid);
+
+ for (l = aux_table_ids; l; l = l->next) {
+ EContactField contact_field = GPOINTER_TO_UINT (l->data);
+ SummaryField *field = summary_field_get (ebsql, contact_field);
+
+ ebsql_string_append_printf (string,
+ "LEFT OUTER JOIN %Q AS %s ON summary.uid = %s.uid ",
+ field->aux_table,
+ field->aux_table_symbolic,
+ field->aux_table_symbolic);
+ }
+}
+
+static void
+book_backend_sqlite_generate_search_expression (EBookBackendSqlite *ebsql,
+ GString *string,
+ QueryElement **elements,
+ gint n_elements)
+{
+ SubQueryContext *ctx;
+ QueryDelimiter *delim;
+ QueryFieldTest *test;
+ gint i;
+
+ ctx = sub_query_context_new();
+
+ for (i = 0; i < n_elements; i++) {
+ GenerateFieldTest generate_test_func = NULL;
+
+ /* Seperate field tests with the appropriate grouping */
+ if (elements[i]->query != BOOK_QUERY_SUB_END &&
+ sub_query_context_increment (ctx) > 0) {
+ guint delim_type = sub_query_context_peek_type (ctx);
+
+ switch (delim_type) {
+ case BOOK_QUERY_SUB_AND:
+
+ g_string_append (string, " AND ");
+ break;
+
+ case BOOK_QUERY_SUB_OR:
+
+ g_string_append (string, " OR ");
+ break;
+
+ case BOOK_QUERY_SUB_NOT:
+ /* NOT supported ... */
+ case BOOK_QUERY_SUB_END:
+ default:
+ g_warn_if_reached ();
+ }
+ }
+
+ if (elements[i]->query >= E_BOOK_QUERY_LAST) {
+ delim = (QueryDelimiter *)elements[i];
+
+ switch (delim->query) {
+ case BOOK_QUERY_SUB_AND:
+ case BOOK_QUERY_SUB_OR:
+
+ /* Open a grouped statement and push the context */
+ sub_query_context_push (ctx, delim->query);
+ g_string_append_c (string, '(');
+ break;
+
+ case BOOK_QUERY_SUB_NOT:
+ /* NOT supported ... */
+ g_warn_if_reached ();
+ break;
+
+ case BOOK_QUERY_SUB_END:
+ /* Close a grouped statement and pop the context */
+ g_string_append_c (string, ')');
+ sub_query_context_pop (ctx);
+ break;
+ default:
+ g_warn_if_reached ();
+ }
+
+ continue;
+ }
+
+ /* Find the appropriate field test generator */
+ test = (QueryFieldTest *)elements[i];
+ if (test->query < G_N_ELEMENTS (field_test_func_table))
+ generate_test_func = field_test_func_table[test->query];
+
+ if (!generate_test_func) {
+ g_warning ("Unsupported query encountered too late");
+ continue;
+ }
+
+ /* Generate the field test */
+ generate_test_func (ebsql, string, test);
+ }
+
+ sub_query_context_free (ctx);
+}
+
+
+static gboolean
+book_backend_sqlite_search_summary (EBookBackendSqlite *ebsql,
+ QueryElement **elements,
+ gint n_elements,
+ GSList *aux_table_ids,
+ SearchType search_type,
+ GSList **return_data,
+ GError **error)
+{
+ GString *string;
+ EbSqlSqliteCallback callback = NULL;
+ gboolean success;
+
+ /* We might calculate a reasonable estimation of bytes
+ * during the preflight checks */
+ string = g_string_sized_new (GENERATED_QUERY_BYTES);
+
+ /* Generate the SELECT portion of the query, this
+ * will setup two important things:
+ *
+ * o Take care of listing which data should be
+ * returned depending on the SearchType
+ *
+ * o Make available all the tables and symbols which
+ * are needed by the SummaryFields involved in this
+ * query.
+ */
+ book_backend_sqlite_generate_select (ebsql, string,
+ search_type,
+ aux_table_ids);
+
+ g_string_append (string, "WHERE ");
+
+ /*
+ * Now generate the search expression
+ */
+ book_backend_sqlite_generate_search_expression (ebsql,
+ string,
+ elements,
+ n_elements);
+
+ switch (search_type) {
+ case SEARCH_UID_AND_VCARD:
+ callback = collect_full_results_cb;
+ break;
+ case SEARCH_UID_AND_REV:
+ callback = collect_lean_results_cb;
+ break;
+ case SEARCH_UID:
+ callback = collect_uid_results_cb;
+ break;
+ }
+
+ success = book_backend_sqlite_exec (ebsql, string->str,
+ callback, return_data,
+ error);
+ g_string_free (string, TRUE);
+
+ return success;
+}
+
+/* book_backend_sqlite_search_query:
+ * @ebsql: An EBookBackendSqlite
+ * @sexp: The search expression, or NULL for all contacts
+ * @search_type: Indicates what kind of data should be returned
+ * @return_data: A list of data fetched from the DB, as specified by 'search_type'
+ * @error: Location to store any error which may have occurred
+ *
+ * This is the main common entry point for querying contacts.
+ *
+ * If the query cannot be satisfied with the summary, then
+ * a fallback will automatically be used.
+ *
+ *
+ */
+static gboolean
+book_backend_sqlite_search_query (EBookBackendSqlite *ebsql,
+ const gchar *sexp,
+ SearchType search_type,
+ GSList **return_data,
+ GError **error)
+{
+ PreflightStatus status = PREFLIGHT_OK;
+ QueryElement **elements;
+ gint n_elements;
+ gboolean success = FALSE;
+ GSList *aux_table_ids = NULL;
+
+ /* First handle the case where no search expression is provided */
+ if (sexp == NULL || *sexp == '\0')
+ return book_backend_sqlite_list_all_contacts (ebsql, search_type, return_data, error);
+
+ /* Now start with the query preflighting */
+ elements = query_preflight_for_sql_query (ebsql, sexp, &n_elements,
+ &status, &aux_table_ids);
+
+ /* Depending on the preflighting status, search the summary,
+ * fallback on a full search, or report an error.
+ */
+ switch (status) {
+ case PREFLIGHT_OK:
+ /* Query expression can be satisfied with our summary,
+ * let's perform the real query now.
+ */
+ success = book_backend_sqlite_search_summary (ebsql,
+ elements,
+ n_elements,
+ aux_table_ids,
+ search_type,
+ return_data,
+ error);
+ break;
+
+ case PREFLIGHT_NOT_SUMMARIZED:
+ /* Query expression accesses fields outside the summary,
+ * let's perform our slower fallback routine
+ */
+ success = book_backend_sqlite_search_fallback (ebsql, sexp,
+ search_type,
+ return_data,
+ error);
+ break;
+
+ case PREFLIGHT_INVALID:
+ g_set_error (error,
+ E_BOOK_SQL_ERROR,
+ E_BOOK_SQL_ERROR_INVALID_QUERY,
+ _("Invalid Query"));
+ break;
+
+ case PREFLIGHT_UNSUPPORTED:
+ g_set_error (error,
+ E_BOOK_SQL_ERROR,
+ E_BOOK_SQL_ERROR_NOT_SUPPORTED,
+ _("Query contained unsupported elements"));
+ break;
+ }
+
+ g_slist_free (aux_table_ids);
+ query_element_free_array (elements, n_elements);
+
+ return success;
+}
+
+/**********************************************************
+ * 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);
+
+ summary_fields_array_free (priv->summary_fields,
+ priv->n_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,
+ 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->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, previous_schema, 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;
+ 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, folderid, default_summary_fields[i], NULL);
+
+ /* Add the default index flags */
+ summary_fields_add_indexes (
+ summary_fields,
+ default_indexed_fields,
+ default_index_types,
+ G_N_ELEMENTS (default_indexed_fields));
+
+ ebsql = e_book_backend_sqlite_new_internal (
+ path, folderid,
+ store_vcard,
+ (SummaryField *) summary_fields->data,
+ summary_fields->len,
+ 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 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, folderid, E_CONTACT_UID, error);
+ summary_field_append (summary_fields, folderid, E_CONTACT_REV, error);
+
+ for (i = 0; i < n_fields; i++) {
+ if (!summary_field_append (summary_fields, folderid, fields[i], error)) {
+ had_error = TRUE;
+ break;
+ }
+ }
+
+ if (had_error) {
+ gint n_sfields;
+ SummaryField *sfields;
+
+ /* Properly free the array */
+ n_sfields = summary_fields->len;
+ sfields = (SummaryField *)g_array_free (summary_fields, FALSE);
+ summary_fields_array_free (sfields, n_sfields);
+
+ 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);
+
+ ebsql = e_book_backend_sqlite_new_internal (
+ path, folderid,
+ store_vcard,
+ (SummaryField *) summary_fields->data,
+ summary_fields->len,
+ 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;
+ gint i;
+ 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 data from the auxiliary tables first */
+ for (i = 0; success && i < ebsql->priv->n_summary_fields; i++) {
+ SummaryField *field = &(ebsql->priv->summary_fields[i]);
+
+ if (field->type != E_TYPE_CONTACT_ATTR_LIST)
+ continue;
+
+ stmt = generate_delete_stmt (field->aux_table, uids);
+ success = book_backend_sqlite_exec (ebsql, stmt, NULL, NULL, error);
+ g_free (stmt);
+ }
+
+ /* Now delete the entry from the main contacts */
+ 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
+ **/
+gboolean
+e_book_backend_sqlite_get_contact (EBookBackendSqlite *ebsql,
+ const gchar *uid,
+ gboolean meta_contact,
+ EContact **contact,
+ GError **error)
+{
+ gboolean success = FALSE;
+ gchar *vcard = NULL;
+
+ 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 (contact != NULL && *contact == NULL, FALSE);
+
+ success = e_book_backend_sqlite_get_vcard_string (ebsql,
+ uid,
+ meta_contact,
+ &vcard,
+ error);
+
+ if (success && vcard) {
+ *contact = e_contact_new_from_vcard_with_uid (vcard, uid);
+ g_free (vcard);
+ }
+
+ return success;
+}
+
+/**
+ * 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
+ */
+gboolean
+e_book_backend_sqlite_get_vcard_string (EBookBackendSqlite *ebsql,
+ const gchar *uid,
+ gboolean meta_contact,
+ gchar **ret_vcard,
+ GError **error)
+{
+ gboolean success = FALSE;
+ gchar *vcard_str = NULL;
+
+ 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 (ret_vcard != NULL && *ret_vcard == NULL, FALSE);
+
+ LOCK_MUTEX (&ebsql->priv->lock);
+
+ /* Try constructing contacts from only UID/REV first if that's requested */
+ if (meta_contact) {
+ GSList *vcards = NULL;
+
+ success = book_backend_sqlite_exec_printf (
+ ebsql, "SELECT summary.uid, summary.Rev FROM %Q AS summary WHERE uid = %Q",
+ collect_lean_results_cb, &vcards, error,
+ ebsql->priv->folderid, uid);
+
+ if (vcards) {
+ EbSqlSearchData *search_data = (EbSqlSearchData *) vcards->data;
+
+ vcard_str = search_data->vcard;
+ search_data->vcard = NULL;
+
+ g_slist_free_full (vcards, (GDestroyNotify)e_book_backend_sqlite_search_data_free);
+ vcards = NULL;
+ }
+
+ } else if (ebsql->priv->store_vcard) {
+ success = book_backend_sqlite_exec_printf (
+ ebsql, "SELECT vcard FROM %Q WHERE uid = %Q",
+ get_string_cb, &vcard_str, error, ebsql->priv->folderid, uid);
+ } else {
+ g_set_error (error,
+ E_BOOK_SQL_ERROR,
+ E_BOOK_SQL_ERROR_OTHER,
+ _("Full vcards are not stored in cache, "
+ "vcards cannot be returned"));
+ }
+
+ UNLOCK_MUTEX (&ebsql->priv->lock);
+
+ *ret_vcard = vcard_str;
+
+ if (success && !vcard_str) {
+ /* Odd, but true */
+ g_set_error (error,
+ E_BOOK_SQL_ERROR,
+ E_BOOK_SQL_ERROR_CONTACT_NOT_FOUND,
+ _("Contact '%s' not found"), uid);
+ success = FALSE;
+ }
+
+ return success;
+}
+
+/**
+ * e_book_backend_sqlite_check_summary_query:
+ * @ebsql: an #EBookBackendSqlite
+ * @sexp: the search expression to check
+ *
+ * Checks whether @sexp contains only checks for the summary fields
+ * configured in @ebsql, and thus can be executed without falling
+ * back to a full search.
+ *
+ * Since: 3.8
+ **/
+gboolean
+e_book_backend_sqlite_check_summary_query (EBookBackendSqlite *ebsql,
+ const gchar *sexp)
+{
+ gboolean is_summary;
+
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+
+ LOCK_MUTEX (&ebsql->priv->lock);
+ is_summary = query_preflight_for_summary_check (ebsql, sexp);
+ 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
+ **/
+gboolean
+e_book_backend_sqlite_search (EBookBackendSqlite *ebsql,
+ const gchar *sexp,
+ gboolean meta_contacts,
+ GSList **ret_list,
+ GError **error)
+{
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+ g_return_val_if_fail (ret_list != NULL && *ret_list == NULL, FALSE);
+
+ LOCK_MUTEX (&ebsql->priv->lock);
+ success = book_backend_sqlite_search_query (ebsql, sexp,
+ meta_contacts ?
+ SEARCH_UID_AND_REV : SEARCH_UID_AND_VCARD,
+ ret_list,
+ error);
+ UNLOCK_MUTEX (&ebsql->priv->lock);
+
+ return success;
+}
+
+/**
+ * e_book_backend_sqlite_search_uids:
+ *
+ * FIXME: Document me.
+ *
+ * Since: 3.2
+ **/
+gboolean
+e_book_backend_sqlite_search_uids (EBookBackendSqlite *ebsql,
+ const gchar *sexp,
+ GSList **ret_list,
+ GError **error)
+{
+ gboolean success;
+
+ g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITE (ebsql), FALSE);
+ g_return_val_if_fail (ret_list != NULL && *ret_list == NULL, FALSE);
+
+ LOCK_MUTEX (&ebsql->priv->lock);
+ success = book_backend_sqlite_search_query (ebsql, sexp,
+ SEARCH_UID,
+ ret_list,
+ error);
+ UNLOCK_MUTEX (&ebsql->priv->lock);
+
+ return success;
+}
+
+/**
+ * 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)
+{
+ gchar *stmt;
+ gchar *count_stmt;
+
+ g_free (cursor->select_vcards);
+ g_free (cursor->select_count);
+ g_free (cursor->query);
+ g_clear_object (&(cursor->sexp));
+
+ /* XXX Need to restore once we get the query builder finished */
+#if 0
+ 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);
+ }
+#endif
+ 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);
+
+ /* XXX Need to restore once we get the query builder finished */
+#if 0
+ 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;
+ }
+#endif
+
+ 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,
+ 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);
+
+ 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)
+{
+ EbSqlCursor *cursor;
+ 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;
+
+ LOCK_MUTEX (&ebsql->priv->lock);
+
+ /* We only support cursors for summary fields in the query */
+ if (sexp && !query_preflight_for_summary_check (ebsql, sexp)) {
+ g_set_error (error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_INVALID_QUERY,
+ _("Only summary queries are supported by EbSqlCursor"));
+ UNLOCK_MUTEX (&ebsql->priv->lock);
+ 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"));
+ UNLOCK_MUTEX (&ebsql->priv->lock);
+ return NULL;
+ }
+
+ /* We only support summarized sort keys which are not multi value fields */
+ for (i = 0; i < n_sort_fields; i++) {
+ SummaryField *field;
+
+ field = summary_field_get (ebsql, sort_fields[i]);
+
+ if (field == NULL) {
+ 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"));
+ UNLOCK_MUTEX (&ebsql->priv->lock);
+ return NULL;
+ }
+
+ if ((field->index & INDEX_FLAG (SORT_KEY)) == 0) {
+ g_set_error (error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_INVALID_QUERY,
+ _("Cannot sort by a field which is not configured with
E_BOOK_INDEX_SORT_KEY"));
+ UNLOCK_MUTEX (&ebsql->priv->lock);
+ return NULL;
+ }
+
+ if (field->type == E_TYPE_CONTACT_ATTR_LIST) {
+ g_set_error (error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_INVALID_QUERY,
+ _("Cannot sort by a field which may have multiple values"));
+ UNLOCK_MUTEX (&ebsql->priv->lock);
+ return NULL;
+ }
+ }
+
+ cursor = ebsql_cursor_new (ebsql, sexp, sort_fields, sort_types, n_sort_fields);
+
+ UNLOCK_MUTEX (&ebsql->priv->lock);
+
+ return cursor;
+}
+
+/**
+ * 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)
+{
+ 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;
+
+ LOCK_MUTEX (&ebsql->priv->lock);
+
+ /* We only support cursors for summary fields in the query */
+ if (sexp && !query_preflight_for_summary_check (ebsql, sexp)) {
+ g_set_error (error, E_BOOK_SQL_ERROR, E_BOOK_SQL_ERROR_INVALID_QUERY,
+ _("Only summary queries are supported by EbSqlCursor"));
+ UNLOCK_MUTEX (&ebsql->priv->lock);
+ return FALSE;
+ }
+
+ ebsql_cursor_setup_query (ebsql, cursor, sexp);
+
+ UNLOCK_MUTEX (&ebsql->priv->lock);
+
+ 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..f318a4d
--- /dev/null
+++ b/addressbook/libedata-book/e-book-backend-sqlite.h
@@ -0,0 +1,310 @@
+/*-*- 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);
+
+/* 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 *exists,
+ GError **error);
+gboolean e_book_backend_sqlite_get_contact (EBookBackendSqlite *ebsql,
+ const gchar *uid,
+ gboolean meta_contact,
+ EContact **ret_contact,
+ GError **error);
+gboolean e_book_backend_sqlite_get_vcard_string
+ (EBookBackendSqlite *ebsql,
+ const gchar *uid,
+ gboolean meta_contact,
+ gchar **ret_vcard,
+ GError **error);
+gboolean e_book_backend_sqlite_search (EBookBackendSqlite *ebsql,
+ const gchar *sexp,
+ gboolean meta_contacts,
+ GSList **ret_list,
+ GError **error);
+gboolean e_book_backend_sqlite_search_uids (EBookBackendSqlite *ebsql,
+ const gchar *sexp,
+ GSList **ret_list,
+ 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 */
diff --git a/addressbook/libedata-book/libedata-book.h b/addressbook/libedata-book/libedata-book.h
index d6f4251..d91acea 100644
--- a/addressbook/libedata-book/libedata-book.h
+++ b/addressbook/libedata-book/libedata-book.h
@@ -29,6 +29,7 @@
#include <libedata-book/e-book-backend-factory.h>
#include <libedata-book/e-book-backend-sexp.h>
#include <libedata-book/e-book-backend-sqlitedb.h>
+#include <libedata-book/e-book-backend-sqlite.h>
#include <libedata-book/e-book-backend-summary.h>
#include <libedata-book/e-book-backend.h>
#include <libedata-book/e-data-book-cursor.h>
[
Date Prev][
Date Next] [
Thread Prev][
Thread Next]
[
Thread Index]
[
Date Index]
[
Author Index]