[evolution-data-server/wip/offline-cache] Add a test for search expressions of ECalCache



commit ae5bebefcbd3e7d25ecfc003e2dc4e7fb973d65e
Author: Milan Crha <mcrha redhat com>
Date:   Tue Feb 21 22:34:56 2017 +0100

    Add a test for search expressions of ECalCache

 src/calendar/libedata-cal/e-cal-backend-sexp.c |   28 +-
 src/calendar/libedata-cal/e-cal-cache.c        |  284 ++++++++++-----
 tests/libedata-cal/CMakeLists.txt              |    1 +
 tests/libedata-cal/components/event-2.ics      |    2 +
 tests/libedata-cal/components/event-3.ics      |    1 +
 tests/libedata-cal/components/event-4.ics      |    2 +
 tests/libedata-cal/components/event-6-a.ics    |   13 +
 tests/libedata-cal/components/event-6.ics      |   12 +
 tests/libedata-cal/components/event-7.ics      |   13 +
 tests/libedata-cal/components/event-8.ics      |   16 +
 tests/libedata-cal/components/event-9.ics      |   17 +
 tests/libedata-cal/components/task-1.ics       |    9 +
 tests/libedata-cal/components/task-2.ics       |   11 +
 tests/libedata-cal/components/task-3.ics       |   13 +
 tests/libedata-cal/components/task-4.ics       |   13 +
 tests/libedata-cal/components/task-5.ics       |   13 +
 tests/libedata-cal/components/task-6.ics       |   14 +
 tests/libedata-cal/components/task-7.ics       |   17 +
 tests/libedata-cal/components/task-8.ics       |   11 +
 tests/libedata-cal/components/task-9.ics       |   11 +
 tests/libedata-cal/test-cal-cache-intervals.c  |   35 ++-
 tests/libedata-cal/test-cal-cache-offline.c    |    2 +-
 tests/libedata-cal/test-cal-cache-search.c     |  470 ++++++++++++++++++++++++
 tests/libedata-cal/test-cal-cache-utils.c      |   27 ++-
 tests/libedata-cal/test-cal-cache-utils.h      |    8 +-
 25 files changed, 936 insertions(+), 107 deletions(-)
