#define WRITER_LOCK(ebssdb) g_rw_lock_writer_lock (&ebsdb->priv->rwlock)
#define WRITER_UNLOCK(ebssdb) g_rw_lock_writer_unlock (&ebsdb->priv->rwlock)
+typedef enum {
+ INDEX_PREFIX = (1 << 0),
+ INDEX_SUFFIX = (1 << 1)
+} IndexFlags;
+
+typedef struct {
+ 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 */
+ IndexFlags index; /* Whether this summary field should have an index in the SQLite DB */
+} SummaryField;
+
struct _EBookBackendSqliteDBPrivate {
sqlite3 *db;
gchar *path;
GMutex in_transaction_lock;
guint32 in_transaction;
+
+ SummaryField *summary_fields;
+ gint n_summary_fields;
+ guint have_attr_list : 1;
+ guint have_attr_list_prefix : 1;
+ guint have_attr_list_suffix : 1;
};
G_DEFINE_TYPE (EBookBackendSqliteDB, e_book_backend_sqlitedb, G_TYPE_OBJECT)
static GHashTable *db_connections = NULL;
static GMutex dbcon_lock;
-typedef struct {
- EContactField field; /* The EContact field */
- GType fundamental_type; /* The fundamental type (string or gint) */
- const gchar *dbname; /* The key for this field in the sqlite3 table */
-} SummeryField;
-
-static SummeryField summary_fields[] = {
- { E_CONTACT_UID, G_TYPE_STRING, "uid" },
- { E_CONTACT_REV, G_TYPE_STRING, "rev" },
- { E_CONTACT_FILE_AS, G_TYPE_STRING, "file_as" },
- { E_CONTACT_NICKNAME, G_TYPE_STRING, "nickname" },
- { E_CONTACT_FULL_NAME, G_TYPE_STRING, "full_name" },
- { E_CONTACT_GIVEN_NAME, G_TYPE_STRING, "given_name" },
- { E_CONTACT_FAMILY_NAME, G_TYPE_STRING, "family_name" },
- { E_CONTACT_EMAIL_1, G_TYPE_STRING, "email_1" },
- { E_CONTACT_EMAIL_2, G_TYPE_STRING, "email_2" },
- { E_CONTACT_EMAIL_3, G_TYPE_STRING, "email_3" },
- { E_CONTACT_EMAIL_4, G_TYPE_STRING, "email_4" },
- { E_CONTACT_IS_LIST, G_TYPE_BOOLEAN, "is_list" },
- { E_CONTACT_LIST_SHOW_ADDRESSES, G_TYPE_BOOLEAN, "list_show_addresses" },
- { E_CONTACT_WANTS_HTML, G_TYPE_BOOLEAN, "wants_html" }
+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
+};
+
+static EBookIndexType default_index_types[] = {
+ E_BOOK_INDEX_PREFIX,
+ E_BOOK_INDEX_PREFIX
};
static const gchar *
-summary_dbname_from_field (EContactField field)
+summary_dbname_from_field (EBookBackendSqliteDB *ebsdb,
+ EContactField field)
{
gint i;
- for (i = 0; i < G_N_ELEMENTS (summary_fields); i++) {
- if (summary_fields[i].field == field)
- return summary_fields[i].dbname;
+ for (i = 0; i < ebsdb->priv->n_summary_fields; i++) {
+ if (ebsdb->priv->summary_fields[i].field == field)
+ return ebsdb->priv->summary_fields[i].dbname;
}
return NULL;
}
static gint
-store_data_to_vcard (gpointer ref, gint ncol, gchar **cols, gchar **name);
+summary_index_from_field_name (EBookBackendSqliteDB *ebsdb,
+ const gchar *field_name)
+{
+ gint i;
+ EContactField field;
+
+ field = e_contact_field_id (field_name);
+
+ for (i = 0; i < ebsdb->priv->n_summary_fields; i++) {
+ if (ebsdb->priv->summary_fields[i].field == field)
+ return i;
+ }
+
+ return -1;
+}
+
+typedef struct {
+ EBookBackendSqliteDB *ebsdb;
+ GSList *list;
+} StoreVCardData;
static GQuark
e_book_backend_sqlitedb_error_quark (void)
sqlite3_close (priv->db);
g_free (priv->path);
+ g_free (priv->summary_fields);
g_mutex_clear (&priv->in_transaction_lock);
g_mutex_init (&ebsdb->priv->in_transaction_lock);
}
-static void
-e_book_sqlitedb_match_func (sqlite3_context *ctx,
- gint nArgs,
- sqlite3_value **values)
-{
- gboolean matches = FALSE;
- const gchar *what, *where;
-
- g_return_if_fail (ctx != NULL);
- g_return_if_fail (nArgs == 2);
- g_return_if_fail (values != NULL);
-
- what = (const gchar *) sqlite3_value_text (values[0]);
- where = (const gchar *) sqlite3_value_text (values[1]);
-
- if (what && where && !*what) {
- matches = TRUE;
- } else if (what && where) {
- gboolean word = TRUE;
- gint i, j;
-
- for (i = 0, j = 0; where[i] && !matches; i++) {
- gchar c = where[i];
-
- if (c == ' ') {
- word = TRUE;
- j = 0;
- } else if (word && tolower (c) == tolower (what[j])) {
- j++;
- if (what[j] == 0 && (where[i + 1] == 0 || isspace (where[i + 1])))
- matches = TRUE;
- } else {
- word = FALSE;
- }
- }
- }
-
- sqlite3_result_int (ctx, matches ? 1 : 0);
-}
-
/**
* e_book_sql_exec
* @db:
string = g_string_new (
"CREATE TABLE IF NOT EXISTS %Q ( uid TEXT PRIMARY KEY, ");
- for (i = 1; i < G_N_ELEMENTS (summary_fields); i++) {
- g_string_append (string, summary_fields[i].dbname);
- g_string_append_c (string, ' ');
-
- if (summary_fields[i].fundamental_type == G_TYPE_STRING)
- g_string_append (string, "TEXT, ");
- else if (summary_fields[i].fundamental_type == G_TYPE_BOOLEAN)
- g_string_append (string, "INTEGER, ");
- else
+ for (i = 1; i < ebsdb->priv->n_summary_fields; i++) {
+ if (ebsdb->priv->summary_fields[i].type == G_TYPE_STRING) {
+ g_string_append (string, ebsdb->priv->summary_fields[i].dbname);
+ g_string_append (string, " TEXT, ");
+ } else if (ebsdb->priv->summary_fields[i].type == G_TYPE_BOOLEAN) {
+ g_string_append (string, ebsdb->priv->summary_fields[i].dbname);
+ g_string_append (string, " INTEGER, ");
+ } else if (ebsdb->priv->summary_fields[i].type != E_TYPE_CONTACT_ATTR_LIST)
g_warn_if_reached ();
+
+ /* Additional columns holding normalized reverse values for suffix matching */
+ if (ebsdb->priv->summary_fields[i].type == G_TYPE_STRING &&
+ (ebsdb->priv->summary_fields[i].index & INDEX_SUFFIX) != 0) {
+ g_string_append (string, ebsdb->priv->summary_fields[i].dbname);
+ g_string_append (string, "_reverse TEXT, ");
+ }
}
g_string_append (string, "vcard TEXT, bdata TEXT)");
sqlite3_free (stmt);
- if (!success)
- goto exit;
+ /* Create indexes on the summary fields configured for indexing */
+ for (i = 0; success && i < ebsdb->priv->n_summary_fields; i++) {
+ if ((ebsdb->priv->summary_fields[i].index & INDEX_PREFIX) != 0 &&
+ ebsdb->priv->summary_fields[i].type != E_TYPE_CONTACT_ATTR_LIST) {
+ /* Derive index name from field & folder */
+ tmp = g_strdup_printf ("INDEX_%s_%s",
+ summary_dbname_from_field (ebsdb, ebsdb->priv->summary_fields[i].field),
+ folderid);
+ stmt = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON %Q (%s)", tmp, folderid,
+ summary_dbname_from_field (ebsdb, ebsdb->priv->summary_fields[i].field));
+ success = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, error);
+ sqlite3_free (stmt);
+ g_free (tmp);
+ }
- /* Create indexes on full_name and email_1 as autocompletion
- * queries would mainly rely on this. Assuming that the frequency
- * of matching on these would be higher than on the other fields
- * like email_2, surname etc. email_1 should be the primary email. */
- tmp = g_strdup_printf ("FNINDEX-%s", folderid);
- stmt = sqlite3_mprintf (
- "CREATE INDEX IF NOT EXISTS %Q ON %Q (full_name)",
- tmp, folderid);
- success = book_backend_sql_exec (
- ebsdb->priv->db, stmt, NULL, NULL, error);
- sqlite3_free (stmt);
- g_free (tmp);
+ if (success &&
+ (ebsdb->priv->summary_fields[i].index & INDEX_SUFFIX) != 0 &&
+ ebsdb->priv->summary_fields[i].type != E_TYPE_CONTACT_ATTR_LIST) {
+ /* Derive index name from field & folder */
+ tmp = g_strdup_printf ("RINDEX_%s_%s",
+ summary_dbname_from_field (ebsdb, ebsdb->priv->summary_fields[i].field),
+ folderid);
+ stmt = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS %Q ON %Q (%s_reverse)", tmp, folderid,
+ summary_dbname_from_field (ebsdb, ebsdb->priv->summary_fields[i].field));
+ success = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, error);
+ sqlite3_free (stmt);
+ g_free (tmp);
+ }
+ }
- if (!success)
- goto exit;
+ /* Construct the create statement from the attribute list summary table */
+ if (success && ebsdb->priv->have_attr_list) {
+ string = g_string_new ("CREATE TABLE IF NOT EXISTS %Q ( uid TEXT NOT NULL REFERENCES %s(uid), "
+ "field TEXT, value TEXT");
- tmp = g_strdup_printf ("EMINDEX-%s", folderid);
- stmt = sqlite3_mprintf (
- "CREATE INDEX IF NOT EXISTS %Q ON %Q (email_1)",
- tmp, folderid);
- success = book_backend_sql_exec (
- ebsdb->priv->db, stmt, NULL, NULL, error);
- sqlite3_free (stmt);
- g_free (tmp);
+ if (ebsdb->priv->have_attr_list_suffix)
+ g_string_append (string, ", value_reverse TEXT");
+
+ g_string_append_c (string, ')');
+
+ tmp = g_strdup_printf ("%s_lists", folderid);
+ stmt = sqlite3_mprintf (string->str, tmp, folderid);
+ g_string_free (string, TRUE);
+
+ success = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, error);
+ sqlite3_free (stmt);
+
+ /* Give the UID an index in this table, always */
+ stmt = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS LISTINDEX ON %Q (uid)", tmp);
+ success = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, error);
+ sqlite3_free (stmt);
+
+ /* Create indexes if specified */
+ if (success && ebsdb->priv->have_attr_list_prefix) {
+ stmt = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS VALINDEX ON %Q (value)", tmp);
+ success = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, error);
+ sqlite3_free (stmt);
+ }
+
+ if (success && ebsdb->priv->have_attr_list_suffix) {
+ stmt = sqlite3_mprintf ("CREATE INDEX IF NOT EXISTS RVALINDEX ON %Q (value_reverse)", tmp);
+ success = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, error);
+ sqlite3_free (stmt);
+ }
+
+ g_free (tmp);
+
+ }
-exit:
WRITER_UNLOCK (ebsdb);
return success;
return FALSE;
}
- sqlite3_create_function (
- ebsdb->priv->db, "MATCH", 2, SQLITE_UTF8, NULL,
- e_book_sqlitedb_match_func, NULL, NULL);
-
WRITER_LOCK (ebsdb);
book_backend_sql_exec (
ebsdb->priv->db,
"PRAGMA foreign_keys = ON",
NULL, NULL, NULL);
+ book_backend_sql_exec (
+ ebsdb->priv->db,
+ "PRAGMA case_sensitive_like = ON",
+ NULL, NULL, NULL);
WRITER_UNLOCK (ebsdb);
return create_folders_table (ebsdb, error);
}
-/**
- * e_book_backend_sqlitedb_new
- * @path: location where the db would be created
- * @emailid: email id of the user
- * @folderid: folder id of the address-book
- * @folder_name: name 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.
- * @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
- **/
-EBookBackendSqliteDB *
-e_book_backend_sqlitedb_new (const gchar *path,
- const gchar *emailid,
- const gchar *folderid,
- const gchar *folder_name,
- gboolean store_vcard,
- GError **error)
+static EBookBackendSqliteDB *
+e_book_backend_sqlitedb_new_internal (const gchar *path,
+ const gchar *emailid,
+ const gchar *folderid,
+ const gchar *folder_name,
+ gboolean store_vcard,
+ SummaryField *fields,
+ gint n_fields,
+ gboolean have_attr_list,
+ gboolean have_attr_list_prefix,
+ gboolean have_attr_list_suffix,
+ GError **error)
{
EBookBackendSqliteDB *ebsdb;
gchar *hash_key, *filename;
ebsdb = g_object_new (E_TYPE_BOOK_BACKEND_SQLITEDB, NULL);
ebsdb->priv->path = g_strdup (path);
+ ebsdb->priv->summary_fields = fields;
+ ebsdb->priv->n_summary_fields = n_fields;
+ ebsdb->priv->have_attr_list = have_attr_list;
+ ebsdb->priv->have_attr_list_prefix = have_attr_list_prefix;
+ ebsdb->priv->have_attr_list_suffix = have_attr_list_suffix;
ebsdb->priv->store_vcard = store_vcard;
if (g_mkdir_with_parents (path, 0777) < 0) {
g_mutex_unlock (&dbcon_lock);
return ebsdb;
}
+static gboolean
+append_summary_field (GArray *array,
+ EContactField field,
+ gboolean *have_attr_list,
+ GError **error)
+{
+ const gchar *dbname = NULL;
+ GType type = G_TYPE_INVALID;
+ gint i;
+ SummaryField new_field = { 0, };
+
+ if (field < 1 || field >= E_CONTACT_FIELD_LAST) {
+ g_set_error (error, E_BOOK_SDB_ERROR,
+ 0, _("Invalid contact field '%d' specified in summary"), field);
+ return FALSE;
+ }
+
+ /* Avoid including the same field twice in the summary */
+ for (i = 0; i < array->len; i++) {
+ SummaryField *iter = &g_array_index (array, SummaryField, i);
+ if (field == iter->field)
+ return TRUE;
+ }
+
+ /* 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_SDB_ERROR, 0,
+ _("Contact field '%s' of type '%s' specified in summary, "
+ "but only boolean, string and string list field types are supported"),
+ e_contact_pretty_name (field), g_type_name (type));
+ return FALSE;
+ }
+
+ if (type == E_TYPE_CONTACT_ATTR_LIST)
+ *have_attr_list = TRUE;
+
+ new_field.field = field;
+ new_field.dbname = dbname;
+ new_field.type = type;
+ g_array_append_val (array, new_field);
+
+ return TRUE;
+}
+
+static void
+summary_fields_add_indexes (GArray *array,
+ EContactField *indexes,
+ EBookIndexType *index_types,
+ gint n_indexes,
+ gboolean *have_attr_list_prefix,
+ gboolean *have_attr_list_suffix)
+{
+ gint 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]) {
+ switch (index_types[j]) {
+ case E_BOOK_INDEX_PREFIX:
+ sfield->index |= INDEX_PREFIX;
+
+ if (sfield->type == E_TYPE_CONTACT_ATTR_LIST)
+ *have_attr_list_prefix = TRUE;
+ break;
+ case E_BOOK_INDEX_SUFFIX:
+ sfield->index |= INDEX_SUFFIX;
+
+ if (sfield->type == E_TYPE_CONTACT_ATTR_LIST)
+ *have_attr_list_suffix = TRUE;
+ break;
+ default:
+ g_warn_if_reached ();
+ break;
+ }
+ }
+ }
+ }
+}
+
+
+/**
+ * e_book_backend_sqlitedb_new_full:
+ * @path: location where the db would be created
+ * @emailid: email id of the user
+ * @folderid: folder id of the address-book
+ * @folder_name: name 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_sqlitedb_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 #EBookBackendSqliteDB
+ *
+ * Since: 3.8
+ **/
+EBookBackendSqliteDB *
+e_book_backend_sqlitedb_new_full (const gchar *path,
+ const gchar *emailid,
+ const gchar *folderid,
+ const gchar *folder_name,
+ gboolean store_vcard,
+ ESourceBackendSummarySetup *setup,
+ GError **error)
+{
+ EBookBackendSqliteDB *ebsdb = NULL;
+ EContactField *fields;
+ EContactField *indexed_fields;
+ EBookIndexType *index_types = NULL;
+ gboolean have_attr_list = FALSE;
+ gboolean have_attr_list_prefix = FALSE;
+ gboolean have_attr_list_suffix = FALSE;
+ 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) {
+ ebsdb = e_book_backend_sqlitedb_new (path, emailid, folderid, folder_name, store_vcard, error);
+ g_free (fields);
+ g_free (index_types);
+ g_free (indexed_fields);
+
+ return ebsdb;
+ }
+
+ summary_fields = g_array_new (FALSE, FALSE, sizeof (SummaryField));
+
+ /* Ensure the non-optional fields first */
+ append_summary_field (summary_fields, E_CONTACT_UID, &have_attr_list, error);
+ append_summary_field (summary_fields, E_CONTACT_REV, &have_attr_list, error);
+
+ for (i = 0; i < n_fields; i++) {
+ if (!append_summary_field (summary_fields, fields[i], &have_attr_list, error)) {
+ had_error = TRUE;
+ break;
+ }
+ }
+
+ if (had_error) {
+ g_array_free (summary_fields, TRUE);
+ g_free (fields);
+ g_free (index_types);
+ g_free (indexed_fields);
+ return NULL;
+ }
+
+ /* Add the 'indexed' flag to the SummaryField structs */
+ summary_fields_add_indexes (summary_fields, indexed_fields, index_types, n_indexed_fields,
+ &have_attr_list_prefix, &have_attr_list_suffix);
+
+ ebsdb = e_book_backend_sqlitedb_new_internal (path, emailid, folderid, folder_name,
+ store_vcard,
+ (SummaryField *) summary_fields->data,
+ summary_fields->len,
+ have_attr_list,
+ have_attr_list_prefix,
+ have_attr_list_suffix,
+ error);
+
+ g_free (fields);
+ g_free (index_types);
+ g_free (indexed_fields);
+ g_array_free (summary_fields, FALSE);
+
+ return ebsdb;
+}
+
+
+
+/**
+ * e_book_backend_sqlitedb_new
+ * @path: location where the db would be created
+ * @emailid: email id of the user
+ * @folderid: folder id of the address-book
+ * @folder_name: name 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.
+ * @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
+ **/
+EBookBackendSqliteDB *
+e_book_backend_sqlitedb_new (const gchar *path,
+ const gchar *emailid,
+ const gchar *folderid,
+ const gchar *folder_name,
+ gboolean store_vcard,
+ GError **error)
+{
+ EBookBackendSqliteDB *ebsdb;
+ GArray *summary_fields;
+ gboolean have_attr_list = FALSE;
+ gboolean have_attr_list_prefix = FALSE;
+ gboolean have_attr_list_suffix = FALSE;
+ 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++)
+ append_summary_field (summary_fields, default_summary_fields[i], &have_attr_list, NULL);
+
+ /* Add the default index flags */
+ summary_fields_add_indexes (summary_fields,
+ default_indexed_fields,
+ default_index_types,
+ G_N_ELEMENTS (default_indexed_fields),
+ &have_attr_list_prefix, &have_attr_list_suffix);
+
+ ebsdb = e_book_backend_sqlitedb_new_internal (path, emailid, folderid, folder_name,
+ store_vcard,
+ (SummaryField *) summary_fields->data,
+ summary_fields->len,
+ have_attr_list,
+ have_attr_list_prefix,
+ have_attr_list_suffix,
+ error);
+ g_array_free (summary_fields, FALSE);
+
+ return ebsdb;
+}
+
gboolean
e_book_backend_sqlitedb_lock_updates (EBookBackendSqliteDB *ebsdb,
GError **error)
/* Add Contact (free the result with g_free() ) */
static gchar *
-insert_stmt_from_contact (EContact *contact,
+insert_stmt_from_contact (EBookBackendSqliteDB *ebsdb,
+ EContact *contact,
gboolean partial_content,
const gchar *folderid,
gboolean store_vcard)
string = g_string_new (str);
sqlite3_free (str);
- for (i = 0; i < G_N_ELEMENTS (summary_fields); i++) {
- if (i > 0)
- g_string_append (string, ", ");
+ for (i = 0; i < ebsdb->priv->n_summary_fields; i++) {
- if (summary_fields[i].fundamental_type == G_TYPE_STRING) {
+ if (ebsdb->priv->summary_fields[i].type == G_TYPE_STRING) {
gchar *val;
+ gchar *normal;
- val = e_contact_get (contact, summary_fields[i].field);
- str = sqlite3_mprintf ("%Q", val);
+ if (i > 0)
+ g_string_append (string, ", ");
- g_string_append (string, str);
+ val = e_contact_get (contact, ebsdb->priv->summary_fields[i].field);
+
+ /* Special exception, never normalize the UID or REV string */
+ if (ebsdb->priv->summary_fields[i].field != E_CONTACT_UID &&
+ ebsdb->priv->summary_fields[i].field != E_CONTACT_REV)
+ normal = e_util_utf8_normalize (val);
+ else
+ normal = g_strdup (val);
+ str = sqlite3_mprintf ("%Q", normal);
+ g_string_append (string, str);
sqlite3_free (str);
+
+ if ((ebsdb->priv->summary_fields[i].index & INDEX_SUFFIX) != 0) {
+ gchar *reverse = normal ? g_utf8_strreverse (normal, -1) : NULL;
+
+ str = sqlite3_mprintf ("%Q", reverse);
+ g_string_append (string, ", ");
+ g_string_append (string, str);
+ sqlite3_free (str);
+ g_free (reverse);
+ }
+
+ g_free (normal);
g_free (val);
- } else if (summary_fields[i].fundamental_type == G_TYPE_BOOLEAN) {
+ } else if (ebsdb->priv->summary_fields[i].type == G_TYPE_BOOLEAN) {
gboolean val;
- val = e_contact_get (contact, summary_fields[i].field) ? TRUE : FALSE;
+ if (i > 0)
+ g_string_append (string, ", ");
+
+ val = e_contact_get (contact, ebsdb->priv->summary_fields[i].field) ? TRUE : FALSE;
g_string_append_printf (string, "%d", val ? 1 : 0);
- } else
+ } else if (ebsdb->priv->summary_fields[i].type != E_TYPE_CONTACT_ATTR_LIST)
g_warn_if_reached ();
}
return g_string_free (string, FALSE);
}
+static gboolean
+insert_contact (EBookBackendSqliteDB *ebsdb,
+ EContact *contact,
+ gboolean partial_content,
+ const gchar *folderid,
+ GError **error)
+{
+ EBookBackendSqliteDBPrivate *priv;
+ gboolean success;
+ gchar *stmt;
+
+ priv = ebsdb->priv;
+
+ /* Update main summary table */
+ stmt = insert_stmt_from_contact (ebsdb, contact, partial_content, folderid, priv->store_vcard);
+ success = book_backend_sql_exec (priv->db, stmt, NULL, NULL, error);
+ g_free (stmt);
+
+ /* Update attribute list table */
+ if (success && priv->have_attr_list) {
+ gchar *list_folder = g_strdup_printf ("%s_lists", folderid);
+ gchar *uid;
+ gint i;
+ GList *values, *l;
+
+ /* First remove all entries for this UID */
+ uid = e_contact_get (contact, E_CONTACT_UID);
+ stmt = sqlite3_mprintf ("DELETE FROM %Q WHERE uid = %Q", list_folder, uid);
+ success = book_backend_sql_exec (priv->db, stmt, NULL, NULL, error);
+ sqlite3_free (stmt);
+
+ for (i = 0; success && i < priv->n_summary_fields; i++) {
+
+ if (priv->summary_fields[i].type != E_TYPE_CONTACT_ATTR_LIST)
+ continue;
+
+ values = e_contact_get (contact, priv->summary_fields[i].field);
+
+ for (l = values; success && l != NULL; l = l->next) {
+ gchar *value = (gchar *)l->data;
+ gchar *normal = e_util_utf8_normalize (value);
+
+ if (priv->have_attr_list_suffix) {
+ gchar *reverse = normal ? g_utf8_strreverse (normal, -1) : NULL;
+
+ stmt = sqlite3_mprintf ("INSERT INTO %Q (uid, field, value, value_reverse) "
+ "VALUES (%Q, %Q, %Q, %Q)",
+ list_folder, uid,
+ priv->summary_fields[i].dbname,
+ normal, reverse);
+
+ g_free (reverse);
+ } else {
+ stmt = sqlite3_mprintf ("INSERT INTO %Q (uid, field, value) "
+ "VALUES (%Q, %Q, %Q)",
+ list_folder, uid,
+ priv->summary_fields[i].dbname,
+ normal);
+ }
+
+ success = book_backend_sql_exec (priv->db, stmt, NULL, NULL, error);
+ sqlite3_free (stmt);
+ g_free (normal);
+ }
+
+ /* Free the list of allocated strings */
+ e_contact_attr_list_free (values);
+ }
+
+ g_free (list_folder);
+ g_free (uid);
+ }
+
+ return success;
+}
+
+
/**
* e_book_backend_sqlitedb_add_contact
* @ebsdb:
gboolean partial_content,
GError **error)
{
- GSList *link;
+ GSList *l;
+ gboolean success;
g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITEDB (ebsdb), FALSE);
g_return_val_if_fail (folderid != NULL, FALSE);
g_return_val_if_fail (contacts != NULL, FALSE);
- if (!book_backend_sqlitedb_start_transaction (ebsdb, error))
- return FALSE;
+ success = book_backend_sqlitedb_start_transaction (ebsdb, error);
- for (link = contacts; link != NULL; link = g_slist_next (link)) {
- EContact *contact = E_CONTACT (link->data);
- gboolean success;
- gchar *stmt;
+ for (l = contacts; success && l != NULL; l = g_slist_next (l)) {
+ EContact *contact = (EContact *) l->data;
- stmt = insert_stmt_from_contact (
- contact, partial_content, folderid,
- ebsdb->priv->store_vcard);
- success = book_backend_sql_exec (
- ebsdb->priv->db, stmt, NULL, NULL, error);
- g_free (stmt);
-
- if (!success)
- goto rollback;
+ success = insert_contact (ebsdb, contact, partial_content, folderid, error);
}
- return book_backend_sqlitedb_commit_transaction (ebsdb, error);
+ if (success)
+ return book_backend_sqlitedb_commit_transaction (ebsdb, error);
-rollback:
/* The GError is already set. */
book_backend_sqlitedb_rollback_transaction (ebsdb, NULL);
ebsdb, folderid, &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_sqlitedb_remove_contacts:
*
GSList *uids,
GError **error)
{
- GSList *l;
- GString *str;
- gchar *tmp;
gboolean success;
+ gchar *stmt;
g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITEDB (ebsdb), FALSE);
g_return_val_if_fail (folderid != NULL, FALSE);
g_return_val_if_fail (uids != NULL, FALSE);
- if (!book_backend_sqlitedb_start_transaction (ebsdb, error))
- return FALSE;
+ success = book_backend_sqlitedb_start_transaction (ebsdb, error);
- str = g_string_new ("DELETE FROM ");
+ /* Delete the auxillary contact infos first */
+ if (success && ebsdb->priv->have_attr_list) {
+ gchar *lists_folder = g_strdup_printf ("%s_lists", folderid);
- tmp = sqlite3_mprintf ("%Q WHERE uid IN (", folderid);
- g_string_append (str, tmp);
- sqlite3_free (tmp);
+ stmt = generate_delete_stmt (lists_folder, uids);
+ g_free (lists_folder);
- for (l = uids; l != NULL; l = g_slist_next (l)) {
- gchar *uid = (gchar *) l->data;
+ success = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, error);
+ g_free (stmt);
+ }
- tmp = sqlite3_mprintf ("%Q", uid);
- g_string_append_printf (str, " %s ,", tmp);
- sqlite3_free (tmp);
+ if (success) {
+ stmt = generate_delete_stmt (folderid, uids);
+ success = book_backend_sql_exec (ebsdb->priv->db, stmt, NULL, NULL, error);
+ g_free (stmt);
}
- /* remove the last comma */
- g_string_truncate (str, str->len - 1);
- g_string_append (str, ")");
-
- success = book_backend_sql_exec (
- ebsdb->priv->db, str->str, NULL, NULL, error);
-
- g_string_free (str, TRUE);
-
- if (!success)
- goto rollback;
-
- return book_backend_sqlitedb_commit_transaction (ebsdb, error);
+ if (success)
+ return book_backend_sqlitedb_commit_transaction (ebsdb, error);
-rollback:
/* The GError is already set. */
book_backend_sqlitedb_rollback_transaction (ebsdb, NULL);
return contact;
}
-static void
-accumulate_fields_select_stmt (const gchar *field_name,
- gpointer is_present,
- GString *string)
-{
- EContactField field = e_contact_field_id (field_name);
- const gchar *dbname = NULL;
-
- if (field == E_CONTACT_UID)
- return;
-
- dbname = summary_dbname_from_field (field);
-
- /* The field of interest is not in the summary information,
- * technically we shouldnt reach this case
- */
- if (!dbname)
- return;
-
- g_string_append (string, ", ");
- g_string_append (string, dbname);
-}
-
-static void
-check_field_foreach (const gchar *field_name,
- gpointer is_present,
- gboolean *is_summary_query)
-{
- EContactField field = e_contact_field_id (field_name);
-
- if (!summary_dbname_from_field (field)) {
- *is_summary_query = FALSE;
- }
-}
-
/**
* e_book_backend_sqlitedb_is_summary_fields:
- *
- * FIXME: Document me.
+ * @fields_of_interest: A hash table containing the fields of interest
+ *
+ * This only checks if all the fields are part of the default summary fields,
+ * not part of the configured summary fields.
*
* Since: 3.2
+ *
+ * Deprecated: 3.8: Use e_book_backend_sqlitedb_check_summary_fields() instead.
**/
gboolean
e_book_backend_sqlitedb_is_summary_fields (GHashTable *fields_of_interest)
{
gboolean summary_fields = TRUE;
+ GHashTableIter iter;
+ gpointer key, value;
+ gint i;
if (!fields_of_interest)
return FALSE;
- g_hash_table_foreach (fields_of_interest, (GHFunc) check_field_foreach, &summary_fields);
+ g_hash_table_iter_init (&iter, fields_of_interest);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ const gchar *field_name = key;
+ EContactField field = e_contact_field_id (field_name);
+ gboolean found = FALSE;
+
+ for (i = 0; i < G_N_ELEMENTS (default_summary_fields); i++) {
+ if (field == default_summary_fields[i]) {
+ found = TRUE;
+ break;
+ }
+ }
+
+ if (!found) {
+ summary_fields = FALSE;
+ break;
+ }
+ }
return summary_fields;
}
-/* free return value with g_free */
-static gchar *
-summary_select_stmt (const gchar *folderid,
- GHashTable *fields_of_interest,
- gboolean *with_all_required_fields)
+/**
+ * e_book_backend_sqlitedb_check_summary_fields:
+ * @ebsdb: An #EBookBackendSqliteDB
+ * @fields_of_interest: A hash table containing the fields of interest
+ *
+ * Checks if all the specified fields are part of the configured summary
+ * fields for @ebsdb
+ *
+ * Since: 3.8
+ **/
+gboolean
+e_book_backend_sqlitedb_check_summary_fields (EBookBackendSqliteDB *ebsdb,
+ GHashTable *fields_of_interest)
{
- GString *string;
- gchar *str;
+ gboolean summary_fields = TRUE;
+ GHashTableIter iter;
+ gpointer key, value;
- string = g_string_new ("SELECT uid");
+ if (!fields_of_interest)
+ return FALSE;
- /* If filtering by fields of interest, only query those and include the 'uid'
- *
- */
- if (fields_of_interest && e_book_backend_sqlitedb_is_summary_fields (fields_of_interest)) {
- g_hash_table_foreach (fields_of_interest, (GHFunc) accumulate_fields_select_stmt, string);
+ g_hash_table_iter_init (&iter, fields_of_interest);
+ while (g_hash_table_iter_next (&iter, &key, &value)) {
+ const gchar *field_name = key;
+ EContactField field = e_contact_field_id (field_name);
- /* The query should return all the required information */
- if (with_all_required_fields)
- *with_all_required_fields = TRUE;
- } else if (with_all_required_fields) {
- /* If the fields of interest is null or contains fields that are not
- * part of the summary then only the uids are returned.
- */
- *with_all_required_fields = FALSE;
+ if (summary_dbname_from_field (ebsdb, field) == NULL) {
+ summary_fields = FALSE;
+ break;
+ }
}
- str = sqlite3_mprintf (" FROM %Q", folderid);
- g_string_append (string, str);
- sqlite3_free (str);
-
- return g_string_free (string, FALSE);
+ return summary_fields;
}
/**
gboolean *with_all_required_fields,
GError **error)
{
- gchar *stmt, *select_stmt;
+ gchar *stmt;
gchar *vcard_str = NULL;
gboolean local_with_all_required_fields = FALSE;
READER_LOCK (ebsdb);
if (!ebsdb->priv->store_vcard) {
- GSList *vcards = NULL;
-
- select_stmt = summary_select_stmt (
- folderid, fields_of_interest,
- &local_with_all_required_fields);
- stmt = sqlite3_mprintf ("%s WHERE uid = %Q", select_stmt, uid);
-
- book_backend_sql_exec (
- ebsdb->priv->db, stmt,
- store_data_to_vcard, &vcards, error);
-
- sqlite3_free (stmt);
- g_free (select_stmt);
-
- if (vcards) {
- EbSdbSearchData *s_data = (EbSdbSearchData *) vcards->data;
-
- vcard_str = s_data->vcard;
- s_data->vcard = NULL;
+ g_set_error (error, E_BOOK_SDB_ERROR,
+ 0, _("Full search_contacts are not stored in cache. vcards cannot be returned."));
- e_book_backend_sqlitedb_search_data_free (s_data);
-
- g_slist_free (vcards);
- vcards = NULL;
- }
} else {
stmt = sqlite3_mprintf (
"SELECT vcard FROM %Q WHERE uid = %Q", folderid, uid);
return vcard_str;
}
+
+enum {
+ CHECK_IS_SUMMARY = (1 << 0),
+ CHECK_IS_LIST_ATTR = (1 << 1),
+};
+
+static ESExpResult *
+func_check_subset (ESExp *f,
+ gint argc,
+ struct _ESExpTerm **argv,
+ gpointer data)
+{
+ ESExpResult *r, *r1;
+ gboolean one_non_summary_query = FALSE;
+ gint result = 0;
+ gint i;
+
+ for (i = 0; i < argc; i++) {
+ r1 = e_sexp_term_eval (f, argv[i]);
+
+ if (r1->type != ESEXP_RES_INT) {
+ e_sexp_result_free (f, r1);
+ continue;
+ }
+
+ result |= r1->value.number;
+
+ if ((r1->value.number & CHECK_IS_SUMMARY) == 0)
+ one_non_summary_query = TRUE;
+
+ e_sexp_result_free (f, r1);
+ }
+
+ /* If at least one subset is not a summary query,
+ * then the whole query is not a summary query and
+ * thus cannot be done with an SQL statement
+ */
+ if (one_non_summary_query)
+ result = 0;
+
+ r = e_sexp_result_new (f, ESEXP_RES_INT);
+ r->value.number = result;
+
+ return r;
+}
+
static ESExpResult *
func_check (struct _ESExp *f,
gint argc,
struct _ESExpResult **argv,
gpointer data)
{
+ EBookBackendSqliteDB *ebsdb = data;
ESExpResult *r;
- gint truth = FALSE;
+ gint ret_val = 0;
if (argc == 2
&& argv[0]->type == ESEXP_RES_STRING
/* Special case, when testing the special symbolic 'any field' we can
* consider it a summary query (it's similar to a 'no query'). */
if (g_strcmp0 (query_name, "x-evolution-any-field") == 0 &&
- g_strcmp0 (query_value, "") == 0)
- truth = TRUE;
+ g_strcmp0 (query_value, "") == 0) {
+ ret_val |= CHECK_IS_SUMMARY;
+ goto check_finish;
+ }
- for (i = 0; truth == FALSE && i < G_N_ELEMENTS (summary_fields); i++) {
+ if (ebsdb) {
+ for (i = 0; i < ebsdb->priv->n_summary_fields; i++) {
+ if (!strcmp (e_contact_field_name (ebsdb->priv->summary_fields[i].field), query_name)) {
+ ret_val |= CHECK_IS_SUMMARY;
- if (!strcmp (e_contact_field_name (summary_fields[i].field), query_name))
- truth = TRUE;
+ if (ebsdb->priv->summary_fields[i].type == E_TYPE_CONTACT_ATTR_LIST)
+ ret_val |= CHECK_IS_LIST_ATTR;
+ }
+ }
+ } else {
+ for (i = 0; i < G_N_ELEMENTS (default_summary_fields); i++) {
+
+ if (!strcmp (e_contact_field_name (default_summary_fields[i]), query_name)) {
+ ret_val |= CHECK_IS_SUMMARY;
+
+ if (e_contact_field_type (default_summary_fields[i]) == E_TYPE_CONTACT_ATTR_LIST)
+ ret_val |= CHECK_IS_LIST_ATTR;
+ }
+ }
}
}
- r = e_sexp_result_new (f, ESEXP_RES_BOOL);
- r->value.boolean = truth;
+ check_finish:
+
+ r = e_sexp_result_new (f, ESEXP_RES_INT);
+ r->value.number = ret_val;
return r;
}
gint type; /* set to 1 if a function can perform shortcut evaluation, or
doesn't execute everything, 0 otherwise */
} check_symbols[] = {
+ { "and", (ESExpFunc *) func_check_subset, 1},
+ { "or", (ESExpFunc *) func_check_subset, 1},
+
{ "contains", func_check, 0 },
{ "is", func_check, 0 },
{ "beginswith", func_check, 0 },
};
/**
- * e_book_backend_sqlitedb_is_summary_query:
+ * e_book_backend_sqlitedb_check_summary_query:
+ * @ebsdb: an #EBookBackendSqliteDB
+ * @query: the query to check
+ * @with_list_attrs: Return location to store whether the query touches upon list attributes
*
- * FIXME: Document me.
+ * Checks whether @query contains only checks for the summary fields
+ * configured in @ebsdb
*
- * Since: 3.2
+ * Since: 3.8
**/
gboolean
-e_book_backend_sqlitedb_is_summary_query (const gchar *query)
+e_book_backend_sqlitedb_check_summary_query (EBookBackendSqliteDB *ebsdb,
+ const gchar *query,
+ gboolean *with_list_attrs)
{
ESExp *sexp;
ESExpResult *r;
- gboolean retval;
+ gboolean retval = FALSE;
gint i;
gint esexp_error;
for (i = 0; i < G_N_ELEMENTS (check_symbols); i++) {
if (check_symbols[i].type == 1) {
e_sexp_add_ifunction (
- sexp, 0, check_symbols[i].name,
- (ESExpIFunc *) check_symbols[i].func, NULL);
+ sexp, 0, check_symbols[i].name,
+ (ESExpIFunc *) check_symbols[i].func, ebsdb);
} else {
e_sexp_add_function (
sexp, 0, check_symbols[i].name,
- check_symbols[i].func, NULL);
+ check_symbols[i].func, ebsdb);
}
}
}
r = e_sexp_eval (sexp);
+ if (r && r->type == ESEXP_RES_INT) {
+ retval = (r->value.number & CHECK_IS_SUMMARY) != 0;
- retval = (r && r->type == ESEXP_RES_BOOL && r->value.boolean);
+ if ((r->value.number & CHECK_IS_LIST_ATTR) != 0 && with_list_attrs)
+ *with_list_attrs = TRUE;
+ }
e_sexp_result_free (sexp, r);
-
e_sexp_unref (sexp);
return retval;
}
+
+/**
+ * e_book_backend_sqlitedb_is_summary_query:
+ *
+ * Checks whether the query contains only checks for the default summary fields
+ *
+ * Since: 3.2
+ *
+ * Deprecated: 3.8: Use e_book_backend_sqlitedb_check_summary_query() instead
+ **/
+gboolean
+e_book_backend_sqlitedb_is_summary_query (const gchar *query)
+{
+ return e_book_backend_sqlitedb_check_summary_query (NULL, query, NULL);
+}
+
static ESExpResult *
func_and (ESExp *f,
gint argc,
static gchar *
convert_string_value (const gchar *value,
+ gboolean normalize,
+ gboolean reverse,
match_type match)
{
GString *str;
gchar c;
gboolean escape_modifier_needed = FALSE;
const gchar *escape_modifier = " ESCAPE '^'";
+ gchar *reverse_val = NULL;
+ gchar *normal;
+ const gchar *ptr;
g_return_val_if_fail (value != NULL, NULL);
+ if (normalize)
+ normal = e_util_utf8_normalize (value);
+ else
+ normal = g_strdup (value);
+
/* Just assume each character must be escaped. The result of this function
* is discarded shortly after calling this function. Therefore it's
* acceptable to possibly allocate twice the memory needed.
*/
- len = strlen (value);
+ len = strlen (normal);
str = g_string_sized_new (2 * len + 4 + strlen (escape_modifier) - 1);
g_string_append_c (str, '\'');
break;
}
- while ((c = *value++)) {
+ if (reverse) {
+ reverse_val = g_utf8_strreverse (normal, -1);
+ ptr = reverse_val;
+ } else {
+ ptr = normal;
+ }
+
+ while ((c = *ptr++)) {
if (c == '\'') {
g_string_append_c (str, '\'');
} else if (c == '%' || c == '^') {
if (escape_modifier_needed)
g_string_append (str, escape_modifier);
+ g_free (reverse_val);
+ g_free (normal);
+
return g_string_free (str, FALSE);
}
+static gchar *
+field_name_and_query_term (EBookBackendSqliteDB *ebsdb,
+ const gchar *folderid,
+ const gchar *field_name_input,
+ const gchar *query_term_input,
+ match_type match,
+ gboolean *is_list_attr,
+ gchar **query_term)
+{
+ gint summary_index;
+ gchar *field_name = NULL;
+ gchar *value = NULL;
+ gboolean list_attr = FALSE;
+
+ summary_index = summary_index_from_field_name (ebsdb, field_name_input);
+
+ if (summary_index < 0) {
+ g_critical ("Only summary field matches should be converted to sql queries");
+ field_name = g_strconcat (folderid, ".", field_name_input, NULL);
+ value = convert_string_value (query_term_input, TRUE, FALSE, match);
+ } else {
+ gboolean suffix_search = FALSE;
+
+ /* If its a suffix search and we have reverse data to search... */
+ if (match == MATCH_ENDS_WITH &&
+ (ebsdb->priv->summary_fields[summary_index].index & INDEX_SUFFIX) != 0)
+ suffix_search = TRUE;
+
+ /* Or also if its an exact match, and we *only* have reverse data which is indexed,
+ * then prefer the indexed reverse search. */
+ else if (match == MATCH_IS &&
+ (ebsdb->priv->summary_fields[summary_index].index & INDEX_SUFFIX) != 0 &&
+ (ebsdb->priv->summary_fields[summary_index].index & INDEX_PREFIX) == 0)
+ suffix_search = TRUE;
+
+ if (suffix_search) {
+ /* Special case for suffix matching:
+ * o Reverse the string
+ * o Check the reversed column instead
+ * o Make it a prefix search
+ */
+ if (ebsdb->priv->summary_fields[summary_index].type == E_TYPE_CONTACT_ATTR_LIST) {
+ field_name = g_strconcat (folderid, "_lists.value_reverse", NULL);
+ list_attr = TRUE;
+ } else
+ field_name = g_strconcat (folderid, ".",
+ ebsdb->priv->summary_fields[summary_index].dbname, "_reverse", NULL);
+
+ if (ebsdb->priv->summary_fields[summary_index].field == E_CONTACT_UID ||
+ ebsdb->priv->summary_fields[summary_index].field == E_CONTACT_REV)
+ value = convert_string_value (query_term_input, FALSE, TRUE, MATCH_BEGINS_WITH);
+ else
+ value = convert_string_value (query_term_input, TRUE, TRUE, MATCH_BEGINS_WITH);
+ } else {
+
+ if (ebsdb->priv->summary_fields[summary_index].type == E_TYPE_CONTACT_ATTR_LIST) {
+ field_name = g_strconcat (folderid, "_lists.value", NULL);
+ list_attr = TRUE;
+ } else
+ field_name = g_strconcat (folderid, ".",
+ ebsdb->priv->summary_fields[summary_index].dbname, NULL);
+
+ if (ebsdb->priv->summary_fields[summary_index].field == E_CONTACT_UID ||
+ ebsdb->priv->summary_fields[summary_index].field == E_CONTACT_REV)
+ value = convert_string_value (query_term_input, FALSE, FALSE, match);
+ else
+ value = convert_string_value (query_term_input, TRUE, FALSE, match);
+ }
+ }
+
+ if (is_list_attr)
+ *is_list_attr = list_attr;
+
+ *query_term = value;
+
+ return field_name;
+}
+
+typedef struct {
+ EBookBackendSqliteDB *ebsdb;
+ const gchar *folderid;
+} BuildQueryData;
+
static ESExpResult *
convert_match_exp (struct _ESExp *f,
gint argc,
gpointer data,
match_type match)
{
+ BuildQueryData *qdata = (BuildQueryData *)data;
+ EBookBackendSqliteDB *ebsdb = qdata->ebsdb;
ESExpResult *r;
gchar *str = NULL;
field = argv[0]->value.string;
if (argv[1]->type == ESEXP_RES_STRING && argv[1]->value.string[0] != 0) {
- gchar *value = convert_string_value (argv[1]->value.string, match);
+ const gchar *oper = "LIKE";
+ gchar *field_name, *query_term;
+
+ if (match == MATCH_IS)
+ oper = "=";
if (!strcmp (field, "full_name")) {
- gchar *full, *sur, *given, *nick;
-
- full = g_strdup_printf ("(full_name IS NOT NULL AND full_name LIKE %s)",value);
- sur = g_strdup_printf ("(family_name IS NOT NULL AND family_name LIKE %s)",value);
- given = g_strdup_printf ("(given_name IS NOT NULL AND given_name LIKE %s)",value);
- nick = g_strdup_printf ("(nickname IS NOT NULL AND nickname LIKE %s)",value);
-
- str = g_strdup_printf (" %s OR %s OR %s OR %s ", full, sur, given, nick);
-
- g_free (full);
- g_free (sur);
- g_free (given);
- g_free (nick);
- } else if (!strcmp (field, "email")) {
- gint i;
- GString *emails = g_string_new (NULL);
-
- for (i = 1; i < 4; i++) {
- g_string_append_printf (emails, "(email_%d IS NOT NULL AND email_%d LIKE %s)", i, i, value);
- g_string_append (emails, " OR ");
+ GString *names = g_string_new (NULL);
+
+ field_name = field_name_and_query_term (ebsdb, qdata->folderid, "full_name",
+ argv[1]->value.string,
+ match, NULL, &query_term);
+ g_string_append_printf (names, "(%s IS NOT NULL AND %s %s %s)",
+ field_name, field_name, oper, query_term);
+ g_free (field_name);
+ g_free (query_term);
+
+ if (summary_dbname_from_field (ebsdb, E_CONTACT_FAMILY_NAME)) {
+
+ field_name = field_name_and_query_term (ebsdb, qdata->folderid, "family_name",
+ argv[1]->value.string,
+ match, NULL, &query_term);
+ g_string_append_printf
+ (names, " OR (%s IS NOT NULL AND %s %s %s)",
+ field_name, field_name, oper, query_term);
+ g_free (field_name);
+ g_free (query_term);
}
- g_string_append_printf (emails, "(email_4 IS NOT NULL AND email_4 LIKE %s)", value);
- str = emails->str;
- g_string_free (emails, FALSE);
- } else if (!strcmp (field, "id")) {
- str = g_strdup_printf ("(uid IS NOT NULL AND uid LIKE %s)", value);
- } else
- str = g_strdup_printf ("(%s IS NOT NULL AND %s LIKE %s)", field, field, value);
- g_free (value);
+ if (summary_dbname_from_field (ebsdb, E_CONTACT_GIVEN_NAME)) {
+
+ field_name = field_name_and_query_term (ebsdb, qdata->folderid, "given_name",
+ argv[1]->value.string,
+ match, NULL, &query_term);
+ g_string_append_printf
+ (names, " OR (%s IS NOT NULL AND %s %s %s)",
+ field_name, field_name, oper, query_term);
+ g_free (field_name);
+ g_free (query_term);
+ }
+
+ if (summary_dbname_from_field (ebsdb, E_CONTACT_NICKNAME)) {
+
+ field_name = field_name_and_query_term (ebsdb, qdata->folderid, "nickname",
+ argv[1]->value.string,
+ match, NULL, &query_term);
+ g_string_append_printf
+ (names, " OR (%s IS NOT NULL AND %s %s %s)",
+ field_name, field_name, oper, query_term);
+ g_free (field_name);
+ g_free (query_term);
+ }
+
+ str = names->str;
+ g_string_free (names, FALSE);
+
+ } else {
+ gboolean is_list = FALSE;
+
+ /* This should ideally be the only valid case from all the above special casing, but oh well... */
+ field_name = field_name_and_query_term (ebsdb, qdata->folderid, field,
+ argv[1]->value.string,
+ match, &is_list, &query_term);
+
+ if (is_list) {
+ gchar *folder_uid, *list_folder_uid, *list_folder_field;
+ gchar *tmp;
+
+ folder_uid = g_strconcat (qdata->folderid, ".uid", NULL);
+ list_folder_uid = g_strconcat (qdata->folderid, "_lists.uid", NULL);
+ list_folder_field = g_strconcat (qdata->folderid, "_lists.field", NULL);
+
+ tmp = sqlite3_mprintf ("%s = %s AND %s = %Q",
+ folder_uid, list_folder_uid,
+ list_folder_field, field);
+
+ str = g_strdup_printf ("(%s AND %s %s %s)",
+ tmp, field_name, oper, query_term);
+ sqlite3_free (tmp);
+ g_free (folder_uid);
+ g_free (list_folder_uid);
+ g_free (list_folder_field);
+ } else
+ str = g_strdup_printf ("(%s IS NOT NULL AND %s %s %s)",
+ field_name, field_name, oper, query_term);
+
+ g_free (field_name);
+ g_free (query_term);
+ }
}
}
};
static gchar *
-sexp_to_sql_query (const gchar *query)
+sexp_to_sql_query (EBookBackendSqliteDB *ebsdb,
+ const gchar *folderid,
+ const gchar *query)
{
+ BuildQueryData data = { ebsdb, folderid };
ESExp *sexp;
ESExpResult *r;
gint i;
for (i = 0; i < G_N_ELEMENTS (symbols); i++) {
if (symbols[i].immediate)
e_sexp_add_ifunction (
- sexp, 0, symbols[i].name,
- (ESExpIFunc *) symbols[i].func, NULL);
+ sexp, 0, symbols[i].name,
+ (ESExpIFunc *) symbols[i].func, &data);
else
e_sexp_add_function (
sexp, 0, symbols[i].name,
- symbols[i].func, NULL);
+ symbols[i].func, &data);
}
e_sexp_input_text (sexp, query, strlen (query));
return 0;
}
-static gint
-store_data_to_vcard (gpointer ref,
- gint ncol,
- gchar **cols,
- gchar **name)
-{
- GSList **vcard_data = ref;
- EbSdbSearchData *search_data = g_new0 (EbSdbSearchData, 1);
- EContact *contact = e_contact_new ();
- gchar *vcard;
- gint i, j;
-
- /* parse through cols, this will be useful if the api starts supporting field restrictions */
- for (i = 0; i < ncol; i++)
- {
- gboolean found = FALSE;
-
- if (!name[i] || !cols[i])
- continue;
-
- for (j = 0; j < G_N_ELEMENTS (summary_fields); j++) {
-
- if (!strcmp (name[i], summary_fields[j].dbname)) {
-
- if (summary_fields[j].fundamental_type == G_TYPE_STRING)
- e_contact_set (contact, summary_fields[j].field, cols[i]);
- else if (summary_fields[j].fundamental_type == G_TYPE_BOOLEAN) {
- gboolean val = cols[i] ? strtoul (cols[i], NULL, 10) != 0 : FALSE;
- e_contact_set (contact, summary_fields[j].field, GINT_TO_POINTER (val ? TRUE : FALSE));
- } else
- g_warn_if_reached ();
-
- if (summary_fields[j].field == E_CONTACT_UID)
- search_data->uid = g_strdup (cols[i]);
-
- found = TRUE;
- break;
- }
- }
-
- if (found)
- continue;
-
- if (!strcmp (name[i], "bdata"))
- search_data->bdata = g_strdup (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 GSList *
book_backend_sqlitedb_search_query (EBookBackendSqliteDB *ebsdb,
const gchar *sql,
const gchar *folderid,
GHashTable *fields_of_interest,
gboolean *with_all_required_fields,
+ gboolean query_with_list_attrs,
GError **error)
{
GSList *vcard_data = NULL;
- gchar *stmt, *select_stmt;
+ gchar *stmt;
gboolean local_with_all_required_fields = FALSE;
- gboolean success;
+ gboolean success = TRUE;
READER_LOCK (ebsdb);
if (!ebsdb->priv->store_vcard) {
- select_stmt = summary_select_stmt (
- folderid, fields_of_interest,
- &local_with_all_required_fields);
+ g_set_error (error, E_BOOK_SDB_ERROR,
+ 0, _("Full search_contacts are not stored in cache. vcards cannot be returned."));
+ } else {
if (sql && sql[0]) {
- stmt = sqlite3_mprintf (
- "%s WHERE %s", select_stmt, sql);
- success = book_backend_sql_exec (
- ebsdb->priv->db, stmt,
- store_data_to_vcard, &vcard_data, error);
- sqlite3_free (stmt);
- } else {
- success = book_backend_sql_exec (
- ebsdb->priv->db, select_stmt,
- store_data_to_vcard, &vcard_data, error);
- }
- g_free (select_stmt);
+ if (query_with_list_attrs) {
+ gchar *list_table = g_strconcat (folderid, "_lists", NULL);
+ gchar *uid_field = g_strconcat (folderid, ".uid", NULL);
+
+ stmt = sqlite3_mprintf ("SELECT DISTINCT %s, vcard, bdata FROM %Q, %Q WHERE %s",
+ uid_field, folderid, list_table, sql);
+ g_free (list_table);
+ g_free (uid_field);
+ } else {
+ stmt = sqlite3_mprintf ("SELECT uid, vcard, bdata FROM %Q WHERE %s", folderid, sql);
+ }
- } else {
- if (sql && sql[0]) {
- stmt = sqlite3_mprintf (
- "SELECT uid, vcard, bdata FROM %Q WHERE %s",
- folderid, sql);
success = book_backend_sql_exec (
- ebsdb->priv->db, stmt,
+ ebsdb->priv->db, stmt,
addto_vcard_list_cb , &vcard_data, error);
+
sqlite3_free (stmt);
} else {
stmt = sqlite3_mprintf (
GSList *search_contacts = NULL;
gboolean local_searched = FALSE;
gboolean local_with_all_required_fields = FALSE;
+ gboolean query_with_list_attrs = FALSE;
g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITEDB (ebsdb), NULL);
g_return_val_if_fail (folderid != NULL, NULL);
if (sexp && !*sexp)
sexp = NULL;
- if (!sexp || e_book_backend_sqlitedb_is_summary_query (sexp)) {
+ if (!sexp || e_book_backend_sqlitedb_check_summary_query (ebsdb, sexp,
+ &query_with_list_attrs)) {
gchar *sql_query;
- sql_query = sexp ? sexp_to_sql_query (sexp) : NULL;
+ sql_query = sexp ? sexp_to_sql_query (ebsdb, folderid, sexp) : NULL;
search_contacts = book_backend_sqlitedb_search_query (
ebsdb, sql_query, folderid,
fields_of_interest,
- &local_with_all_required_fields, error);
+ &local_with_all_required_fields,
+ query_with_list_attrs, error);
g_free (sql_query);
local_searched = TRUE;
} else {
g_set_error (
error, E_BOOK_SDB_ERROR, 0,
- "Full search_contacts are not stored in cache. "
- "Hence only summary query is supported.");
+ _("Full search_contacts are not stored in cache. "
+ "Hence only summary query is supported."));
}
if (searched)
{
GSList *uids = NULL;
gboolean local_searched = FALSE;
+ gboolean query_with_list_attrs = FALSE;
g_return_val_if_fail (E_IS_BOOK_BACKEND_SQLITEDB (ebsdb), NULL);
g_return_val_if_fail (folderid != NULL, NULL);
if (sexp && !*sexp)
sexp = NULL;
- if (!sexp || e_book_backend_sqlitedb_is_summary_query (sexp)) {
+ if (!sexp || e_book_backend_sqlitedb_check_summary_query (ebsdb, sexp, &query_with_list_attrs)) {
gchar *stmt;
- gchar *sql_query = sexp ? sexp_to_sql_query (sexp) : NULL;
+ gchar *sql_query = sexp ? sexp_to_sql_query (ebsdb, folderid, sexp) : NULL;
READER_LOCK (ebsdb);
- stmt = sqlite3_mprintf (
- "SELECT uid FROM %Q%s%s",
- folderid,
- sql_query ? " WHERE " : "",
- sql_query ? sql_query : "");
- book_backend_sql_exec (
- ebsdb->priv->db, stmt, addto_slist_cb, &uids, error);
- sqlite3_free (stmt);
+ if (sql_query && sql_query[0]) {
+
+ if (query_with_list_attrs) {
+ gchar *list_table = g_strconcat (folderid, "_lists", NULL);
+ gchar *uid_field = g_strconcat (folderid, ".uid", NULL);
+
+ stmt = sqlite3_mprintf ("SELECT DISTINCT %s FROM %Q, %Q WHERE %s",
+ uid_field, folderid, list_table, sql_query);
+
+ g_free (list_table);
+ g_free (uid_field);
+ } else
+ stmt = sqlite3_mprintf ("SELECT uid FROM %Q WHERE %s", folderid, sql_query);
+
+ book_backend_sql_exec (ebsdb->priv->db, stmt, addto_slist_cb, &uids, error);
+ sqlite3_free (stmt);
+
+ } else {
+ stmt = sqlite3_mprintf ("SELECT uid FROM %Q", folderid);
+ book_backend_sql_exec (ebsdb->priv->db, stmt, addto_slist_cb, &uids, error);
+ sqlite3_free (stmt);
+ }
READER_UNLOCK (ebsdb);
} else {
g_set_error (
error, E_BOOK_SDB_ERROR, 0,
- "Full vcards are not stored in cache. "
- "Hence only summary query is supported.");
+ _("Full vcards are not stored in cache. "
+ "Hence only summary query is supported."));
}
if (searched)
if (ret == -1) {
g_set_error (
error, E_BOOK_SDB_ERROR, 0,
- "Unable to remove the db file: errno %d", errno);
+ _("Unable to remove the db file: errno %d"), errno);
return FALSE;
}