From c113da749d092108436e0f86526d026e838cc898 Mon Sep 17 00:00:00 2001 From: Tristan Van Berkom Date: Sat, 30 Nov 2013 23:44:33 +0900 Subject: Experimental work in progress --- addressbook/libedata-book/e-book-sqlite.c | 619 +++++------------------------- 1 file changed, 91 insertions(+), 528 deletions(-) diff --git a/addressbook/libedata-book/e-book-sqlite.c b/addressbook/libedata-book/e-book-sqlite.c index 2da05e4e8..56f33ccf6 100644 --- a/addressbook/libedata-book/e-book-sqlite.c +++ b/addressbook/libedata-book/e-book-sqlite.c @@ -36,9 +36,6 @@ #include "e-book-backend-sexp.h" - - - /****************************************************** * Debugging Macros * ****************************************************** @@ -220,10 +217,17 @@ ebsql_init_debug (void) #define FOLDER_VERSION 8 #define INSERT_MULTI_STMT_BYTES 128 #define COLUMN_DEFINITION_BYTES 32 -#define GENERATED_QUERY_BYTES 2048 +#define GENERATED_QUERY_BYTES 1024 #define DEFAULT_FOLDER_ID "folder_id" +/* We use a 64 bitmask to track which auxiliary tables + * are needed to satisfy a query, it's doubtful that + * anyone will need an addressbook with 64 fields configured + * in the summary. + */ +#define EBSQL_MAX_SUMMARY_FIELDS 64 + /* The number of SQLite virtual machine instructions that are * evaluated at a time, the user passed GCancellable is * checked between each batch of evaluated instructions. @@ -569,17 +573,30 @@ summary_fields_add_indexes (GArray *array, } } -static SummaryField * -summary_field_get (EBookSqlite *ebsql, - EContactField field_id) +static inline gint +summary_field_get_index (EBookSqlite *ebsql, + EContactField field_id) { gint i; for (i = 0; i < ebsql->priv->n_summary_fields; i++) { if (ebsql->priv->summary_fields[i].field_id == field_id) - return &(ebsql->priv->summary_fields[i]); + return i; } + return -1; +} + +static inline SummaryField * +summary_field_get (EBookSqlite *ebsql, + EContactField field_id) +{ + gint index; + + index = summary_field_get_index (ebsql, field_id); + if (index >= 0) + return &(ebsql->priv->summary_fields[index]); + return NULL; } @@ -2365,6 +2382,22 @@ ebsql_init_aux_tables (EBookSqlite *ebsql, field->aux_table, ebsql->priv->folderid); g_string_free (string, TRUE); + + if (success) { + /* Create an index on the 'uid' column, this speeds up inserts on large + * addressbooks, because rows need to be deleted by UID before reinserting + */ + tmp = g_strconcat ("UIDINDEX", + "_", field->dbname, + "_", ebsql->priv->folderid, + NULL); + ebsql_exec_printf (ebsql, + "CREATE INDEX IF NOT EXISTS %Q ON %Q (%s)", + NULL, NULL, NULL, error, + tmp, field->aux_table, "uid"); + g_free (tmp); + } + /* Add indexes to columns in this auxiliary table */ for (l = aux_columns; success && l; l = l->next) { @@ -3605,9 +3638,6 @@ typedef struct { SummaryField *field; /* The summary field for 'field' */ gchar *value; /* The value to compare with */ - /* For preflighting without collecting strings */ - guint has_value : 1; - guint has_extra : 1; } QueryFieldTest; typedef struct { @@ -3617,8 +3647,6 @@ typedef struct { EContactField field_id; /* The EContactField to compare */ SummaryField *field; /* The summary field for 'field' */ gchar *value; /* The value to compare with */ - guint has_value : 1; - guint has_extra : 1; /* Extension */ gchar *region; /* Region code from the query input */ @@ -3636,20 +3664,14 @@ typedef enum { PREFLIGHT_UNSUPPORTED, } PreflightStatus; -typedef struct { - EContactField field_id; /* multi string field id */ - GPtrArray *constraints; /* segmented query, if applicable */ -} PreflightAuxData; - /* Stack initializer for the PreflightContext struct below */ -#define PREFLIGHT_CONTEXT_INIT { PREFLIGHT_OK, NULL, FALSE, NULL } +#define PREFLIGHT_CONTEXT_INIT { PREFLIGHT_OK, NULL, FALSE, 0 } typedef struct { - PreflightStatus status; /* result status */ - GPtrArray *constraints; /* main query */ - gboolean list_all; /* TRUE if all results should be returned */ - - GSList *aux_fields; /* List of PreflightAuxData */ + PreflightStatus status; /* result status */ + GPtrArray *constraints; /* main query */ + gboolean list_all; /* TRUE if all results should be returned */ + guint64 aux_mask; /* Bitmask of which auxiliary tables are needed in the query */ } PreflightContext; static QueryElement * @@ -3681,8 +3703,6 @@ query_field_test_new (guint query, /* Instead of g_slice_new0, NULL them out manually */ test->field = NULL; test->value = NULL; - test->has_value = FALSE; - test->has_extra = FALSE; return test; } @@ -3702,8 +3722,6 @@ query_phone_test_new (guint query, /* Instead of g_slice_new0, NULL them out manually */ test->field = NULL; test->value = NULL; - test->has_value = FALSE; - test->has_extra = FALSE; /* Extra QueryPhoneTest fields */ test->region = NULL; @@ -3801,67 +3819,10 @@ constraints_insert_field_test (GPtrArray *array, test = query_field_test_new (query, field->field_id); test->field = field; test->value = g_strdup (value); - test->has_value = (value && value[0]); constraints_insert (array, idx, test); } -static PreflightAuxData * -preflight_aux_data_new (EContactField field_id) -{ - PreflightAuxData *aux_data = g_slice_new (PreflightAuxData); - - aux_data->field_id = field_id; - aux_data->constraints = NULL; - - return aux_data; -} - -static void -preflight_aux_data_free (PreflightAuxData *aux_data) -{ - if (aux_data) { - if (aux_data->constraints) - g_ptr_array_free (aux_data->constraints, TRUE); - - g_slice_free (PreflightAuxData, aux_data); - } -} - -static gint -preflight_aux_data_find (PreflightAuxData *aux_data, - gpointer data) -{ - EContactField field_id = GPOINTER_TO_UINT (data); - - /* Unsigned comparison, just to be safe, - * let's not return something like: - * 'aux_data->field_id - field_id' - */ - if (aux_data->field_id > field_id) - return 1; - else if (aux_data->field_id < field_id) - return -1; - - return 0; -} - -static PreflightAuxData * -preflight_context_search_aux (PreflightContext *context, - EContactField field_id) -{ - PreflightAuxData *aux_data = NULL; - GSList *link; - - link = g_slist_find_custom (context->aux_fields, - GUINT_TO_POINTER (field_id), - (GCompareFunc)preflight_aux_data_find); - if (link) - aux_data = link->data; - - return aux_data; -} - static void preflight_context_clear (PreflightContext *context) { @@ -3869,11 +3830,6 @@ preflight_context_clear (PreflightContext *context) /* Free any allocated data, but leave the context values in place */ if (context->constraints) g_ptr_array_free (context->constraints, TRUE); - - g_slist_free_full (context->aux_fields, - (GDestroyNotify)preflight_aux_data_free); - - context->aux_fields = NULL; context->constraints = NULL; } } @@ -4121,21 +4077,17 @@ func_check (struct _ESExp *f, QueryPhoneTest *test; /* Collect data from this field test */ - test = query_phone_test_new (query_type, field); - test->has_value = (query_value && query_value[0]); - test->has_extra = (query_extra && query_extra[0]); - test->value = g_strdup (query_value); - test->region = g_strdup (query_extra); + test = query_phone_test_new (query_type, field); + test->value = g_strdup (query_value); + test->region = g_strdup (query_extra); element = (QueryElement *)test; } else { QueryFieldTest *test; /* Collect data from this field test */ - test = query_field_test_new (query_type, field); - test->has_value = (query_value && query_value[0]); - test->has_extra = (query_extra && query_extra[0]); - test->value = g_strdup (query_value); + test = query_field_test_new (query_type, field); + test->value = g_strdup (query_value); element = (QueryElement *)test; } @@ -4150,8 +4102,10 @@ func_check (struct _ESExp *f, return result; } -/* Initial stage of preflighting, parse the search - * expression and generate our array of QueryElements +/* Initial stage of preflighting: + * + * o Parse the search expression and generate our array of QueryElements + * o Collect lengths of query terms */ static void query_preflight_initialize (PreflightContext *context, @@ -4234,9 +4188,10 @@ check_has_attr_list_cb (QueryElement *element, return (data->has_attr_list == FALSE); } -/* 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. +/* What is done in this pass: + * o Viability of the query is analyzed, i.e. can it be done with the summary columns. + * o Phone numbers are parsed and loaded onto QueryPhoneTests + * o Bitmask of auxiliary tables is collected */ static void query_preflight_check (PreflightContext *context, @@ -4254,9 +4209,7 @@ query_preflight_check (PreflightContext *context, QueryFieldTest *test; guint field_test; - /* We don't care about the subquery delimiters at this point */ if (elements[i]->query >= BOOK_QUERY_SUB_FIRST) { - /* It's too complicated to properly perform * the unary NOT operator on a constraint which * accesses attribute lists. @@ -4312,7 +4265,7 @@ query_preflight_check (PreflightContext *context, * This is only true if the 'any field contains' query is * the only test in the constraints, however. */ - if (!test->has_value && n_elements == 1) { + if (!test->value && n_elements == 1) { context->list_all = TRUE; @@ -4343,7 +4296,6 @@ query_preflight_check (PreflightContext *context, * or fields which hold multiple strings */ if (test->field) { - if (test->field->type != G_TYPE_STRING && test->field->type != E_TYPE_CONTACT_ATTR_LIST) context->status = MAX (context->status, PREFLIGHT_INVALID); @@ -4403,25 +4355,15 @@ query_preflight_check (PreflightContext *context, if (test->field && test->field->type == E_TYPE_CONTACT_ATTR_LIST) { + gint aux_index = summary_field_get_index (ebsql, test->field_id); - PreflightAuxData *aux_data; - - aux_data = preflight_context_search_aux (context, test->field_id); - if (!aux_data) { - aux_data = preflight_aux_data_new (test->field_id); - context->aux_fields = - g_slist_prepend (context->aux_fields, aux_data); - } + /* It's really improbable that we ever get 64 fields in the summary + * In any case we warn about this in e_book_sqlite_new_full(). + */ + g_warn_if_fail (aux_index < EBSQL_MAX_SUMMARY_FIELDS); + context->aux_mask |= (1 << aux_index); } } - - /* If we cannot satisfy this query with the summary, there is no point - * to return the allocated list */ - if (context->status > PREFLIGHT_OK) { - g_slist_free_full (context->aux_fields, - (GDestroyNotify)preflight_aux_data_free); - context->aux_fields = NULL; - } } /* Handle special case of E_CONTACT_FULL_NAME @@ -4485,382 +4427,6 @@ query_preflight_substitute_full_name (PreflightContext *context, } } -/* Migrates the chunk of the constraints at 'offset' into one of the - * PreflightAuxData indicated by aux_field. - * - * Returns the number of QueryElements which have been removed from - * the main constraints - */ -static gint -query_preflight_migrate_offset (PreflightContext *context, - EContactField aux_field, - gint offset) -{ - PreflightAuxData *aux_data; - QueryElement **elements; - gint sub_counter = 0; - gint dest_offset = 0; - gint n_migrated = 0; - - aux_data = preflight_context_search_aux (context, aux_field); - g_return_val_if_fail (aux_data != NULL, 0); - - if (!aux_data->constraints) { - - /* We created a new batch for 'aux_field', - * we'll be adding this batch directly to the beginning - */ - aux_data->constraints = g_ptr_array_new_with_free_func ((GDestroyNotify)query_element_free); - - } else { - elements = (QueryElement **)aux_data->constraints->pdata; - - /* If we're migrating a second or third constraint, we must ensure that - * it's encapsulated with an AND - */ - if (elements[0]->query != BOOK_QUERY_SUB_AND) { - constraints_insert_delimiter (aux_data->constraints, 0, BOOK_QUERY_SUB_AND); - constraints_insert_delimiter (aux_data->constraints, -1, BOOK_QUERY_SUB_END); - } - - /* We're going to insert this starting at position 1 (directly after opening the AND) - * The order of AND statements in the query is inconsequential. - */ - dest_offset = 1; - } - - elements = (QueryElement **)context->constraints->pdata; - do { - QueryElement *element; - - /* Migrate one element */ - element = constraints_take (context->constraints, offset); - constraints_insert (aux_data->constraints, dest_offset++, element); - - n_migrated++; - - /* If we migrated a group... migrate the whole group */ - if (element->query == BOOK_QUERY_SUB_END) - sub_counter--; - else if (element->query >= BOOK_QUERY_SUB_FIRST) - sub_counter++; - - } while (context->constraints->len > offset && sub_counter > 0); - - - /* Return the number of elements removed from the main constraints */ - return n_migrated; -} - -/* Will set the EContactField to 0 if it's completely isolated - * to the summary table, E_CONTACT_FIELD_LAST if it's not isolated, - * or another attribute list type EContactField if it's isolated - * to that field. - * - * Expects the initial value to be 'E_CONTACT_FIELD_LAST + 1' - */ -static gboolean -check_isolated_cb (QueryElement *element, - gint sub_level, - gint offset, - gpointer user_data) -{ - EContactField *field_id = (EContactField *)user_data; - QueryFieldTest *test = (QueryFieldTest *)element; - - if (*field_id > E_CONTACT_FIELD_LAST) { - - /* First field encountered, let's see what it is... */ - if (test->field->type == E_TYPE_CONTACT_ATTR_LIST) - *field_id = test->field_id; - else - *field_id = 0; - - return TRUE; - - } else if (*field_id == 0) { - - if (test->field->type == E_TYPE_CONTACT_ATTR_LIST) { - - /* Oops, summary and auxiliary encountered */ - *field_id = E_CONTACT_FIELD_LAST; - return FALSE; - } - - } else if (test->field_id != *field_id) { - /* Auxiliary and something else encountered */ - *field_id = E_CONTACT_FIELD_LAST; - return FALSE; - } - - return TRUE; -} - -/* fetch_sub_groups_cb will list the index of each component of a sub, - * unless not every subgroup was isolated, in which case the - * PreflightAndData->isolated will be set to FALSE. - */ -typedef struct { - QueryElement **elements; - gint n_elements; - - gboolean isolated; - gboolean checked; - - GSList *offsets; - GSList *fields; -} PreflightAndData; - -static gboolean -fetch_sub_groups_cb (QueryElement *element, - gint sub_level, - gint offset, - gpointer user_data) -{ - PreflightAndData *data = (PreflightAndData *)user_data; - - data->checked = TRUE; - - if (sub_level == 1 && element->query < BOOK_QUERY_SUB_FIRST) { - - QueryFieldTest *test = (QueryFieldTest *)element; - - data->offsets = - g_slist_prepend (data->offsets, - GINT_TO_POINTER (offset)); - data->fields = - g_slist_prepend (data->fields, - GUINT_TO_POINTER (test->field_id)); - - } else if (sub_level == 2 && - element->query >= BOOK_QUERY_SUB_FIRST && - element->query != BOOK_QUERY_SUB_END) { - - EContactField field_id = E_CONTACT_FIELD_LAST + 1; - - query_preflight_foreach_sub (data->elements, - data->n_elements, - offset, FALSE, - check_isolated_cb, - &field_id); - - if (field_id == E_CONTACT_FIELD_LAST) { - data->isolated = FALSE; - } else { - data->offsets = - g_slist_prepend (data->offsets, - GINT_TO_POINTER (offset)); - data->fields = - g_slist_prepend (data->fields, - GUINT_TO_POINTER (field_id)); - } - } - - return (data->isolated != FALSE); -} - -static void -query_preflight_optimize_and (PreflightContext *context, - EBookSqlite *ebsql, - QueryElement **elements, - gint n_elements) -{ - PreflightAndData data = { elements, n_elements, TRUE, FALSE, NULL, NULL }; - - /* First, find the indexes to the various toplevel elements */ - query_preflight_foreach_sub (elements, - n_elements, - 0, TRUE, - fetch_sub_groups_cb, - &data); - - if (data.checked && data.isolated) { - GSList *l, *ll; - gint n_migrated = 0; - gint remaining; - - /* Lists are created in reverse, with higher offsets - * comming first, let's keep it this way. - * - * We can now migrate them one by one and the later - * offsets (i.e. lower offsets) in the list will not - * be invalid. This order should also reduce calls - * to memmove(). - */ - for (l = data.offsets, ll = data.fields; - l && ll; - l = l->next, ll = ll->next) { - gint offset = GPOINTER_TO_INT (l->data); - EContactField field_id = GPOINTER_TO_UINT (ll->data); - SummaryField *field; - - field = summary_field_get (ebsql, field_id); - - if (field && field->type == E_TYPE_CONTACT_ATTR_LIST) { - n_migrated++; - query_preflight_migrate_offset (context, field_id, offset); - } - } - - /* If there is only one statement left inside the AND clause - * in context->constraints, we need to remove the encapsulating - * AND statement. - */ - remaining = g_slist_length (data.offsets) - n_migrated; - if (remaining < 2) { - g_ptr_array_remove_index (context->constraints, 0); - g_ptr_array_remove_index (context->constraints, - context->constraints->len - 1); - } - } - - g_slist_free (data.offsets); - g_slist_free (data.fields); -} - -static void -query_preflight_optimize_toplevel (PreflightContext *context, - EBookSqlite *ebsql, - QueryElement **elements, - gint n_elements) -{ - EContactField field_id; - - if (elements[0]->query >= BOOK_QUERY_SUB_FIRST) { - - switch (elements[0]->query) { - case BOOK_QUERY_SUB_AND: - - /* AND components at the toplevel can be migrated, so long - * as each component is isolated - */ - query_preflight_optimize_and (context, ebsql, elements, n_elements); - break; - - case BOOK_QUERY_SUB_OR: - - /* OR at the toplevel can be migrated if limited to one table */ - field_id = E_CONTACT_FIELD_LAST + 1; - query_preflight_foreach_sub (elements, - n_elements, - 0, FALSE, - check_isolated_cb, - &field_id); - - if (field_id != 0 && - field_id != E_CONTACT_FIELD_LAST) { - - /* Isolated to an auxiliary table, let's migrate it */ - query_preflight_migrate_offset (context, field_id, 0); - } - break; - - case BOOK_QUERY_SUB_NOT: - - /* We dont support NOT operations on attribute lists as - * summarized queries, so there can not be any optimization - * made here. - */ - break; - - case BOOK_QUERY_SUB_END: - default: - g_warn_if_reached (); - break; - } - - } else { - - QueryFieldTest *test = (QueryFieldTest *)elements[0]; - - /* Toplevel field test should stand alone at the first position */ - - /* Special case of 'x-evolution-any-field' will have no SummaryField - * resolved in test->field - */ - if (test->field && test->field->type == E_TYPE_CONTACT_ATTR_LIST) - query_preflight_migrate_offset (context, test->field_id, 0); - } -} - -/* In this phase, we attempt to pull out field tests from the main constraints array - * which touch auxiliary tables and place them instead into their PreflightAuxData - * constraint arrays respectively. - * - * This will result in queries being generated using nested select statements before joining, - * allowing us to leverage the indexes we created in those. - * - * A query which would normally generate like this: - * ================================================ - * SELECT DISTINCT summary.uid, summary.vcard FROM 'folder_id' AS summary - * LEFT OUTER JOIN 'folder_id_phone_list' AS phone_list ON phone_list.uid = summary.uid - * LEFT OUTER JOIN 'folder_id_email_list' AS email_list ON email_list.uid = summary.uid - * WHERE (phone_list.value_reverse IS NOT NULL AND phone_list.value_reverse LIKE '0505%') - * AND (email_list.value IS NOT NULL AND email_list.value LIKE 'eddie%') - * - * After optimization, will be generated instead like so: - * ================================================ - * SELECT DISTINCT summary.uid, summary.vcard FROM ( - * SELECT DISTINCT phone_list.uid FROM 'folder_id_phone_list' AS phone_list - * WHERE (phone_list.value_reverse IS NOT NULL AND phone_list.value_reverse LIKE '0505%') - * ) AS phone_list_results - * LEFT OUTER JOIN ( - * SELECT DISTINCT email_list.uid FROM 'folder_id_email_list' AS email_list - * WHERE (email_list.value IS NOT NULL AND email_list.value LIKE 'eddie%') - * ) AS email_list_results ON email_list_results.uid = phone_list_results.uid - * LEFT OUTER JOIN 'folder_id' AS summary ON summary.uid = email_list_results.uid - * WHERE summary.uid IS NOT NULL - * - * Currently we make the following assumptions when optimizing the query: - * - * o Any shallow query with only one auxiliary table constraint can have - * the auxiliary constraint migrated into the nested select - * - * o Any grouped query which contains constraints which access the same - * table can be considered an atomic constraint and is a suitable target - * for migration. - * - * o Any toplevel AND query which contains one or more summary table constraints - * and one or more auxiliary table constraints, can have the auxiliary - * table constraints migrated into the nested select. - * - */ -static void -query_preflight_optimize (PreflightContext *context, - EBookSqlite *ebsql) -{ - QueryElement **elements; - gint n_elements; - - if (context->constraints && - context->constraints->len > 0) { - - elements = (QueryElement **)context->constraints->pdata; - n_elements = context->constraints->len; - - query_preflight_optimize_toplevel (context, ebsql, elements, n_elements); - } - - - /* In any case that we did have constraints, add an (exists "uid") constraint - * to the end, this is because it's possible for the optimization above to return - * some NULL rows - */ - if (context->constraints && - context->constraints->len == 0) { - constraints_insert_field_test (context->constraints, 0, - summary_field_get (ebsql, E_CONTACT_UID), - BOOK_QUERY_EXISTS, NULL); - } else { - /* AND it with the remaining constraints */ - constraints_insert_delimiter (context->constraints, 0, BOOK_QUERY_SUB_AND); - constraints_insert_field_test (context->constraints, -1, - summary_field_get (ebsql, E_CONTACT_UID), - BOOK_QUERY_EXISTS, NULL); - constraints_insert_delimiter (context->constraints, -1, BOOK_QUERY_SUB_END); - } -} - static void query_preflight (PreflightContext *context, EBookSqlite *ebsql, @@ -4881,8 +4447,6 @@ query_preflight (PreflightContext *context, /* Handle E_CONTACT_FULL_NAME substitutions */ query_preflight_substitute_full_name (context, ebsql); - /* Optimize queries which touch auxiliary columns */ - /* query_preflight_optimize (context, ebsql); */ } else { /* We might use this context to perform a fallback query, @@ -5354,18 +4918,9 @@ ebsql_generate_constraints (EBookSqlite *ebsql, sub_query_context_free (ctx); } - -/* SELECT DISTINCT summary.uid, summary.vcard FROM folder_id AS summary */ -/* JOIN folder_id_phone_list AS phone_list */ -/* ON phone_list.uid = summary.uid */ -/* JOIN folder_id_email_list AS email_list */ -/* ON email_list.uid = summary.uid */ -/* WHERE phone_list.value LIKE '%0505' AND email_list.value LIKE 'eddie%'; */ - - -/* Generates the SELECT portion of the query, this will possibly add some - * of the constraints into nested selects. Constraints that could not be - * nested will have their symbolic table names in context. +/* Generates the SELECT portion of the query, this will take care of + * preparing the context of the query, and add the needed JOIN statements + * based on which fields are referenced in the query expression. * * This also handles getting the correct callback and asking for the * right data depending on the 'search_type' @@ -5377,11 +4932,12 @@ ebsql_generate_select (EBookSqlite *ebsql, PreflightContext *context, GError **error) { - GSList *l; EbSqlRowFunc callback = NULL; + gint i; g_string_append (string, "SELECT "); - if (context->aux_fields) + if (context->status == PREFLIGHT_OK && + context->aux_mask != 0) g_string_append (string, "DISTINCT "); switch (search_type) { @@ -5401,7 +4957,7 @@ ebsql_generate_select (EBookSqlite *ebsql, break; case SEARCH_COUNT: callback = get_count_cb; - if (context->aux_fields) + if (context->aux_mask != 0) g_string_append (string, "count (DISTINCT summary.uid) "); else g_string_append (string, "count (*) "); @@ -5410,16 +4966,20 @@ ebsql_generate_select (EBookSqlite *ebsql, ebsql_string_append_printf (string, "FROM %Q AS summary", ebsql->priv->folderid); - for (l = context->aux_fields; l; l = l->next) { - PreflightAuxData *aux_data = (PreflightAuxData *)l->data; - SummaryField *field = summary_field_get (ebsql, aux_data->field_id); + /* Add any required auxiliary tables into the query context */ + if (context->status == PREFLIGHT_OK) { + for (i = 0; i < ebsql->priv->n_summary_fields; i++) { + /* We cap this at EBSQL_MAX_SUMMARY_FIELDS (64 bits) at creation time */ + if ((context->aux_mask & (1 << i)) != 0) { + SummaryField *field = &(ebsql->priv->summary_fields[i]); - /* Join the table in the normal way and leave the constraints for later */ - ebsql_string_append_printf (string, " JOIN %Q AS %s ON %s.uid = summary.uid", - field->aux_table, - field->aux_table_symbolic, - field->aux_table_symbolic); + ebsql_string_append_printf (string, " JOIN %Q AS %s ON %s.uid = summary.uid", + field->aux_table, + field->aux_table_symbolic, + field->aux_table_symbolic); + } + } } return callback; @@ -5713,8 +5273,6 @@ ebsql_cursor_setup_query (EBookSqlite *ebsql, cursor->sexp = NULL; } else { /* Generate the constraints for our queries - * - * It can be that they are optimized into the select segment */ string = g_string_new (NULL); ebsql_generate_constraints (ebsql, @@ -6285,7 +5843,12 @@ e_book_sqlite_new_full (const gchar *path, 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) { + if (n_fields <= 0 || n_fields >= EBSQL_MAX_SUMMARY_FIELDS) { + + if (n_fields) + g_warning ("EBookSqlite refused to create addressbook with over %d summary fields", + EBSQL_MAX_SUMMARY_FIELDS); + ebsql = ebsql_new_default (path, vcard_callback, change_callback, -- cgit v1.2.1