---
diff --git a/src/calendar/libedata-cal/e-cal-backend-sexp.c b/src/calendar/libedata-cal/e-cal-backend-sexp.c
index c2c96fe..466d9d2 100644
--- a/src/calendar/libedata-cal/e-cal-backend-sexp.c
+++ b/src/calendar/libedata-cal/e-cal-backend-sexp.c
@@ -536,23 +536,24 @@ matches_any (ECalComponent *comp,
 static gboolean
 matches_priority (ECalComponent *comp ,const gchar *pr)
 {
+       gboolean res = FALSE;
        gint *priority = NULL;
 
        e_cal_component_get_priority (comp, &priority);
 
-       if (!priority || !*priority)
-               return FALSE;
+       if (!priority)
+               return g_str_equal (pr, "UNDEFINED");
 
        if (g_str_equal (pr, "HIGH") && *priority <= 4)
-               return TRUE;
+               res = TRUE;
        else if (g_str_equal (pr, "NORMAL") && *priority == 5)
-               return TRUE;
+               res = TRUE;
        else if (g_str_equal (pr, "LOW") && *priority > 5)
-               return TRUE;
-       else if (g_str_equal (pr, "UNDEFINED") && (!priority || !*priority))
-               return TRUE;
+               res = TRUE;
 
-       return FALSE;
+       e_cal_component_free_priority (priority);
+
+       return res;
 }
 
 static gboolean
@@ -638,10 +639,13 @@ func_percent_complete (ESExp *esexp,
 
        e_cal_component_get_percent (ctx->comp, &percent);
 
-       if (percent && *percent) {
-               result = e_sexp_result_new (esexp, ESEXP_RES_INT);
-               result->value.number = *percent;
+       result = e_sexp_result_new (esexp, ESEXP_RES_INT);
 
+       if (percent) {
+               result->value.number = *percent;
+               e_cal_component_free_percent (percent);
+       } else {
+               result->value.number = -1;
        }
 
        return result;
@@ -935,6 +939,8 @@ func_has_categories (ESExp *esexp,
                result = e_sexp_result_new (esexp, ESEXP_RES_BOOL);
                result->value.boolean = FALSE;
 
+               e_cal_component_free_categories_list (categories);
+
                return result;
        }
 
diff --git a/src/calendar/libedata-cal/e-cal-cache.c b/src/calendar/libedata-cal/e-cal-cache.c
index d6eecb2..eff7407 100644
--- a/src/calendar/libedata-cal/e-cal-cache.c
+++ b/src/calendar/libedata-cal/e-cal-cache.c
@@ -256,6 +256,21 @@ ecc_check_sexp_func (sqlite3_context *context,
        g_object_unref (sexp_obj);
 }
 
+/* negate(x) */
+static void
+ecc_negate_func (sqlite3_context *context,
+                gint argc,
+                sqlite3_value **argv)
+{
+       gint val;
+
+       g_return_if_fail (context != NULL);
+       g_return_if_fail (argc == 1);
+
+       val = sqlite3_value_int (argv[0]);
+       sqlite3_result_int (context, !val);
+}
+
 static gboolean
 e_cal_cache_get_string (ECache *cache,
                        gint ncols,
@@ -362,7 +377,7 @@ ecc_decode_id_sql (const gchar *id,
        if (!*id)
                return FALSE;
 
-       split = g_strsplit (id, "\n", 1);
+       split = g_strsplit (id, "\n", 2);
 
        if (!split || !split[0] || !*split[0]) {
                g_strfreev (split);
@@ -655,29 +670,29 @@ ecc_get_status_as_string (icalproperty_status status)
 {
        switch (status) {
        case ICAL_STATUS_NONE:
-               return "NOT STARTED";
+               return "not started";
        case ICAL_STATUS_COMPLETED:
-               return "COMPLETED";
+               return "completed";
        case ICAL_STATUS_CANCELLED:
-               return "CANCELLED";
+               return "cancelled";
        case ICAL_STATUS_INPROCESS:
-               return "IN PROGRESS";
+               return "in progress";
        case ICAL_STATUS_NEEDSACTION:
-               return "NEEDS ACTION";
+               return "needs action";
        case ICAL_STATUS_TENTATIVE:
-               return "TENTATIVE";
+               return "tentative";
        case ICAL_STATUS_CONFIRMED:
-               return "CONFIRMED";
+               return "confirmed";
        case ICAL_STATUS_DRAFT:
-               return "DRAFT";
+               return "draft";
        case ICAL_STATUS_FINAL:
-               return "FINAL";
+               return "final";
        case ICAL_STATUS_SUBMITTED:
-               return "SUBMITTED";
+               return "submitted";
        case ICAL_STATUS_PENDING:
-               return "PENDING";
+               return "pending";
        case ICAL_STATUS_FAILED:
-               return "FAILED";
+               return "failed";
        case ICAL_STATUS_X:
                break;
        }
@@ -693,9 +708,9 @@ ecc_fill_other_columns (ECalCache *cal_cache,
        time_t occur_start = -1, occur_end = -1;
        ECalComponentDateTime dt;
        ECalComponentText text;
-       ECalComponentClassification classification;
+       ECalComponentClassification classification = E_CAL_COMPONENT_CLASS_PUBLIC;
        icalcomponent *icalcomp;
-       icalproperty_status status;
+       icalproperty_status status = ICAL_STATUS_NONE;
        struct icaltimetype *itt;
        const gchar *str = NULL;
        gint *pint = NULL;
@@ -724,6 +739,8 @@ ecc_fill_other_columns (ECalCache *cal_cache,
        itt = NULL;
        e_cal_component_get_completed (comp, &itt);
        add_value (ECC_COLUMN_COMPLETED, itt ? ecc_encode_itt_to_sql (*itt) : NULL);
+       if (itt)
+               e_cal_component_free_icaltimetype (itt);
 
        text.value = NULL;
        e_cal_component_get_summary (comp, &text);
@@ -740,15 +757,19 @@ ecc_fill_other_columns (ECalCache *cal_cache,
 
        e_cal_component_get_priority (comp, &pint);
        add_value (ECC_COLUMN_PRIORITY, pint && *pint ? g_strdup_printf ("%d", *pint) : NULL);
+       if (pint)
+               e_cal_component_free_priority (pint);
 
        e_cal_component_get_percent (comp, &pint);
        add_value (ECC_COLUMN_PERCENT_COMPLETE, pint && *pint ? g_strdup_printf ("%d", *pint) : NULL);
+       if (pint)
+               e_cal_component_free_percent (pint);
 
        has = e_cal_component_has_alarms (comp);
        add_value (ECC_COLUMN_HAS_ALARM, g_strdup (has ? "1" : "0"));
 
        has = e_cal_component_has_attachments (comp);
-       add_value (ECC_COLUMN_HAS_ALARM, g_strdup (has ? "1" : "0"));
+       add_value (ECC_COLUMN_HAS_ATTACHMENT, g_strdup (has ? "1" : "0"));
 
        e_cal_component_get_dtstart (comp, &dt);
        has = dt.value != NULL;
@@ -775,7 +796,7 @@ ecc_range_as_where_clause (const gchar *start_str,
        if (!start_str && !end_str)
                return NULL;
 
-       stmt = g_string_new ("");
+       stmt = g_string_sized_new (64);
 
        if (start_str) {
                e_cache_sqlite_stmt_append_printf (stmt,
@@ -802,7 +823,8 @@ ecc_range_as_where_clause (const gchar *start_str,
 
 typedef struct _SExpToSqlContext {
        ECalCache *cal_cache;
-       gint sexp_id;
+       guint not_level;
+       gboolean requires_check_sexp;
 } SExpToSqlContext;
 
 static ESExpResult *
@@ -824,20 +846,19 @@ ecc_sexp_func_and_or (ESExp *esexp,
        for (ii = 0; ii < argc; ii++) {
                r1 = e_sexp_term_eval (esexp, argv[ii]);
 
-               if (stmt->len > 1)
-                       g_string_append_printf (stmt, " %s ", oper);
+               if (r1 && r1->type == ESEXP_RES_STRING && r1->value.string) {
+                       if (stmt->len > 1)
+                               g_string_append_printf (stmt, " %s ", oper);
 
-               if (r1 && r1->type == ESEXP_RES_STRING) {
                        g_string_append_printf (stmt, "(%s)", r1->value.string);
                } else {
-                       g_string_append_printf (stmt, "check_sexp(%d,%s)",
-                               ctx->sexp_id, E_CACHE_COLUMN_OBJECT);
+                       ctx->requires_check_sexp = TRUE;
                }
 
                e_sexp_result_free (esexp, r1);
        }
 
-       if (stmt->len == 1) {
+       if (stmt->len == 1 && !ctx->not_level) {
                if (g_str_equal (oper, "AND"))
                        g_string_append_c (stmt, '1');
                else
@@ -847,7 +868,7 @@ ecc_sexp_func_and_or (ESExp *esexp,
        g_string_append_c (stmt, ')');
 
        result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
-       result->value.string = g_string_free (stmt, FALSE);
+       result->value.string = g_string_free (stmt, stmt->len <= 2);
 
        return result;
 }
@@ -873,11 +894,11 @@ ecc_sexp_func_or (ESExp *esexp,
 static ESExpResult *
 ecc_sexp_func_not (ESExp *esexp,
                   gint argc,
-                  ESExpResult **argv,
+                  ESExpTerm **argv,
                   gpointer user_data)
 {
        SExpToSqlContext *ctx = user_data;
-       ESExpResult *result;
+       ESExpResult *result, *r1;
 
        g_return_val_if_fail (ctx != NULL, NULL);
 
@@ -886,15 +907,20 @@ ecc_sexp_func_not (ESExp *esexp,
 
        result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
 
-       if (!argv[0] || argv[0]->type != ESEXP_RES_STRING) {
-               result->value.string =
-                       g_strdup_printf ("check_sexp(%d,%s)",
-                               ctx->sexp_id,
-                               E_CACHE_COLUMN_OBJECT);
+       ctx->not_level++;
+
+       r1 = e_sexp_term_eval (esexp, argv[0]);
+
+       ctx->not_level--;
+
+       if (r1 && r1->type == ESEXP_RES_STRING && r1->value.string) {
+               result->value.string = g_strdup_printf ("negate(%s)", r1->value.string);
        } else {
-               result->value.string = g_strdup_printf ("NOT (%s)", argv[0]->value.string);
+               ctx->requires_check_sexp = TRUE;
        }
 
+       e_sexp_result_free (esexp, r1);
+
        return result;
 }
 
@@ -942,9 +968,6 @@ ecc_sexp_func_occur_in_time_range (ESExp *esexp,
 {
        SExpToSqlContext *ctx = user_data;
        ESExpResult *result;
-       icaltimezone *default_zone = NULL;
-       struct icaltimetype itt_start, itt_end;
-       gchar *start_str, *end_str;
 
        g_return_val_if_fail (ctx != NULL, NULL);
 
@@ -955,23 +978,31 @@ ecc_sexp_func_occur_in_time_range (ESExp *esexp,
                return NULL;
        }
 
-       if (argc == 3)
-               default_zone = ecc_resolve_tzid_cb (argv[2]->value.string, ctx->cal_cache);
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
 
-       itt_start = icaltime_from_timet_with_zone (argv[0]->value.time, 0, default_zone);
-       itt_end = icaltime_from_timet_with_zone (argv[1]->value.time, 0, default_zone);
+       if (!ctx->not_level) {
+               struct icaltimetype itt_start, itt_end;
+               gchar *start_str, *end_str;
 
-       start_str = ecc_encode_itt_to_sql (itt_start);
-       end_str = ecc_encode_itt_to_sql (itt_end);
+               /* The default zone argument, if any, is ignored here */
+               itt_start = icaltime_from_timet_with_zone (argv[0]->value.time, 0, NULL);
+               itt_end = icaltime_from_timet_with_zone (argv[1]->value.time, 0, NULL);
 
-       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
-       result->value.string = ecc_range_as_where_clause (start_str, end_str);
+               start_str = ecc_encode_itt_to_sql (itt_start);
+               end_str = ecc_encode_itt_to_sql (itt_end);
 
-       if (!result->value.string)
-               result->value.string = g_strdup ("1==1");
+               result->value.string = ecc_range_as_where_clause (start_str, end_str);
 
-       g_free (start_str);
-       g_free (end_str);
+               if (!result->value.string)
+                       result->value.string = g_strdup ("1=1");
+
+               g_free (start_str);
+               g_free (end_str);
+       } else {
+               result->value.string = NULL;
+       }
+
+       ctx->requires_check_sexp = TRUE;
 
        return result;
 }
@@ -1057,7 +1088,23 @@ ecc_sexp_func_contains (ESExp *esexp,
        } else if (column) {
                gchar *stmt;
 
-               stmt = e_cache_sqlite_stmt_printf ("%s LIKE '%%%q%%'", column, str);
+               if (g_str_equal (column, ECC_COLUMN_PRIORITY)) {
+                       if (g_ascii_strcasecmp (str, "UNDEFINED") == 0)
+                               stmt = e_cache_sqlite_stmt_printf ("%s IS NULL", column);
+                       else if (g_ascii_strcasecmp (str, "HIGH") == 0)
+                               stmt = e_cache_sqlite_stmt_printf ("%s<=4", column);
+                       else if (g_ascii_strcasecmp (str, "NORMAL") == 0)
+                               stmt = e_cache_sqlite_stmt_printf ("%s=5", column);
+                       else if (g_ascii_strcasecmp (str, "LOW") == 0)
+                               stmt = e_cache_sqlite_stmt_printf ("%s>5", column);
+                       else
+                               stmt = e_cache_sqlite_stmt_printf ("%s IS NOT NULL", column);
+               } else if (g_str_equal (column, ECC_COLUMN_CLASSIFICATION) ||
+                          g_str_equal (column, ECC_COLUMN_STATUS)) {
+                       stmt = e_cache_sqlite_stmt_printf ("%s='%q'", column, str);
+               } else {
+                       stmt = e_cache_sqlite_stmt_printf ("%s LIKE '%%%q%%'", column, str);
+               }
                result->value.string = g_strdup (stmt);
                e_cache_sqlite_stmt_free (stmt);
        } else if (g_str_equal (field, "any")) {
@@ -1072,9 +1119,7 @@ ecc_sexp_func_contains (ESExp *esexp,
 
                result->value.string = g_string_free (stmt, FALSE);
        } else {
-               g_strdup_printf ("check_sexp(%d,%s)",
-                       ctx->sexp_id,
-                       E_CACHE_COLUMN_OBJECT);
+               ctx->requires_check_sexp = TRUE;
        }
 
        g_free (str);
@@ -1119,6 +1164,28 @@ ecc_sexp_func_has_alarms (ESExp *esexp,
 }
 
 static ESExpResult *
+ecc_sexp_func_has_alarms_in_range (ESExp *esexp,
+                                  gint argc,
+                                  ESExpResult **argv,
+                                  gpointer user_data)
+{
+       SExpToSqlContext *ctx = user_data;
+       ESExpResult *result;
+
+       g_return_val_if_fail (ctx != NULL, NULL);
+
+       ctx->requires_check_sexp = TRUE;
+
+       if (!ctx->not_level)
+               return ecc_sexp_func_has_alarms (esexp, argc, argv, user_data);
+
+       result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
+       result->value.string = NULL;
+
+       return result;
+}
+
+static ESExpResult *
 ecc_sexp_func_has_recurrences (ESExp *esexp,
                               gint argc,
                               ESExpResult **argv,
@@ -1280,10 +1347,9 @@ ecc_sexp_func_check_sexp (ESExp *esexp,
        g_return_val_if_fail (ctx != NULL, NULL);
 
        result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
-       result->value.string =
-               g_strdup_printf ("check_sexp(%d,%s)",
-                       ctx->sexp_id,
-                       E_CACHE_COLUMN_OBJECT);
+       result->value.string = NULL;
+
+       ctx->requires_check_sexp = TRUE;
 
        return result;
 }
@@ -1300,10 +1366,9 @@ ecc_sexp_func_icheck_sexp (ESExp *esexp,
        g_return_val_if_fail (ctx != NULL, NULL);
 
        result = e_sexp_result_new (esexp, ESEXP_RES_STRING);
-       result->value.string =
-               g_strdup_printf ("check_sexp(%d,%s)",
-                       ctx->sexp_id,
-                       E_CACHE_COLUMN_OBJECT);
+       result->value.string = NULL;
+
+       ctx->requires_check_sexp = TRUE;
 
        return result;
 }
@@ -1315,7 +1380,7 @@ static struct {
 } symbols[] = {
        { "and",                        ecc_sexp_func_and, 1 },
        { "or",                         ecc_sexp_func_or, 1 },
-       { "not",                        ecc_sexp_func_not, 0 },
+       { "not",                        ecc_sexp_func_not, 1 },
        { "<",                          ecc_sexp_func_icheck_sexp, 1 },
        { ">",                          ecc_sexp_func_icheck_sexp, 1 },
        { "=",                          ecc_sexp_func_icheck_sexp, 1 },
@@ -1340,7 +1405,7 @@ static struct {
        { "contains?",                  ecc_sexp_func_contains, 0 },
        { "has-start?",                 ecc_sexp_func_has_start, 0 },
        { "has-alarms?",                ecc_sexp_func_has_alarms, 0 },
-       { "has-alarms-in-range?",       ecc_sexp_func_check_sexp, 0 },
+       { "has-alarms-in-range?",       ecc_sexp_func_has_alarms_in_range, 0 },
        { "has-recurrences?",           ecc_sexp_func_has_recurrences, 0 },
        { "has-categories?",            ecc_sexp_func_has_categories, 0 },
        { "is-completed?",              ecc_sexp_func_is_completed, 0 },
@@ -1372,7 +1437,8 @@ ecc_convert_sexp_to_sql (ECalCache *cal_cache,
                return TRUE;
 
        ctx.cal_cache = cal_cache;
-       ctx.sexp_id = sexp_id;
+       ctx.not_level = 0;
+       ctx.requires_check_sexp = FALSE;
 
        sexp_parser = e_sexp_new ();
 
@@ -1394,9 +1460,19 @@ ecc_convert_sexp_to_sql (ECalCache *cal_cache,
 
                if (result) {
                        if (result->type == ESEXP_RES_STRING) {
-                               /* Just steal the string from the ESexpResult */
-                               *out_where_clause = result->value.string;
-                               result->value.string = NULL;
+                               if (ctx.requires_check_sexp) {
+                                       if (result->value.string) {
+                                               *out_where_clause = g_strdup_printf ("((%s) AND 
check_sexp(%d,%s))",
+                                                       result->value.string, sexp_id, E_CACHE_COLUMN_OBJECT);
+                                       } else {
+                                               *out_where_clause = g_strdup_printf ("check_sexp(%d,%s)",
+                                                       sexp_id, E_CACHE_COLUMN_OBJECT);
+                                       }
+                               } else {
+                                       /* Just steal the string from the ESExpResult */
+                                       *out_where_clause = result->value.string;
+                                       result->value.string = NULL;
+                               }
                                success = TRUE;
                        }
                }
@@ -1530,6 +1606,14 @@ ecc_init_sqlite_functions (ECalCache *cal_cache,
                cal_cache, ecc_check_sexp_func,
                NULL, NULL);
 
+       if (ret == SQLITE_OK) {
+               /* negate(x) */
+               ret = sqlite3_create_function (sqlitedb,
+                       "negate", 1, SQLITE_UTF8 | SQLITE_DETERMINISTIC,
+                       NULL, ecc_negate_func,
+                       NULL, NULL);
+       }
+
        if (ret != SQLITE_OK) {
                const gchar *errmsg = sqlite3_errmsg (sqlitedb);
 
@@ -2267,6 +2351,26 @@ e_cal_cache_get_components_in_range (ECalCache *cal_cache,
        return success;
 }
 
+static gboolean
+ecc_search_icalstrings_cb (ECalCache *cal_cache,
+                          const gchar *uid,
+                          const gchar *rid,
+                          const gchar *revision,
+                          const gchar *object,
+                          const gchar *extra,
+                          EOfflineState offline_state,
+                          gpointer user_data)
+{
+       GSList **out_icalstrings = user_data;
+
+       g_return_val_if_fail (out_icalstrings != NULL, FALSE);
+       g_return_val_if_fail (object != NULL, FALSE);
+
+       *out_icalstrings = g_slist_prepend (*out_icalstrings, g_strdup (object));
+
+       return TRUE;
+}
+
 /**
  * e_cal_cache_get_components_in_range_as_strings:
  * @cal_cache: an #ECalCache
@@ -2290,8 +2394,8 @@ e_cal_cache_get_components_in_range_as_strings (ECalCache *cal_cache,
                                                GCancellable *cancellable,
                                                GError **error)
 {
-       GString *stmt;
-       gchar *range_start_str, *range_end_str, *range_where_clause;
+       gchar *sexp;
+       struct icaltimetype itt_start, itt_end;
        gboolean success;
 
        g_return_val_if_fail (E_IS_CAL_CACHE (cal_cache), FALSE);
@@ -2299,26 +2403,24 @@ e_cal_cache_get_components_in_range_as_strings (ECalCache *cal_cache,
 
        *out_icalstrings = NULL;
 
-       range_start_str = ecc_encode_timet_to_sql (cal_cache, range_start);
-       range_end_str = ecc_encode_timet_to_sql (cal_cache, range_end);
-       range_where_clause = ecc_range_as_where_clause (range_start_str, range_end_str);
+       itt_start = icaltime_from_timet_with_zone (range_start, FALSE, NULL);
+       itt_end = icaltime_from_timet_with_zone (range_end, FALSE, NULL);
 
-       /* Using 'ORDER BY' to get the master object first */
-       stmt = g_string_new ("SELECT " E_CACHE_COLUMN_OBJECT " FROM " E_CACHE_TABLE_OBJECTS);
-
-       if (range_where_clause) {
-               g_string_append (stmt, " WHERE ");
-               g_string_append (stmt, range_where_clause);
-       }
+       sexp = g_strdup_printf ("(occur-in-time-range? (make-time \"%04d%02d%02dT%02d%02d%02dZ\") (make-time 
\"%04d%02d%02dT%02d%02d%02dZ\"))",
+               itt_start.year, itt_start.month, itt_start.day, itt_start.hour, itt_start.minute, 
itt_start.second,
+               itt_end.year, itt_end.month, itt_end.day, itt_end.hour, itt_end.minute, itt_end.second);
 
-       g_string_append (stmt, " ORDER BY " E_CACHE_COLUMN_UID);
+       success = e_cal_cache_search_with_callback (cal_cache, sexp, ecc_search_icalstrings_cb,
+               out_icalstrings, cancellable, error);
 
-       success = e_cache_sqlite_select (E_CACHE (cal_cache), stmt->str, e_cal_cache_get_strings, 
out_icalstrings, cancellable, error);
+       g_free (sexp);
 
-       g_string_free (stmt, TRUE);
-       g_free (range_where_clause);
-       g_free (range_start_str);
-       g_free (range_end_str);
+       if (success) {
+               *out_icalstrings = g_slist_reverse (*out_icalstrings);
+       } else {
+               g_slist_free_full (*out_icalstrings, g_free);
+               *out_icalstrings = NULL;
+       }
 
        return success;
 }
@@ -3035,10 +3137,18 @@ ecc_get_cached_timezone (ETimezoneCache *cache,
 
        /* See if we already have it in the cache. */
        zone = g_hash_table_lookup (cal_cache->priv->loaded_timezones, tzid);
+       if (zone)
+               goto exit;
 
-       if (zone != NULL)
+       zone = g_hash_table_lookup (cal_cache->priv->modified_timezones, tzid);
+       if (zone)
                goto exit;
 
+       /* Try the location first */
+       /*zone = icaltimezone_get_builtin_timezone (tzid);
+       if (zone)
+               goto exit;*/
+
        /* Try to replace the original time zone with a more complete
         * and/or potentially updated built-in time zone.  Note this also
         * applies to TZIDs which match built-in time zones exactly: they
@@ -3046,10 +3156,10 @@ ecc_get_cached_timezone (ETimezoneCache *cache,
 
        builtin_tzid = e_cal_match_tzid (tzid);
 
-       if (builtin_tzid != NULL)
+       if (builtin_tzid)
                builtin_zone = icaltimezone_get_builtin_timezone_from_tzid (builtin_tzid);
 
-       if (builtin_zone == NULL) {
+       if (!builtin_zone) {
                e_cal_cache_get_timezone (cal_cache, tzid, &zone, NULL, NULL);
                goto exit;
        }
diff --git a/tests/libedata-cal/CMakeLists.txt b/tests/libedata-cal/CMakeLists.txt
index eb70d3c..4f0b24e 100644
--- a/tests/libedata-cal/CMakeLists.txt
+++ b/tests/libedata-cal/CMakeLists.txt
@@ -80,6 +80,7 @@ set(TESTS
        test-intervaltree
        test-cal-cache-offline
        test-cal-cache-intervals
+       test-cal-cache-search
 )
 
 foreach(_test ${TESTS})
diff --git a/tests/libedata-cal/components/event-2.ics b/tests/libedata-cal/components/event-2.ics
index 3b6efef..ce980be 100644
--- a/tests/libedata-cal/components/event-2.ics
+++ b/tests/libedata-cal/components/event-2.ics
@@ -9,4 +9,6 @@ DTEND:20170103T083000Z
 SUMMARY:First working day
 DESCRIPTION:Multiline\n
  description text
+CLASS:CONFIDENTIAL
+CATEGORIES:Work,Hard
 END:VEVENT
diff --git a/tests/libedata-cal/components/event-3.ics b/tests/libedata-cal/components/event-3.ics
index bbbaac5..4fcf5bf 100644
--- a/tests/libedata-cal/components/event-3.ics
+++ b/tests/libedata-cal/components/event-3.ics
@@ -8,4 +8,5 @@ DTSTART:20170104T100000Z
 DTEND:20170110T120000Z
 SUMMARY:Lunch prepare
 LOCATION:Kitchen
+CLASS:PRIVATE
 END:VEVENT
diff --git a/tests/libedata-cal/components/event-4.ics b/tests/libedata-cal/components/event-4.ics
index fd83a62..eb409ff 100644
--- a/tests/libedata-cal/components/event-4.ics
+++ b/tests/libedata-cal/components/event-4.ics
@@ -8,4 +8,6 @@ DTSTART:20170102T100000Z
 DTEND:20170102T180000Z
 SUMMARY:After-party clean up
 LOCATION:All around
+CLASS:PUBLIC
+CATEGORIES:Holiday,Work
 END:VEVENT
diff --git a/tests/libedata-cal/components/event-6-a.ics b/tests/libedata-cal/components/event-6-a.ics
new file mode 100644
index 0000000..404b51e
--- /dev/null
+++ b/tests/libedata-cal/components/event-6-a.ics
@@ -0,0 +1,13 @@
+BEGIN:VEVENT
+UID:event-6
+DTSTAMP:20170221T121736Z
+DTSTART;TZID=America/New_York:20170225T150000
+DTEND;TZID=America/New_York:20170225T160000
+SEQUENCE:2
+SUMMARY:Recurring with detached instance (3rd instance)
+TRANSP:OPAQUE
+CLASS:PUBLIC
+CREATED:20170221T125024Z
+LAST-MODIFIED:20170221T125341Z
+RECURRENCE-ID;TZID=America/New_York:20170225T134900
+END:VEVENT
diff --git a/tests/libedata-cal/components/event-6.ics b/tests/libedata-cal/components/event-6.ics
new file mode 100644
index 0000000..ee85a01
--- /dev/null
+++ b/tests/libedata-cal/components/event-6.ics
@@ -0,0 +1,12 @@
+BEGIN:VEVENT
+UID:event-6
+DTSTAMP:20170221T121736Z
+DTSTART;TZID=America/New_York:20170221T134900
+DTEND;TZID=America/New_York:20170221T144900
+SEQUENCE:1
+SUMMARY:Recurring with detached instance
+RRULE:FREQ=DAILY;COUNT=5;INTERVAL=2
+CLASS:PUBLIC
+CREATED:20170221T125024Z
+LAST-MODIFIED:20170221T125024Z
+END:VEVENT
diff --git a/tests/libedata-cal/components/event-7.ics b/tests/libedata-cal/components/event-7.ics
new file mode 100644
index 0000000..f1c0523
--- /dev/null
+++ b/tests/libedata-cal/components/event-7.ics
@@ -0,0 +1,13 @@
+BEGIN:VEVENT
+UID:event-7
+DTSTAMP:20170221T121736Z
+DTSTART;TZID=America/New_York:20170221T135000
+DTEND;TZID=America/New_York:20170221T145000
+SEQUENCE:1
+SUMMARY:With attachment
+TRANSP:OPAQUE
+ATTACH:file:///usr/share/icons/hicolor/48x48/apps/evolution.png
+CLASS:PUBLIC
+CREATED:20170221T125054Z
+LAST-MODIFIED:20170221T125054Z
+END:VEVENT
diff --git a/tests/libedata-cal/components/event-8.ics b/tests/libedata-cal/components/event-8.ics
new file mode 100644
index 0000000..89db909
--- /dev/null
+++ b/tests/libedata-cal/components/event-8.ics
@@ -0,0 +1,16 @@
+BEGIN:VEVENT
+UID:event-8
+DTSTAMP:20170221T121736Z
+DTSTART;TZID=America/New_York:20170225T160000
+DTEND;TZID=America/New_York:20170225T170000
+SEQUENCE:2
+ORGANIZER;CN=Bob:MAILTO:bob@no.where
+ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;
+ RSVP=TRUE;LANGUAGE=en:MAILTO:alice@no.where
+SUMMARY:Meet Alice
+COMMENT:User commentary text
+TRANSP:OPAQUE
+CLASS:PUBLIC
+CREATED:20170221T131322Z
+LAST-MODIFIED:20170221T131322Z
+END:VEVENT
diff --git a/tests/libedata-cal/components/event-9.ics b/tests/libedata-cal/components/event-9.ics
new file mode 100644
index 0000000..70b96f7
--- /dev/null
+++ b/tests/libedata-cal/components/event-9.ics
@@ -0,0 +1,17 @@
+BEGIN:VEVENT
+UID:event-9
+DTSTAMP:20170221T121736Z
+DTSTART;TZID=America/new_York:20170225T160000
+DTEND;TZID=America/new_York:20170225T170000
+SEQUENCE:2
+ORGANIZER;CN=Alice:MAILTO:alice@no.where
+ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;
+ RSVP=TRUE;CN=Bob;LANGUAGE=en:MAILTO:bob@no.where
+ATTENDEE;CUTYPE=INDIVIDUAL;ROLE=REQ-PARTICIPANT;PARTSTAT=NEEDS-ACTION;
+ RSVP=TRUE;CN=Charlie;LANGUAGE=en:MAILTO:charlie@no.where
+SUMMARY:2-on-1
+TRANSP:OPAQUE
+CLASS:PUBLIC
+CREATED:20170221T131421Z
+LAST-MODIFIED:20170221T131421Z
+END:VEVENT
diff --git a/tests/libedata-cal/components/task-1.ics b/tests/libedata-cal/components/task-1.ics
new file mode 100644
index 0000000..f64663c
--- /dev/null
+++ b/tests/libedata-cal/components/task-1.ics
@@ -0,0 +1,9 @@
+BEGIN:VTODO
+UID:task-1
+DTSTAMP:20170221T130109Z
+SUMMARY:Simple task
+CLASS:PUBLIC
+SEQUENCE:1
+CREATED:20170221T130123Z
+LAST-MODIFIED:20170221T130123Z
+END:VTODO
diff --git a/tests/libedata-cal/components/task-2.ics b/tests/libedata-cal/components/task-2.ics
new file mode 100644
index 0000000..64b83ab
--- /dev/null
+++ b/tests/libedata-cal/components/task-2.ics
@@ -0,0 +1,11 @@
+BEGIN:VTODO
+UID:task-2
+DTSTAMP:20170221T130109Z
+SUMMARY:With Due date
+DUE;TZID=America/New_York:20170313T000000
+PERCENT-COMPLETE:0
+CLASS:PUBLIC
+SEQUENCE:1
+CREATED:20170221T130208Z
+LAST-MODIFIED:20170221T130208Z
+END:VTODO
diff --git a/tests/libedata-cal/components/task-3.ics b/tests/libedata-cal/components/task-3.ics
new file mode 100644
index 0000000..e54e4fc
--- /dev/null
+++ b/tests/libedata-cal/components/task-3.ics
@@ -0,0 +1,13 @@
+BEGIN:VTODO
+UID:task-3
+DTSTAMP:20170221T130109Z
+SUMMARY:With completed
+STATUS:COMPLETED
+COMPLETED;America/New_York:20170221T000000
+PERCENT-COMPLETE:100
+CLASS:PUBLIC
+DESCRIPTION:Task having _with_ in description
+SEQUENCE:1
+CREATED:20170221T130319Z
+LAST-MODIFIED:20170221T130319Z
+END:VTODO
diff --git a/tests/libedata-cal/components/task-4.ics b/tests/libedata-cal/components/task-4.ics
new file mode 100644
index 0000000..82c36aa
--- /dev/null
+++ b/tests/libedata-cal/components/task-4.ics
@@ -0,0 +1,13 @@
+BEGIN:VTODO
+UID:task-4
+DTSTAMP:20170221T130109Z
+SUMMARY:With completed (2nd) and Due
+STATUS:COMPLETED
+DUE;TZID=America/New_York:20170301T000000
+COMPLETED;TZID=America/New_York:20170221T000000
+PERCENT-COMPLETE:100
+CLASS:PUBLIC
+SEQUENCE:1
+CREATED:20170221T130339Z
+LAST-MODIFIED:20170221T130339Z
+END:VTODO
diff --git a/tests/libedata-cal/components/task-5.ics b/tests/libedata-cal/components/task-5.ics
new file mode 100644
index 0000000..b813194
--- /dev/null
+++ b/tests/libedata-cal/components/task-5.ics
@@ -0,0 +1,13 @@
+BEGIN:VTODO
+UID:task-5
+DTSTAMP:20170221T130109Z
+SUMMARY:20% complete
+STATUS:IN-PROCESS
+PERCENT-COMPLETE:20
+CLASS:PUBLIC
+SEQUENCE:1
+PRIORITY:7
+CREATED:20170221T130411Z
+LAST-MODIFIED:20170221T130411Z
+LOCATION:Kitchen
+END:VTODO
diff --git a/tests/libedata-cal/components/task-6.ics b/tests/libedata-cal/components/task-6.ics
new file mode 100644
index 0000000..1ea7777
--- /dev/null
+++ b/tests/libedata-cal/components/task-6.ics
@@ -0,0 +1,14 @@
+BEGIN:VTODO
+UID:task-6
+DTSTAMP:20170221T130109Z
+SUMMARY:90% complete (Confidential)
+STATUS:IN-PROCESS
+PRIORITY:5
+PERCENT-COMPLETE:90
+CLASS:CONFIDENTIAL
+COMMENT:User commentary text
+SEQUENCE:2
+DTSTART;TZID=America/New_York:20131213T131313
+CREATED:20170221T130512Z
+LAST-MODIFIED:20170221T130610Z
+END:VTODO
diff --git a/tests/libedata-cal/components/task-7.ics b/tests/libedata-cal/components/task-7.ics
new file mode 100644
index 0000000..bd01312
--- /dev/null
+++ b/tests/libedata-cal/components/task-7.ics
@@ -0,0 +1,17 @@
+BEGIN:VTODO
+UID:task-7
+DTSTAMP:20170221T130109Z
+SUMMARY:55% complete (Private)
+STATUS:IN-PROCESS
+PERCENT-COMPLETE:55
+CLASS:PRIVATE
+SEQUENCE:3
+PRIORITY:2
+CREATED:20170221T130447Z
+LAST-MODIFIED:20170221T130618Z
+BEGIN:VALARM
+TRIGGER:-PT30M
+ACTION:DISPLAY
+DESCRIPTION:Reminder
+END:VALARM
+END:VTODO
diff --git a/tests/libedata-cal/components/task-8.ics b/tests/libedata-cal/components/task-8.ics
new file mode 100644
index 0000000..f3ca458
--- /dev/null
+++ b/tests/libedata-cal/components/task-8.ics
@@ -0,0 +1,11 @@
+BEGIN:VTODO
+UID:task-8
+DTSTAMP:20170221T130109Z
+SUMMARY:Status - Cancelled
+STATUS:CANCELLED
+PERCENT-COMPLETE:33
+CLASS:PUBLIC
+SEQUENCE:1
+CREATED:20170221T130727Z
+LAST-MODIFIED:20170221T130727Z
+END:VTODO
diff --git a/tests/libedata-cal/components/task-9.ics b/tests/libedata-cal/components/task-9.ics
new file mode 100644
index 0000000..af66b26
--- /dev/null
+++ b/tests/libedata-cal/components/task-9.ics
@@ -0,0 +1,11 @@
+BEGIN:VTODO
+UID:task-9
+DTSTAMP:20170221T130109Z
+SUMMARY:With start date
+DTSTART;TZID=America/New_York:20170213T000000
+PERCENT-COMPLETE:0
+CLASS:PUBLIC
+SEQUENCE:1
+CREATED:20170221T132838Z
+LAST-MODIFIED:20170221T132838Z
+END:VTODO
diff --git a/tests/libedata-cal/test-cal-cache-intervals.c b/tests/libedata-cal/test-cal-cache-intervals.c
index 151e328..7d32622 100644
--- a/tests/libedata-cal/test-cal-cache-intervals.c
+++ b/tests/libedata-cal/test-cal-cache-intervals.c
@@ -66,20 +66,38 @@ compare_intervals (time_t x_start,
 }
 
 static GHashTable *
-search_in_intervals (GSList *intervals,
+search_in_intervals (ETimezoneCache *zone_cache,
+                    GSList *intervals,
                     time_t start,
                     time_t end)
 {
+       ECalBackendSExp *sexp;
+       struct icaltimetype itt_start, itt_end;
+       gchar *expr;
        GSList *link;
        GHashTable *res;
 
+       itt_start = icaltime_from_timet_with_zone (start, FALSE, NULL);
+       itt_end = icaltime_from_timet_with_zone (end, FALSE, NULL);
+
+       expr = g_strdup_printf ("(occur-in-time-range? (make-time \"%04d%02d%02dT%02d%02d%02dZ\") (make-time 
\"%04d%02d%02dT%02d%02d%02dZ\"))",
+               itt_start.year, itt_start.month, itt_start.day, itt_start.hour, itt_start.minute, 
itt_start.second,
+               itt_end.year, itt_end.month, itt_end.day, itt_end.hour, itt_end.minute, itt_end.second);
+
+       sexp = e_cal_backend_sexp_new (expr);
+
+       g_free (expr);
+
+       g_assert_nonnull (sexp);
+
        res = g_hash_table_new_full ((GHashFunc) e_cal_component_id_hash, (GEqualFunc) 
e_cal_component_id_equal,
                (GDestroyNotify) e_cal_component_free_id, g_object_unref);
 
        for (link = intervals; link; link = g_slist_next (link)) {
                IntervalData *data = link->data;
 
-               if (compare_intervals (start, end, data->start, data->end) == 0) {
+               if (compare_intervals (start, end, data->start, data->end) == 0 &&
+                   e_cal_backend_sexp_match_comp (sexp, data->comp, zone_cache)) {
                        ECalComponentId *id = NULL;
 
                        id = e_cal_component_get_id (data->comp);
@@ -89,6 +107,8 @@ search_in_intervals (GSList *intervals,
                }
        }
 
+       g_object_unref (sexp);
+
        return res;
 }
 
@@ -164,6 +184,7 @@ test_intervals (TCUFixture *fixture,
        GRand *myrand;
        IntervalData *interval;
        ECalComponent *comp;
+       ETimezoneCache *zone_cache;
        GSList *l1, *intervals = NULL;
        GHashTable *from_intervals;
        gint num_deleted = 0;
@@ -171,6 +192,8 @@ test_intervals (TCUFixture *fixture,
        gboolean success;
        GError *error = NULL;
 
+       zone_cache = E_TIMEZONE_CACHE (fixture->cal_cache);
+
        myrand = g_rand_new ();
 
        for (ii = 0; ii < NUM_INTERVALS_CLOSED; ii++) {
@@ -221,7 +244,7 @@ test_intervals (TCUFixture *fixture,
                g_assert_no_error (error);
                g_assert (success);
 
-               from_intervals = search_in_intervals (intervals, start, end);
+               from_intervals = search_in_intervals (zone_cache, intervals, start, end);
 
                check_search_results (l1, from_intervals);
 
@@ -240,7 +263,7 @@ test_intervals (TCUFixture *fixture,
                g_assert_no_error (error);
                g_assert (success);
 
-               from_intervals = search_in_intervals (intervals, start, end);
+               from_intervals = search_in_intervals (zone_cache, intervals, start, end);
 
                check_search_results (l1, from_intervals);
 
@@ -281,7 +304,7 @@ test_intervals (TCUFixture *fixture,
 
        for (ii = 0; ii < NUM_SEARCHES; ii++) {
                start = g_rand_int_range (myrand, 0, 1000);
-               end = g_rand_int_range (myrand, start, 2000);
+               end = g_rand_int_range (myrand, start + 1, 2000);
 
                l1 = NULL;
 
@@ -289,7 +312,7 @@ test_intervals (TCUFixture *fixture,
                g_assert_no_error (error);
                g_assert (success);
 
-               from_intervals = search_in_intervals (intervals, start, end);
+               from_intervals = search_in_intervals (zone_cache, intervals, start, end);
 
                check_search_results (l1, from_intervals);
 
diff --git a/tests/libedata-cal/test-cal-cache-offline.c b/tests/libedata-cal/test-cal-cache-offline.c
index 1d0d183..a831157 100644
--- a/tests/libedata-cal/test-cal-cache-offline.c
+++ b/tests/libedata-cal/test-cal-cache-offline.c
@@ -1003,7 +1003,7 @@ gint
 main (gint argc,
       gchar **argv)
 {
-       TCUClosure closure = { 0 };
+       TCUClosure closure = { TCU_LOAD_COMPONENT_SET_NONE };
 
 #if !GLIB_CHECK_VERSION (2, 35, 1)
        g_type_init ();
diff --git a/tests/libedata-cal/test-cal-cache-search.c b/tests/libedata-cal/test-cal-cache-search.c
new file mode 100644
index 0000000..2253c29
--- /dev/null
+++ b/tests/libedata-cal/test-cal-cache-search.c
@@ -0,0 +1,470 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2016 Red Hat, Inc. (www.redhat.com)
+ *
+ * This library is free software: you can redistribute it and/or modify it
+ * under the terms of the GNU Lesser General Public License as published by
+ * the Free Software Foundation.
+ *
+ * This library 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 library. If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdlib.h>
+#include <locale.h>
+#include <libecal/libecal.h>
+
+#include "test-cal-cache-utils.h"
+
+#define dd(x)
+
+static GHashTable *
+test_search_manual (ECalCache *cal_cache,
+                   const gchar *expr)
+{
+       GSList *components = NULL, *link;
+       GHashTable *res;
+       ECalBackendSExp *sexp;
+       ETimezoneCache *zone_cache;
+       gboolean success;
+       GError *error = NULL;
+
+       res = g_hash_table_new_full ((GHashFunc) e_cal_component_id_hash, (GEqualFunc) 
e_cal_component_id_equal,
+               (GDestroyNotify) e_cal_component_free_id, g_object_unref);
+
+       zone_cache = E_TIMEZONE_CACHE (cal_cache);
+
+       /* Get all the components stored in the summary. */
+       success = e_cal_cache_search_components (cal_cache, NULL, &components, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+       g_assert_nonnull (components);
+
+       sexp = e_cal_backend_sexp_new (expr);
+       g_assert (sexp != NULL);
+
+       for (link = components; link; link = g_slist_next (link)) {
+               ECalComponent *comp = link->data;
+
+               if (e_cal_backend_sexp_match_comp (sexp, comp, zone_cache)) {
+                       ECalComponentId *id = NULL;
+
+                       id = e_cal_component_get_id (comp);
+                       g_assert_nonnull (id);
+
+                       g_hash_table_insert (res, id, g_object_ref (comp));
+               }
+       }
+
+       g_slist_free_full (components, g_object_unref);
+       g_object_unref (sexp);
+
+       return res;
+}
+
+#if dd(1)+0
+static void
+test_search_dump_results (GSList *search_data,
+                         GHashTable *should_be)
+{
+       GSList *link;
+       GHashTableIter iter;
+       gpointer key;
+       gint ii;
+
+       printf ("   Found %d in ECalCache:\n", g_slist_length (search_data));
+       for (ii = 0, link = search_data; link; link = g_slist_next (link), ii++) {
+               ECalCacheSearchData *sd = link->data;
+
+               printf ("      [%d]: %s%s%s\n", ii, sd->uid, sd->rid ? ", " : "", sd->rid ? sd->rid : "");
+       }
+
+       printf ("\n");
+       printf ("   Found %d in traverse:\n", g_hash_table_size (should_be));
+
+       ii = 0;
+       g_hash_table_iter_init (&iter, should_be);
+       while (g_hash_table_iter_next (&iter, &key, NULL)) {
+               ECalComponentId *id = key;
+
+               printf ("      [%d]: %s%s%s\n", ii, id->uid, id->rid ? ", " : "", id->rid ? id->rid : "");
+               ii++;
+       }
+
+       printf ("\n");
+}
+#endif
+
+static void
+test_search_result_equal (GSList *items,
+                         GHashTable *should_be,
+                         gboolean (* check_cb) (GHashTable *should_be, gpointer item_data))
+{
+       GSList *link;
+
+       g_assert_cmpint (g_slist_length (items), ==, g_hash_table_size (should_be));
+
+       for (link = items; link; link = g_slist_next (link)) {
+               g_assert (check_cb (should_be, link->data));
+       }
+}
+
+static gboolean
+search_data_check_cb (GHashTable *should_be,
+                     gpointer item_data)
+{
+       ECalCacheSearchData *sd = item_data;
+       ECalComponentId id;
+
+       g_assert (sd != NULL);
+       g_assert (sd->uid != NULL);
+
+       id.uid = sd->uid;
+       id.rid = sd->rid;
+
+       return g_hash_table_contains (should_be, &id);
+}
+
+static gboolean
+component_check_cb (GHashTable *should_be,
+                   gpointer item_data)
+{
+       ECalComponent *comp = item_data;
+       ECalComponentId *id;
+       gboolean contains;
+
+       g_assert (comp != NULL);
+
+       id = e_cal_component_get_id (comp);
+
+       g_assert (id != NULL);
+       g_assert (id->uid != NULL);
+
+       contains = g_hash_table_contains (should_be, id);
+
+       e_cal_component_free_id (id);
+
+       return contains;
+}
+
+static gboolean
+id_check_cb (GHashTable *should_be,
+            gpointer item_data)
+{
+       ECalComponentId *id = item_data;
+
+       g_assert (id != NULL);
+       g_assert (id->uid != NULL);
+
+       return g_hash_table_contains (should_be, id);
+}
+
+static void
+test_search_expr (TCUFixture *fixture,
+                 const gchar *expr,
+                 const gchar *expects)
+{
+       GSList *items = NULL;
+       GHashTable *should_be;
+       gboolean success;
+       GError *error = NULL;
+
+       should_be = test_search_manual (fixture->cal_cache, expr);
+       g_assert_nonnull (should_be);
+
+       success = e_cal_cache_search (fixture->cal_cache, expr, &items, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       dd (test_search_dump_results (items, should_be));
+
+       test_search_result_equal (items, should_be, search_data_check_cb);
+
+       g_slist_free_full (items, e_cal_cache_search_data_free);
+       items = NULL;
+
+       success = e_cal_cache_search_components (fixture->cal_cache, expr, &items, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       test_search_result_equal (items, should_be, component_check_cb);
+
+       g_slist_free_full (items, g_object_unref);
+       items = NULL;
+
+       success = e_cal_cache_search_ids (fixture->cal_cache, expr, &items, NULL, &error);
+       g_assert_no_error (error);
+       g_assert (success);
+
+       if (expects) {
+               GSList *link;
+               gboolean negate = *expects == '!';
+
+               if (negate)
+                       expects++;
+
+               for (link = items; link; link = g_slist_next (link)) {
+                       ECalComponentId *id = link->data;
+
+                       if (g_strcmp0 (id->uid, expects) == 0)
+                               break;
+               }
+
+               if (link && negate)
+                       g_error ("Found '%s' in result of '%s', though it should not be there", expects, 
expr);
+               else if (!link && !negate)
+                       g_error ("Not found '%s' in result of '%s', though it should be there", expects, 
expr);
+       }
+
+       test_search_result_equal (items, should_be, id_check_cb);
+
+       g_slist_free_full (items, (GDestroyNotify) e_cal_component_free_id);
+
+       g_hash_table_destroy (should_be);
+}
+
+static void
+test_search (TCUFixture *fixture,
+            const gchar *expr,
+            const gchar *expects)
+{
+       gchar *not_expr;
+
+       test_search_expr (fixture, expr, expects);
+
+       not_expr = g_strdup_printf ("(not (%s))", expr);
+       test_search_expr (fixture, not_expr, NULL);
+       g_free (not_expr);
+}
+
+static void
+test_search_uid (TCUFixture *fixture,
+                gconstpointer user_data)
+{
+       test_search (fixture, "(uid? \"event-3\")", "event-3");
+       test_search (fixture, "(uid? \"event-6\")", "event-6");
+       test_search (fixture, "(or (uid? \"event-3\") (uid? \"event-6\"))", "event-3");
+       test_search (fixture, "(and (uid? \"event-3\") (uid? \"event-6\"))", "!event-3");
+}
+
+static void
+test_search_occur_in_time_range (TCUFixture *fixture,
+                                gconstpointer user_data)
+{
+       test_search (fixture, "(occur-in-time-range? (make-time \"20010101T000000Z\") (make-time 
\"20010101T010000Z\"))", "!event-1");
+       test_search (fixture, "(occur-in-time-range? (make-time \"20170209T000000Z\") (make-time 
\"20170210T000000Z\"))", "event-1");
+       test_search (fixture, "(occur-in-time-range? (make-time \"20170209T020000Z\") (make-time 
\"20170209T023000Z\"))", "event-1");
+       test_search (fixture, "(occur-in-time-range? (make-time \"20111231T000000Z\") (make-time 
\"20111231T595959Z\"))", "event-5");
+
+       /* the second instance of event-6 */
+       test_search (fixture, "(occur-in-time-range? (make-time \"20170221T130000Z\") (make-time 
\"20170221T140000Z\"))", "event-6");
+       test_search (fixture, "(occur-in-time-range? (make-time \"20170221T130000Z\") (make-time 
\"20170221T140000Z\") \"America/New_York\")", "event-6");
+       test_search (fixture, "(occur-in-time-range? (make-time \"20170221T190000Z\") (make-time 
\"20170221T200000Z\") \"Europe/Berlin\")", "!event-6");
+       test_search (fixture, "(occur-in-time-range? (make-time \"20170221T130000Z\") (make-time 
\"20170221T140000Z\") \"Europe/Berlin\")", "event-6");
+}
+
+static void
+test_search_due_in_time_range (TCUFixture *fixture,
+                              gconstpointer user_data)
+{
+       test_search (fixture, "(due-in-time-range? (make-time \"20170101T000000Z\") (make-time 
\"20170101T010000Z\"))", "!task-4");
+       test_search (fixture, "(due-in-time-range? (make-time \"20170228T000000Z\") (make-time 
\"20170302T000000Z\"))", "task-4");
+}
+
+static void
+test_search_contains (TCUFixture *fixture,
+                     gconstpointer user_data)
+{
+       const TCUClosure *closure = user_data;
+       gboolean searches_events = closure && closure->load_set == TCU_LOAD_COMPONENT_SET_EVENTS;
+
+       test_search (fixture, "(contains? \"any\" \"party\")", searches_events ? "event-5" : NULL);
+       test_search (fixture, "(contains? \"comment\" \"mentar\")", searches_events ? "event-8" : "task-6");
+       test_search (fixture, "(contains? \"description\" \"with\")", searches_events ? "event-1" : "task-3");
+       test_search (fixture, "(contains? \"summary\" \"meet\")", searches_events ? "event-8" : NULL);
+       test_search (fixture, "(contains? \"location\" \"kitchen\")", searches_events ? "event-3" : "task-5");
+       test_search (fixture, "(contains? \"attendee\" \"CharLie\")", searches_events ? "event-9" : NULL);
+       test_search (fixture, "(contains? \"organizer\" \"bOb\")", searches_events ? "event-8" : NULL);
+       test_search (fixture, "(contains? \"classification\" \"Public\")", searches_events ? "event-4" : 
"task-4");
+       test_search (fixture, "(contains? \"classification\" \"Private\")", searches_events ? "event-3" : 
"task-7");
+       test_search (fixture, "(contains? \"classification\" \"Confidential\")", searches_events ? "event-2" 
: "task-6");
+       test_search (fixture, "(contains? \"status\" \"NOT STARTED\")", searches_events ? NULL : "task-1");
+       test_search (fixture, "(contains? \"status\" \"COMPLETED\")", searches_events ? NULL : "task-4");
+       test_search (fixture, "(contains? \"status\" \"CANCELLED\")", searches_events ? NULL : "task-8");
+       test_search (fixture, "(contains? \"status\" \"IN PROGRESS\")", searches_events ? NULL : "task-7");
+       test_search (fixture, "(contains? \"priority\" \"HIGH\")", searches_events ? NULL : "task-7");
+       test_search (fixture, "(contains? \"priority\" \"NORMAL\")", searches_events ? NULL : "task-6");
+       test_search (fixture, "(contains? \"priority\" \"LOW\")", searches_events ? NULL : "task-5");
+       test_search (fixture, "(contains? \"priority\" \"UNDEFINED\")", searches_events ? NULL : "task-1");
+}
+
+static void
+test_search_has_start (TCUFixture *fixture,
+                      gconstpointer user_data)
+{
+       const TCUClosure *closure = user_data;
+       gboolean searches_events = closure && closure->load_set == TCU_LOAD_COMPONENT_SET_EVENTS;
+
+       test_search (fixture, "(has-start?)", searches_events ? "event-1" : "task-9");
+       test_search (fixture, "(has-start?)", searches_events ? "event-1" : "!task-8");
+       test_search (fixture, "(not (has-start?))", searches_events ? "!event-1" : "!task-9");
+       test_search (fixture, "(not (has-start?))", searches_events ? "!event-1" : "task-8");
+}
+
+static void
+test_search_has_alarms (TCUFixture *fixture,
+                       gconstpointer user_data)
+{
+       const TCUClosure *closure = user_data;
+       gboolean searches_events = closure && closure->load_set == TCU_LOAD_COMPONENT_SET_EVENTS;
+
+       test_search (fixture, "(has-alarms?)", searches_events ? "event-1" : "task-7");
+       test_search (fixture, "(has-alarms?)", searches_events ? "event-1" : "!task-6");
+       test_search (fixture, "(not (has-alarms?))", searches_events ? "!event-1" : "!task-7");
+       test_search (fixture, "(not (has-alarms?))", searches_events ? "!event-1" : "task-6");
+}
+
+static void
+test_search_has_alarms_in_range (TCUFixture *fixture,
+                                gconstpointer user_data)
+{
+       const TCUClosure *closure = user_data;
+       gboolean searches_events = closure && closure->load_set == TCU_LOAD_COMPONENT_SET_EVENTS;
+
+       test_search (fixture, "(has-alarms-in-range? (make-time \"20091229T230000Z\") (make-time 
\"20091231T010000Z\"))",
+               searches_events ? "event-5" : "!task-7");
+}
+
+static void
+test_search_has_recurrences (TCUFixture *fixture,
+                            gconstpointer user_data)
+{
+       test_search (fixture, "(has-recurrences?)", "event-6");
+       test_search (fixture, "(not (has-recurrences?))", "!event-6");
+}
+
+static void
+test_search_has_categories (TCUFixture *fixture,
+                           gconstpointer user_data)
+{
+       test_search (fixture, "(has-categories? #f)", "!event-2");
+       test_search (fixture, "(has-categories? \"Holiday\")", "event-4");
+       test_search (fixture, "(has-categories? \"Hard\" \"Work\")", "event-2");
+       test_search (fixture, "(has-categories? \"Hard\" \"Work\")", "!event-4");
+}
+
+static void
+test_search_is_completed (TCUFixture *fixture,
+                         gconstpointer user_data)
+{
+       test_search (fixture, "(is-completed?)", "task-4");
+       test_search (fixture, "(is-completed?)", "!task-5");
+       test_search (fixture, "(not (is-completed?))", "!task-4");
+}
+
+static void
+test_search_completed_before (TCUFixture *fixture,
+                             gconstpointer user_data)
+{
+       test_search (fixture, "(completed-before? (make-time \"20170221T000000Z\"))", "!task-4");
+       test_search (fixture, "(completed-before? (make-time \"20170222T000000Z\"))", "task-4");
+}
+
+static void
+test_search_has_attachments (TCUFixture *fixture,
+                            gconstpointer user_data)
+{
+       test_search (fixture, "(has-attachments?)", "event-7");
+       test_search (fixture, "(not (has-attachments?))", "!event-7");
+}
+
+static void
+test_search_percent_complete (TCUFixture *fixture,
+                             gconstpointer user_data)
+{
+       test_search (fixture, "(< (percent-complete?) 30)", "task-5");
+       test_search (fixture, "(< (percent-complete?) 30)", "!task-7");
+}
+
+static void
+test_search_occurrences_count (TCUFixture *fixture,
+                              gconstpointer user_data)
+{
+       test_search (fixture, "(and (= (occurrences-count?) 1) (occur-in-time-range? (make-time 
\"20170209T000000Z\") (make-time \"20170210T000000Z\")))", "event-1");
+       test_search (fixture, "(= (occurrences-count? (make-time \"20170209T000000Z\") (make-time 
\"20170210T000000Z\")) 1)", "event-1");
+}
+
+static void
+test_search_complex (TCUFixture *fixture,
+                    gconstpointer user_data)
+{
+       test_search (fixture,
+               "(or "
+                       "(and (not (is-completed?)) (has-start?) (not (has-alarms?)))"
+                       "(contains? \"summary\" \"-on-\")"
+                       "(has-attachments?)"
+               ")", "event-3");
+}
+
+gint
+main (gint argc,
+      gchar **argv)
+{
+       TCUClosure closure_events = { TCU_LOAD_COMPONENT_SET_EVENTS };
+       TCUClosure closure_tasks = { TCU_LOAD_COMPONENT_SET_TASKS };
+
+#if !GLIB_CHECK_VERSION (2, 35, 1)
+       g_type_init ();
+#endif
+       g_test_init (&argc, &argv, NULL);
+
+       /* Ensure that the client and server get the same locale */
+       g_assert (g_setenv ("LC_ALL", "en_US.UTF-8", TRUE));
+       setlocale (LC_ALL, "");
+
+       g_test_add ("/ECalCache/Search/Uid", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_search_uid, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/OccurInTimeRange", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_search_occur_in_time_range, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/DueInTimeRange", TCUFixture, &closure_tasks,
+               tcu_fixture_setup, test_search_due_in_time_range, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/Contains/Events", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_search_contains, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/Contains/Tasks", TCUFixture, &closure_tasks,
+               tcu_fixture_setup, test_search_contains, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/HasStart/Events", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_search_has_start, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/HasStart/Tasks", TCUFixture, &closure_tasks,
+               tcu_fixture_setup, test_search_has_start, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/HasAlarms/Events", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_search_has_alarms, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/HasAlarms/Tasks", TCUFixture, &closure_tasks,
+               tcu_fixture_setup, test_search_has_alarms, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/HasAlarmsInRange/Events", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_search_has_alarms_in_range, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/HasAlarmsInRange/Tasks", TCUFixture, &closure_tasks,
+               tcu_fixture_setup, test_search_has_alarms_in_range, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/HasRecurrences", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_search_has_recurrences, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/HasCategories", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_search_has_categories, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/IsCompleted", TCUFixture, &closure_tasks,
+               tcu_fixture_setup, test_search_is_completed, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/CompletedBefore", TCUFixture, &closure_tasks,
+               tcu_fixture_setup, test_search_completed_before, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/HasAttachments", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_search_has_attachments, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/PercentComplete", TCUFixture, &closure_tasks,
+               tcu_fixture_setup, test_search_percent_complete, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/OccurrencesCount", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_search_occurrences_count, tcu_fixture_teardown);
+       g_test_add ("/ECalCache/Search/Complex", TCUFixture, &closure_events,
+               tcu_fixture_setup, test_search_complex, tcu_fixture_teardown);
+
+       return g_test_run ();
+}
diff --git a/tests/libedata-cal/test-cal-cache-utils.c b/tests/libedata-cal/test-cal-cache-utils.c
index b14e3a4..c109466 100644
--- a/tests/libedata-cal/test-cal-cache-utils.c
+++ b/tests/libedata-cal/test-cal-cache-utils.c
@@ -54,7 +54,7 @@ void
 tcu_fixture_setup (TCUFixture *fixture,
                   gconstpointer user_data)
 {
-       /* TCUClosure *closure = (TCUClosure *) user_data; */
+       const TCUClosure *closure = user_data;
        gchar *filename, *directory;
        GError *error = NULL;
 
@@ -75,6 +75,31 @@ tcu_fixture_setup (TCUFixture *fixture,
                g_error ("Failed to create the ECalCache: %s", error->message);
 
        g_free (filename);
+
+       if (closure) {
+               if (closure->load_set == TCU_LOAD_COMPONENT_SET_EVENTS) {
+                       tcu_add_component_from_test_case (fixture, "event-1", NULL);
+                       tcu_add_component_from_test_case (fixture, "event-2", NULL);
+                       tcu_add_component_from_test_case (fixture, "event-3", NULL);
+                       tcu_add_component_from_test_case (fixture, "event-4", NULL);
+                       tcu_add_component_from_test_case (fixture, "event-5", NULL);
+                       tcu_add_component_from_test_case (fixture, "event-6", NULL);
+                       tcu_add_component_from_test_case (fixture, "event-6-a", NULL);
+                       tcu_add_component_from_test_case (fixture, "event-7", NULL);
+                       tcu_add_component_from_test_case (fixture, "event-8", NULL);
+                       tcu_add_component_from_test_case (fixture, "event-9", NULL);
+               } else if (closure->load_set == TCU_LOAD_COMPONENT_SET_TASKS) {
+                       tcu_add_component_from_test_case (fixture, "task-1", NULL);
+                       tcu_add_component_from_test_case (fixture, "task-2", NULL);
+                       tcu_add_component_from_test_case (fixture, "task-3", NULL);
+                       tcu_add_component_from_test_case (fixture, "task-4", NULL);
+                       tcu_add_component_from_test_case (fixture, "task-5", NULL);
+                       tcu_add_component_from_test_case (fixture, "task-6", NULL);
+                       tcu_add_component_from_test_case (fixture, "task-7", NULL);
+                       tcu_add_component_from_test_case (fixture, "task-8", NULL);
+                       tcu_add_component_from_test_case (fixture, "task-9", NULL);
+               }
+       }
 }
 
 void
diff --git a/tests/libedata-cal/test-cal-cache-utils.h b/tests/libedata-cal/test-cal-cache-utils.h
index 373f93e..d3616fc 100644
--- a/tests/libedata-cal/test-cal-cache-utils.h
+++ b/tests/libedata-cal/test-cal-cache-utils.h
@@ -22,12 +22,18 @@
 
 G_BEGIN_DECLS
 
+typedef enum {
+       TCU_LOAD_COMPONENT_SET_NONE,
+       TCU_LOAD_COMPONENT_SET_EVENTS,
+       TCU_LOAD_COMPONENT_SET_TASKS
+} TCULoadComponentSet;
+
 typedef struct {
        ECalCache *cal_cache;
 } TCUFixture;
 
 typedef struct {
-       gint dummy;
+       TCULoadComponentSet load_set;
 } TCUClosure;
 
 void           tcu_fixture_setup                       (TCUFixture *fixture,


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