[evolution-data-server/intel-work-3-12: 2/4] EBookSqlite: Added support for transliterated queries



commit 0baa84b5b6d351af20ffbcc0e9d15523b7de712b
Author: Mateusz Polrola <mateusz polrola gmail com>
Date:   Fri May 30 12:44:35 2014 +0200

    EBookSqlite: Added support for transliterated queries
    
    Cherry picked from commits in openismus-work-3-8:
    Added ETransliterator wrapper object. 0471dc
    EBookQuery: Added transliteration queries. c99eba
    EBookBackendSexp: Support new transliterated query types. 569d94
    test-client-custom-summary.c: Added tests for transliteration queries.
    ebe6d9
    test-client-custom-summary.c: Added test for transliterated queries
    c7be1c
    test-client-cursor-operations.c: Added tests for using
    transliteratedqueries f0a2d1
    Tests: Added 3 new contacts and a few more query tests. 87e1d1
    EBookSqlite: Not handle transliterated queries gracefully 7b6d9a
    Added E_BOOK_INDEX_TRANSLIT index type 19f482
    EBookSqlite: Full support for transliterated queries e59b57
    Added transliterated indexes to the custom book in the test case 6b857b

 .../libebook-contacts/e-book-contacts-types.h      |    3 +-
 addressbook/libebook-contacts/e-book-query.c       |   52 +++++
 addressbook/libebook-contacts/e-book-query.h       |    9 +
 addressbook/libedata-book/e-book-backend-sexp.c    |  139 +++++++++++
 addressbook/libedata-book/e-book-sqlite.c          |  242 +++++++++++++++++++-
 libedataserver/Makefile.am                         |    2 +
 libedataserver/e-collator.c                        |    2 +-
 libedataserver/e-transliterator-private.cpp        |   20 +-
 libedataserver/e-transliterator-private.h          |   12 +-
 libedataserver/e-transliterator.c                  |  155 +++++++++++++
 libedataserver/e-transliterator.h                  |   55 +++++
 libedataserver/libedataserver.h                    |    1 +
 .../client/test-book-client-cursor-operations.c    |   41 ++++
 .../client/test-book-client-custom-summary.c       |  103 ++++++++-
 tests/libebook/data/vcards/custom-13.vcf           |    6 +-
 tests/libebook/data/vcards/custom-14.vcf           |    6 +-
 tests/libebook/data/vcards/custom-15.vcf           |    6 +-
 tests/libebook/data/vcards/custom-16.vcf           |    6 +
 tests/libebook/data/vcards/sorted-4.vcf            |    1 +
 tests/libebook/data/vcards/sorted-5.vcf            |    2 +-
 tests/libebook/data/vcards/sorted-9.vcf            |    2 +-
 21 files changed, 825 insertions(+), 40 deletions(-)
---
diff --git a/addressbook/libebook-contacts/e-book-contacts-types.h 
b/addressbook/libebook-contacts/e-book-contacts-types.h
index 7ee3b79..3c0f3f7 100644
--- a/addressbook/libebook-contacts/e-book-contacts-types.h
+++ b/addressbook/libebook-contacts/e-book-contacts-types.h
@@ -146,7 +146,8 @@ typedef enum {
        E_BOOK_INDEX_PREFIX = 0,
        E_BOOK_INDEX_SUFFIX,
        E_BOOK_INDEX_PHONE,
-       E_BOOK_INDEX_SORT_KEY
+       E_BOOK_INDEX_SORT_KEY,
+       E_BOOK_INDEX_TRANSLIT
 } EBookIndexType;
 
 /**
diff --git a/addressbook/libebook-contacts/e-book-query.c b/addressbook/libebook-contacts/e-book-query.c
index 419255a..39b7c20 100644
--- a/addressbook/libebook-contacts/e-book-query.c
+++ b/addressbook/libebook-contacts/e-book-query.c
@@ -670,6 +670,42 @@ func_regex_raw (struct _ESExp *f,
 }
 
 static ESExpResult *
+func_translit_contains (struct _ESExp *f,
+                       gint argc,
+                       struct _ESExpResult **argv,
+                       gpointer data)
+{
+       return func_field_test (E_BOOK_QUERY_TRANSLIT_CONTAINS, f, argc, argv, data);
+}
+
+static ESExpResult *
+func_translit_is (struct _ESExp *f,
+                 gint argc,
+                 struct _ESExpResult **argv,
+                 gpointer data)
+{
+       return func_field_test (E_BOOK_QUERY_TRANSLIT_IS, f, argc, argv, data);
+}
+
+static ESExpResult *
+func_translit_beginswith (struct _ESExp *f,
+                         gint argc,
+                         struct _ESExpResult **argv,
+                         gpointer data)
+{
+       return func_field_test (E_BOOK_QUERY_TRANSLIT_BEGINS_WITH, f, argc, argv, data);
+}
+
+static ESExpResult *
+func_translit_endswith (struct _ESExp *f,
+                       gint argc,
+                       struct _ESExpResult **argv,
+                       gpointer data)
+{
+       return func_field_test (E_BOOK_QUERY_TRANSLIT_ENDS_WITH, f, argc, argv, data);
+}
+
+static ESExpResult *
 func_exists (struct _ESExp *f,
              gint argc,
              struct _ESExpResult **argv,
@@ -734,6 +770,10 @@ static const struct {
        { "eqphone_short", func_eqphone_short, 0 },
        { "regex_normal", func_regex_normal, 0 },
        { "regex_raw", func_regex_raw, 0 },
+       { "translit_is", func_translit_is, 0 },
+       { "translit_contains", func_translit_contains, 0 },
+       { "translit_beginswith", func_translit_beginswith, 0 },
+       { "translit_endswith", func_translit_endswith, 0 },
        { "exists", func_exists, 0 },
        { "exists_vcard", func_exists_vcard, 0 }
 };
@@ -814,6 +854,14 @@ field_test_name (EBookQueryTest field_test)
                return "regex_normal";
        case E_BOOK_QUERY_REGEX_RAW:
                return "regex_raw";
+       case E_BOOK_QUERY_TRANSLIT_IS:
+               return "translit_is";
+       case E_BOOK_QUERY_TRANSLIT_CONTAINS:
+               return "translit_contains";
+       case E_BOOK_QUERY_TRANSLIT_BEGINS_WITH:
+               return "translit_beginswith";
+       case E_BOOK_QUERY_TRANSLIT_ENDS_WITH:
+               return "translit_endswith";
        case E_BOOK_QUERY_LAST:
                g_return_val_if_reached (NULL);
        }
@@ -836,6 +884,10 @@ is_phone_test (EBookQueryTest field_test)
        case E_BOOK_QUERY_ENDS_WITH:
        case E_BOOK_QUERY_REGEX_NORMAL:
        case E_BOOK_QUERY_REGEX_RAW:
+       case E_BOOK_QUERY_TRANSLIT_IS:
+       case E_BOOK_QUERY_TRANSLIT_CONTAINS:
+       case E_BOOK_QUERY_TRANSLIT_BEGINS_WITH:
+       case E_BOOK_QUERY_TRANSLIT_ENDS_WITH:
        case E_BOOK_QUERY_LAST:
                break;
        }
diff --git a/addressbook/libebook-contacts/e-book-query.h b/addressbook/libebook-contacts/e-book-query.h
index d0aa787..25c1e6b 100644
--- a/addressbook/libebook-contacts/e-book-query.h
+++ b/addressbook/libebook-contacts/e-book-query.h
@@ -58,6 +58,10 @@ typedef struct _EBookQuery EBookQuery;
  * @E_BOOK_QUERY_REGEX_RAW: A regular expression query against raw contact data, this is usually slower than
  * a %E_BOOK_QUERY_REGEX_NORMAL as it implies that #EVCards must be parsed in order to get the raw data
  * for comparison.
+ * @E_BOOK_QUERY_TRANSLIT_IS: Like %E_BOOK_QUERY_IS, but transliterates text to Latin script before the 
comparison
+ * @E_BOOK_QUERY_TRANSLIT_CONTAINS: Like %E_BOOK_QUERY_CONTAINS, but transliterates text to Latin script 
before the comparison
+ * @E_BOOK_QUERY_TRANSLIT_BEGINS_WITH: Like %E_BOOK_QUERY_BEGINS_WITH, but transliterates text to Latin 
script before the comparison
+ * @E_BOOK_QUERY_TRANSLIT_ENDS_WITH: Like %E_BOOK_QUERY_ENDS_WITH, but transliterates text to Latin script 
before the comparison
  * @E_BOOK_QUERY_LAST: End marker for the #EBookQueryTest enumeration, not a valid query test.
  *
  * The kind of test a query created by e_book_query_field_test() shall perform.
@@ -77,6 +81,11 @@ typedef enum {
   E_BOOK_QUERY_REGEX_NORMAL,
   E_BOOK_QUERY_REGEX_RAW,
 
+  E_BOOK_QUERY_TRANSLIT_IS,
+  E_BOOK_QUERY_TRANSLIT_CONTAINS,
+  E_BOOK_QUERY_TRANSLIT_BEGINS_WITH,
+  E_BOOK_QUERY_TRANSLIT_ENDS_WITH,
+
   /*
     Consider these "coming soon".
 
diff --git a/addressbook/libedata-book/e-book-backend-sexp.c b/addressbook/libedata-book/e-book-backend-sexp.c
index e0bf16c..842b2ab 100644
--- a/addressbook/libedata-book/e-book-backend-sexp.c
+++ b/addressbook/libedata-book/e-book-backend-sexp.c
@@ -1073,6 +1073,141 @@ func_exists_vcard (struct _ESExp *f,
        return r;
 }
 
+/* first space between words is treated as wildcard character;
+ * we are looking for s2 in s1, so s2 will be breaked into words
+ */
+static gboolean
+translit_contains_helper (const gchar *s1,
+                         const gchar *s2,
+                         const gchar *region)
+{
+       ETransliterator *transliterator;
+       gchar *t1, *t2;
+       gboolean res = FALSE;
+
+       transliterator = e_transliterator_new ("Any-Latin");
+       t1 = e_transliterator_transliterate (transliterator, s1);
+       t2 = e_transliterator_transliterate (transliterator, s2);
+       e_transliterator_unref (transliterator);
+
+       res = contains_helper (t1, t2, region);
+
+       g_free (t1);
+       g_free (t2);
+
+       return res;
+}
+
+static ESExpResult *
+func_translit_contains (struct _ESExp *f,
+                       gint argc,
+                       struct _ESExpResult **argv,
+                       gpointer data)
+{
+       SearchContext *ctx = data;
+
+       return entry_compare (ctx, f, argc, argv, translit_contains_helper);
+}
+
+static gboolean
+translit_is_helper (const gchar *ps1,
+                   const gchar *ps2,
+                   const gchar *region)
+{
+       ETransliterator *transliterator;
+       gchar *t1, *t2;
+       gboolean res = FALSE;
+
+       transliterator = e_transliterator_new ("Any-Latin");
+       t1 = e_transliterator_transliterate (transliterator, ps1);
+       t2 = e_transliterator_transliterate (transliterator, ps2);
+       e_transliterator_unref (transliterator);
+
+       res = is_helper (t1, t2, region);
+
+       g_free (t1);
+       g_free (t2);
+
+       return res;
+}
+
+static ESExpResult *
+func_translit_is (struct _ESExp *f,
+                 gint argc,
+                 struct _ESExpResult **argv,
+                 gpointer data)
+{
+       SearchContext *ctx = data;
+
+       return entry_compare (ctx, f, argc, argv, translit_is_helper);
+}
+
+static gboolean
+translit_endswith_helper (const gchar *ps1,
+                         const gchar *ps2,
+                         const gchar *region)
+{
+       ETransliterator *transliterator;
+       gchar *t1, *t2;
+       gboolean res = FALSE;
+
+       transliterator = e_transliterator_new ("Any-Latin");
+       t1 = e_transliterator_transliterate (transliterator, ps1);
+       t2 = e_transliterator_transliterate (transliterator, ps2);
+       e_transliterator_unref (transliterator);
+
+       res = endswith_helper (t1, t2, region);
+
+       g_free (t1);
+       g_free (t2);
+
+       return res;
+}
+
+static ESExpResult *
+func_translit_endswith (struct _ESExp *f,
+                       gint argc,
+                       struct _ESExpResult **argv,
+                       gpointer data)
+{
+       SearchContext *ctx = data;
+
+       return entry_compare (ctx, f, argc, argv, translit_endswith_helper);
+}
+
+static gboolean
+translit_beginswith_helper (const gchar *ps1,
+                           const gchar *ps2,
+                           const gchar *region)
+{
+       ETransliterator *transliterator;
+       gchar *t1, *t2;
+       gboolean res = FALSE;
+
+       transliterator = e_transliterator_new ("Any-Latin");
+       t1 = e_transliterator_transliterate (transliterator, ps1);
+       t2 = e_transliterator_transliterate (transliterator, ps2);
+       e_transliterator_unref (transliterator);
+
+       res = beginswith_helper (t1, t2, region);
+
+       g_free (t1);
+       g_free (t2);
+
+       return res;
+}
+
+static ESExpResult *
+func_translit_beginswith (struct _ESExp *f,
+                         gint argc,
+                         struct _ESExpResult **argv,
+                         gpointer data)
+{
+       SearchContext *ctx = data;
+
+       return entry_compare (ctx, f, argc, argv, translit_beginswith_helper);
+}
+
 static void
 book_backend_sexp_finalize (GObject *object)
 {
@@ -1124,6 +1259,10 @@ static struct {
        { "regex_raw", func_regex_raw, 0 },
        { "exists", func_exists, 0 },
        { "exists_vcard", func_exists_vcard, 0 },
+       { "translit_contains", func_translit_contains, 0 },
+       { "translit_is", func_translit_is, 0 },
+       { "translit_beginswith", func_translit_beginswith, 0 },
+       { "translit_endswith", func_translit_endswith, 0 },
 };
 
 /**
diff --git a/addressbook/libedata-book/e-book-sqlite.c b/addressbook/libedata-book/e-book-sqlite.c
index 044d9b1..4881bcb 100644
--- a/addressbook/libedata-book/e-book-sqlite.c
+++ b/addressbook/libedata-book/e-book-sqlite.c
@@ -289,6 +289,7 @@ ebsql_init_debug (void)
 #define EBSQL_SUFFIX_SORT_KEY        "localized"
 #define EBSQL_SUFFIX_PHONE           "phone"
 #define EBSQL_SUFFIX_COUNTRY         "country"
+#define EBSQL_SUFFIX_TRANSLIT        "translit"
 
 /* Track EBookIndexType's in a bit mask  */
 #define INDEX_FLAG(type)  (1 << E_BOOK_INDEX_##type)
@@ -359,6 +360,7 @@ struct _EBookSqlitePrivate {
        GCancellable   *cancel;          /* User passed GCancellable, we abort an operation if cancelled */
 
        ECollator      *collator;        /* The ECollator to create sort keys for any sortable fields */
+       ETransliterator *transliterator; /* For transliterated queries */
 
        /* SQLite resources  */
        sqlite3        *db;
@@ -679,6 +681,12 @@ summary_field_list_columns (SummaryField *field,
                columns = g_slist_prepend (columns, info);
        }
 
+       /* Transliterated value column */
+       if (field->type != G_TYPE_BOOLEAN && (field->index & INDEX_FLAG (TRANSLIT)) != 0) {
+               info    = column_info_new (field, folderid, EBSQL_SUFFIX_TRANSLIT, "TEXT", NULL, "TINDEX");
+               columns = g_slist_prepend (columns, info);
+       }
+
        return g_slist_reverse (columns);
 }
 
@@ -2051,6 +2059,8 @@ format_multivalues (EBookSqlite *ebsql)
                                g_string_append (string, ";suffix");
                        if ((ebsql->priv->summary_fields[i].index & INDEX_FLAG (PHONE)) != 0)
                                g_string_append (string, ";phone");
+                       if ((ebsql->priv->summary_fields[i].index & INDEX_FLAG (TRANSLIT)) != 0)
+                               g_string_append (string, ";translit");
                }
        }
 
@@ -2145,6 +2155,10 @@ ebsql_introspect_summary (EBookSqlite *ebsql,
                        computed = INDEX_FLAG (SORT_KEY);
                        freeme = g_strndup (col, p - col);
                        col = freeme;
+               } else if ((p = strstr (col, "_" EBSQL_SUFFIX_TRANSLIT)) != NULL) {
+                       computed = INDEX_FLAG (TRANSLIT);
+                       freeme = g_strndup (col, p - col);
+                       col = freeme;
                }
 
                /* First check exception fields */
@@ -2228,6 +2242,8 @@ ebsql_introspect_summary (EBookSqlite *ebsql,
                                                iter->index |= INDEX_FLAG (SUFFIX);
                                        } else if (strcmp (params[j], "phone") == 0) {
                                                iter->index |= INDEX_FLAG (PHONE);
+                                       } else if (strcmp (params[j], "translit") == 0) {
+                                               iter->index |= INDEX_FLAG (TRANSLIT);
                                        }
                                }
                        }
@@ -3257,6 +3273,9 @@ ebsql_prepare_multi_insert (EBookSqlite *ebsql,
                g_string_append (string, ", value_" EBSQL_SUFFIX_COUNTRY);
        }
 
+       if ((field->index & INDEX_FLAG (TRANSLIT)) != 0)
+               g_string_append (string, ", value_" EBSQL_SUFFIX_TRANSLIT);
+
        g_string_append (string, ") VALUES (:uid, :value");
 
        if ((field->index & INDEX_FLAG (SUFFIX)) != 0)
@@ -3267,6 +3286,9 @@ ebsql_prepare_multi_insert (EBookSqlite *ebsql,
                g_string_append (string, ", :value_" EBSQL_SUFFIX_COUNTRY);
        }
 
+       if ((field->index & INDEX_FLAG (TRANSLIT)) != 0)
+               g_string_append (string, ", :value_" EBSQL_SUFFIX_TRANSLIT);
+
        g_string_append_c (string, ')');
 
        stmt = ebsql_prepare_statement (ebsql, string->str, error);
@@ -3284,7 +3306,7 @@ ebsql_run_multi_insert_one (EBookSqlite *ebsql,
                             GError **error)
 {
        gchar *normal = e_util_utf8_normalize (value);
-       gchar *str;
+       gchar *tmp, *str;
        gint ret, param_idx = 1;
 
        /* :uid */
@@ -3319,6 +3341,20 @@ ebsql_run_multi_insert_one (EBookSqlite *ebsql,
 
        }
 
+       if (ret == SQLITE_OK && (field->index & INDEX_FLAG (TRANSLIT)) != 0) {
+
+               if (value) {
+                       tmp = e_transliterator_transliterate (ebsql->priv->transliterator, value);
+                       str = e_util_utf8_normalize (tmp);
+                       g_free (tmp);
+               } else {
+                       str = NULL;
+               }
+
+               /* :value_translit */
+               ret = sqlite3_bind_text (stmt, param_idx++, str, -1, g_free);
+       }
+
        /* Run the statement */
        return ebsql_complete_statement (ebsql, stmt, ret, error);
 }
@@ -3414,6 +3450,12 @@ ebsql_prepare_insert (EBookSqlite *ebsql,
                                g_string_append (string, field->dbname);
                                g_string_append (string, "_" EBSQL_SUFFIX_COUNTRY);
                        }
+
+                       if ((field->index & INDEX_FLAG (TRANSLIT)) != 0) {
+                               g_string_append (string, ", ");
+                               g_string_append (string, field->dbname);
+                               g_string_append (string, "_" EBSQL_SUFFIX_TRANSLIT);
+                       }
                }
        }
        g_string_append (string, ", vcard, bdata)");
@@ -3450,6 +3492,9 @@ ebsql_prepare_insert (EBookSqlite *ebsql,
                                g_string_append_printf (string, ", :%s_" EBSQL_SUFFIX_COUNTRY, field->dbname);
                        }
 
+                       if ((field->index & INDEX_FLAG (TRANSLIT)) != 0)
+                               g_string_append_printf (string, ", :%s_" EBSQL_SUFFIX_TRANSLIT, 
field->dbname);
+
                } else if (field->type != E_TYPE_CONTACT_ATTR_LIST)
                        g_warn_if_reached ();
        }
@@ -3603,6 +3648,20 @@ ebsql_run_insert (EBookSqlite *ebsql,
                                        sqlite3_bind_int (stmt, param_idx++, country_code);
                        }
 
+                       if (ret == SQLITE_OK &&
+                           (field->index & INDEX_FLAG (TRANSLIT)) != 0) {
+
+                               if (val) {
+                                       gchar *tmp = e_transliterator_transliterate 
(ebsql->priv->transliterator, val);
+                                       str = e_util_utf8_normalize (tmp);
+                                       g_free (tmp);
+                               } else {
+                                       str = NULL;
+                               }
+
+                               ret = sqlite3_bind_text (stmt, param_idx++, str, -1, g_free);
+                       }
+
                        g_free (val);
                } else if (field->type == G_TYPE_BOOLEAN) {
                        gboolean val;
@@ -3778,7 +3837,11 @@ enum {
         (query) == E_BOOK_QUERY_EQUALS_NATIONAL_PHONE_NUMBER ? "eqphone-national" : \
         (query) == E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER ? "eqphone-short" : \
         (query) == E_BOOK_QUERY_REGEX_NORMAL ? "regex-normal" : \
-        (query) == E_BOOK_QUERY_REGEX_NORMAL ? "regex-raw" : "(unknown)")
+        (query) == E_BOOK_QUERY_REGEX_RAW ? "regex-raw" : \
+         (query) == E_BOOK_QUERY_TRANSLIT_IS    ? "translit-is" : \
+        (query) == E_BOOK_QUERY_TRANSLIT_CONTAINS ? "translit-contains" : \
+        (query) == E_BOOK_QUERY_TRANSLIT_BEGINS_WITH ? "translit-begins-with" : \
+        (query) == E_BOOK_QUERY_TRANSLIT_ENDS_WITH ? "translit-ends-with" : "unknown")
 
 #define EBSQL_FIELD_ID_STR(field_id) \
        ((field_id) == E_CONTACT_FIELD_LAST ? "x-evolution-any-field" : \
@@ -4127,7 +4190,6 @@ static const struct {
        { "and",              TRUE, BOOK_QUERY_SUB_AND },
        { "or",               TRUE, BOOK_QUERY_SUB_OR },
        { "not",              TRUE, BOOK_QUERY_SUB_NOT },
-
        { "contains",         FALSE, E_BOOK_QUERY_CONTAINS },
        { "is",               FALSE, E_BOOK_QUERY_IS },
        { "beginswith",       FALSE, E_BOOK_QUERY_BEGINS_WITH },
@@ -4137,6 +4199,10 @@ static const struct {
        { "eqphone_short",    FALSE, E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER },
        { "regex_normal",     FALSE, E_BOOK_QUERY_REGEX_NORMAL },
        { "regex_raw",        FALSE, E_BOOK_QUERY_REGEX_RAW },
+       { "translit_is",         FALSE, E_BOOK_QUERY_TRANSLIT_IS },
+       { "translit_contains",   FALSE, E_BOOK_QUERY_TRANSLIT_CONTAINS },
+       { "translit_beginswith", FALSE, E_BOOK_QUERY_TRANSLIT_BEGINS_WITH },
+       { "translit_endswith",   FALSE, E_BOOK_QUERY_TRANSLIT_ENDS_WITH },
        { "exists",           FALSE, BOOK_QUERY_EXISTS },
 };
 
@@ -4603,6 +4669,24 @@ query_preflight_check (PreflightContext *context,
                                }
                        }
                        break;
+
+               case E_BOOK_QUERY_TRANSLIT_IS:
+               case E_BOOK_QUERY_TRANSLIT_CONTAINS:
+               case E_BOOK_QUERY_TRANSLIT_BEGINS_WITH:
+               case E_BOOK_QUERY_TRANSLIT_ENDS_WITH:
+
+                       /* These only only searchable in the summary with the E_BOOK_INDEX_TRANSLIT index */
+                       if (test->field == NULL ||
+                           (test->field->index & INDEX_FLAG (TRANSLIT)) == 0) {
+
+                               context->status = MAX (context->status, PREFLIGHT_NOT_SUMMARIZED);
+                               EBSQL_NOTE (PREFLIGHT,
+                                           g_printerr ("PREFLIGHT CHECK: "
+                                                       "Query `%s' needs fallback search, new status: %s\n",
+                                                       EBSQL_QUERY_TYPE_STR (field_test),
+                                                       EBSQL_STATUS_STR (context->status)));
+                       }
+                       break;
                }
 
                if (test->field &&
@@ -4640,6 +4724,7 @@ query_preflight_substitute_full_name (PreflightContext *context,
                SummaryField *family_name, *given_name, *nickname;
                QueryElement *element;
                QueryFieldTest *test;
+               gboolean need_translit = FALSE;
 
                element = g_ptr_array_index (context->constraints, i);
 
@@ -4650,10 +4735,30 @@ query_preflight_substitute_full_name (PreflightContext *context,
                if (test->field_id != E_CONTACT_FULL_NAME)
                        continue;
 
+               /* If it's transliterated, we won't do this */
+               if (element->query >= E_BOOK_QUERY_TRANSLIT_IS &&
+                   element->query <= E_BOOK_QUERY_TRANSLIT_ENDS_WITH)
+                       need_translit = TRUE;
+
                family_name = summary_field_get (ebsql, E_CONTACT_FAMILY_NAME);
                given_name = summary_field_get (ebsql, E_CONTACT_GIVEN_NAME);
                nickname = summary_field_get (ebsql, E_CONTACT_NICKNAME);
 
+               /* Only OR them in if they are also indexed for transliteration */
+               if (need_translit) {
+                       if (family_name &&
+                           (family_name->index & INDEX_FLAG (TRANSLIT)) == 0)
+                               family_name = NULL;
+
+                       if (given_name &&
+                           (given_name->index & INDEX_FLAG (TRANSLIT)) == 0)
+                               given_name = NULL;
+
+                       if (nickname &&
+                           (nickname->index & INDEX_FLAG (TRANSLIT)) == 0)
+                               nickname = NULL;
+               }
+
                /* If any of these are in the summary, then we'll construct
                 * a grouped OR statment for this E_CONTACT_FULL_NAME test */
                if (family_name || given_name || nickname) {
@@ -4748,7 +4853,8 @@ typedef void (* GenerateFieldTest) (EBookSqlite      *ebsql,
  * with %Q or %q
  */
 static gchar *
-ebsql_normalize_for_like (QueryFieldTest *test,
+ebsql_normalize_for_like (EBookSqlite *ebsql,
+                         QueryFieldTest *test,
                           gboolean reverse_string,
                           gboolean *escape_needed)
 {
@@ -4765,6 +4871,13 @@ ebsql_normalize_for_like (QueryFieldTest *test,
        if (test->field_id == E_CONTACT_UID ||
            test->field_id == E_CONTACT_REV) {
                normal = test->value;
+       } else if (test->query >= E_BOOK_QUERY_TRANSLIT_CONTAINS &&
+                  test->query <= E_BOOK_QUERY_TRANSLIT_ENDS_WITH) {
+               gchar *tmp = e_transliterator_transliterate (ebsql->priv->transliterator, test->value);
+               freeme = e_util_utf8_normalize (tmp);
+               g_free (tmp);
+
+               normal = freeme;
        } else {
                freeme = e_util_utf8_normalize (test->value);
                normal = freeme;
@@ -4834,7 +4947,7 @@ field_test_query_contains (EBookSqlite *ebsql,
        gboolean need_escape;
        gchar *escaped;
 
-       escaped = ebsql_normalize_for_like (test, FALSE, &need_escape);
+       escaped = ebsql_normalize_for_like (ebsql, test, FALSE, &need_escape);
 
        g_string_append_c (string, '(');
 
@@ -4862,7 +4975,7 @@ field_test_query_begins_with (EBookSqlite *ebsql,
        gboolean need_escape;
        gchar *escaped;
 
-       escaped = ebsql_normalize_for_like (test, FALSE, &need_escape);
+       escaped = ebsql_normalize_for_like (ebsql, test, FALSE, &need_escape);
 
        g_string_append_c (string, '(');
        ebsql_string_append_column (string, field, NULL);
@@ -4891,7 +5004,7 @@ field_test_query_ends_with (EBookSqlite *ebsql,
 
        if ((field->index & INDEX_FLAG (SUFFIX)) != 0) {
 
-               escaped = ebsql_normalize_for_like (test, TRUE, &need_escape);
+               escaped = ebsql_normalize_for_like (ebsql, test, TRUE, &need_escape);
 
                g_string_append_c (string, '(');
                ebsql_string_append_column (string, field, EBSQL_SUFFIX_REVERSE);
@@ -4904,7 +5017,7 @@ field_test_query_ends_with (EBookSqlite *ebsql,
 
        } else {
 
-               escaped = ebsql_normalize_for_like (test, FALSE, &need_escape);
+               escaped = ebsql_normalize_for_like (ebsql, test, FALSE, &need_escape);
                g_string_append_c (string, '(');
 
                ebsql_string_append_column (string, field, NULL);
@@ -5035,9 +5148,109 @@ field_test_query_regex_normal (EBookSqlite *ebsql,
 }
 
 static void
+field_test_query_translit_is (EBookSqlite *ebsql,
+                             GString *string,
+                              QueryFieldTest *test)
+{
+       SummaryField *field = test->field;
+       gchar *normal, *translit;
+
+       translit = e_transliterator_transliterate (ebsql->priv->transliterator, test->value);
+       normal = e_util_utf8_normalize (translit);
+
+       ebsql_string_append_column (string, field, EBSQL_SUFFIX_TRANSLIT);
+       ebsql_string_append_printf (string, " = %Q", normal);
+
+       g_free (translit);
+       g_free (normal);
+}
+
+static void
+field_test_query_translit_contains (EBookSqlite *ebsql,
+                                   GString *string,
+                                    QueryFieldTest *test)
+{
+       SummaryField *field = test->field;
+       gboolean need_escape;
+       gchar *escaped;
+
+       escaped = ebsql_normalize_for_like (ebsql, test, FALSE, &need_escape);
+
+       g_string_append_c (string, '(');
+
+       ebsql_string_append_column (string, field, EBSQL_SUFFIX_TRANSLIT);
+       g_string_append (string, " IS NOT NULL AND ");
+       ebsql_string_append_column (string, field, EBSQL_SUFFIX_TRANSLIT);
+       g_string_append (string, " LIKE '%");
+       g_string_append (string, escaped);
+       g_string_append (string, "%'");
+
+       if (need_escape)
+               g_string_append (string, EBSQL_ESCAPE_SEQUENCE);
+
+       g_string_append_c (string, ')');
+
+       g_free (escaped);
+}
+
+static void
+field_test_query_translit_begins_with (EBookSqlite *ebsql,
+                                                                  GString *string,
+                                                                  QueryFieldTest *test)
+{
+       SummaryField *field = test->field;
+       gboolean need_escape;
+       gchar *escaped;
+
+       escaped = ebsql_normalize_for_like (ebsql, test, FALSE, &need_escape);
+
+       g_string_append_c (string, '(');
+       ebsql_string_append_column (string, field, EBSQL_SUFFIX_TRANSLIT);
+       g_string_append (string, " IS NOT NULL AND ");
+
+       ebsql_string_append_column (string, field, EBSQL_SUFFIX_TRANSLIT);
+       g_string_append (string, " LIKE \'");
+       g_string_append (string, escaped);
+       g_string_append (string, "%\'");
+
+       if (need_escape)
+               g_string_append (string, EBSQL_ESCAPE_SEQUENCE);
+       g_string_append_c (string, ')');
+
+       g_free (escaped);
+}
+
+static void
+field_test_query_translit_ends_with (EBookSqlite *ebsql,
+                                                                    GString *string,
+                                                                    QueryFieldTest *test)
+{
+       SummaryField *field = test->field;
+       gboolean need_escape;
+       gchar *escaped;
+
+       escaped = ebsql_normalize_for_like (ebsql, test, FALSE, &need_escape);
+       g_string_append_c (string, '(');
+
+       ebsql_string_append_column (string, field, EBSQL_SUFFIX_TRANSLIT);
+       g_string_append (string, " IS NOT NULL AND ");
+
+       ebsql_string_append_column (string, field, EBSQL_SUFFIX_TRANSLIT);
+       g_string_append (string, " LIKE \'%");
+       g_string_append (string, escaped);
+       g_string_append (string, "\'");
+
+       if (need_escape)
+               g_string_append (string, EBSQL_ESCAPE_SEQUENCE);
+
+       g_string_append_c (string, ')');
+       g_free (escaped);
+}
+
+static void
 field_test_query_exists (EBookSqlite *ebsql,
-                         GString *string,
-                         QueryFieldTest *test)
+                                                GString *string,
+                                                QueryFieldTest *test)
 {
        SummaryField *field = test->field;
 
@@ -5059,6 +5272,10 @@ static const GenerateFieldTest field_test_func_table[] = {
        field_test_query_eqphone_short,    /* E_BOOK_QUERY_EQUALS_SHORT_PHONE_NUMBER */
        field_test_query_regex_normal,     /* E_BOOK_QUERY_REGEX_NORMAL */
        NULL /* Requires fallback */,      /* E_BOOK_QUERY_REGEX_RAW  */
+       field_test_query_translit_is,      /* E_BOOK_QUERY_TRANSLIT_IS  */
+       field_test_query_translit_contains,/* E_BOOK_QUERY_TRANSLIT_CONTAINS  */
+       field_test_query_translit_begins_with,/* E_BOOK_QUERY_TRANSLIT_BEGINS_WITH  */
+       field_test_query_translit_ends_with,/* E_BOOK_QUERY_TRANSLIT_ENDS_WITH  */
        field_test_query_exists,           /* BOOK_QUERY_EXISTS */
 };
 
@@ -5924,6 +6141,9 @@ e_book_sqlite_finalize (GObject *object)
        if (priv->collator)
                e_collator_unref (priv->collator);
 
+       if (ebsql->priv->transliterator)
+               e_transliterator_unref (ebsql->priv->transliterator);
+
        g_mutex_clear (&priv->lock);
        g_mutex_clear (&priv->updates_lock);
 
@@ -5968,6 +6188,8 @@ e_book_sqlite_init (EBookSqlite *ebsql)
 
        g_mutex_init (&ebsql->priv->lock);
        g_mutex_init (&ebsql->priv->updates_lock);
+
+       ebsql->priv->transliterator = e_transliterator_new ("Any-Latin");
 }
 
 /**********************************************************
diff --git a/libedataserver/Makefile.am b/libedataserver/Makefile.am
index 0391f83..9a607da 100644
--- a/libedataserver/Makefile.am
+++ b/libedataserver/Makefile.am
@@ -92,6 +92,7 @@ libedataserver_1_2_la_SOURCES = \
        e-debug-log.c \
        e-time-utils.c \
        e-transliterator-private.h \
+       e-transliterator.c \
        e-uid.c \
        e-url.c \
        e-data-server-util.c \
@@ -126,6 +127,7 @@ libedataserverinclude_HEADERS = \
        e-categories.h \
        e-client.h \
        e-collator.h \
+       e-transliterator.h \
        e-credentials.h \
        e-flag.h \
        e-gdbus-templates.h \
diff --git a/libedataserver/e-collator.c b/libedataserver/e-collator.c
index fee720b..116cfb1 100644
--- a/libedataserver/e-collator.c
+++ b/libedataserver/e-collator.c
@@ -70,7 +70,7 @@ struct _ECollator
        gint             inflow;
        gint             overflow;
 
-       ETransliterator *transliterator;
+       ECxxTransliterator *transliterator;
 };
 
 /*****************************************************
diff --git a/libedataserver/e-transliterator-private.cpp b/libedataserver/e-transliterator-private.cpp
index f777a0f..153538d 100644
--- a/libedataserver/e-transliterator-private.cpp
+++ b/libedataserver/e-transliterator-private.cpp
@@ -2,7 +2,7 @@
 /*
  * Copyright (C) 2013 Intel Corporation
  *
- * This library is free software; you can redistribute it and/or modify it
+g * 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.
  *
@@ -36,43 +36,43 @@
 
 using icu::Transliterator;
 
-struct _ETransliterator {
+struct _ECxxTransliterator {
        Transliterator *priv;
 };
 
 /* Create an Transliterator for the source and target
  * language stripts
  */
-ETransliterator *
+ECxxTransliterator *
 _e_transliterator_cxx_new (const gchar *transliterator_id)
 {
        UErrorCode status = U_ZERO_ERROR;
-       ETransliterator *transliterator;
+       ECxxTransliterator *transliterator;
 
        g_return_val_if_fail (transliterator_id != NULL, NULL);
 
-       transliterator = g_slice_new (ETransliterator);
+       transliterator = g_slice_new (ECxxTransliterator);
        transliterator->priv = Transliterator::createInstance (transliterator_id, UTRANS_FORWARD, status); 
 
        return transliterator;
 }
 
-/* Frees an ETransliterator and it's associated resources
+/* Frees an ECxxTransliterator and it's associated resources
  */
 void
-_e_transliterator_cxx_free (ETransliterator *transliterator)
+_e_transliterator_cxx_free (ECxxTransliterator *transliterator)
 {
        if (transliterator) {
                delete transliterator->priv;
-               g_slice_free (ETransliterator, transliterator);
+               g_slice_free (ECxxTransliterator, transliterator);
        }
 }
 
 /* Transliterates 'str' and returns the new allocated result
  */
 gchar *
-_e_transliterator_cxx_transliterate (ETransliterator  *transliterator,
-                                    const gchar      *str)
+_e_transliterator_cxx_transliterate (ECxxTransliterator  *transliterator,
+                                    const gchar         *str)
 {
        UnicodeString transform;
        std::string sourceUTF8;
diff --git a/libedataserver/e-transliterator-private.h b/libedataserver/e-transliterator-private.h
index e977811..30a2012 100644
--- a/libedataserver/e-transliterator-private.h
+++ b/libedataserver/e-transliterator-private.h
@@ -35,19 +35,19 @@ G_BEGIN_DECLS
 #endif
 
 /**
- * ETransliterator:
+ * ECxxTransliterator:
  *
  * A private opaque type describing an alphabetic index
  *
  * Since: 3.12
  **/
-typedef struct _ETransliterator ETransliterator;
+typedef struct _ECxxTransliterator ECxxTransliterator;
 
 /* defined in e-transliterator-private.cpp, and used by by e-collator.c */
-E_TRANSLITERATOR_LOCAL ETransliterator *_e_transliterator_cxx_new             (const gchar      
*transliterator_id);
-E_TRANSLITERATOR_LOCAL void             _e_transliterator_cxx_free            (ETransliterator  
*transliterator);
-E_TRANSLITERATOR_LOCAL gchar           *_e_transliterator_cxx_transliterate   (ETransliterator  
*transliterator,
-                                                                              const gchar      *str);
+E_TRANSLITERATOR_LOCAL ECxxTransliterator *_e_transliterator_cxx_new             (const gchar        
*transliterator_id);
+E_TRANSLITERATOR_LOCAL void                _e_transliterator_cxx_free            (ECxxTransliterator 
*transliterator);
+E_TRANSLITERATOR_LOCAL gchar              *_e_transliterator_cxx_transliterate   (ECxxTransliterator 
*transliterator,
+                                                                                 const gchar        *str);
 
 G_END_DECLS
 
diff --git a/libedataserver/e-transliterator.c b/libedataserver/e-transliterator.c
new file mode 100644
index 0000000..26665a3
--- /dev/null
+++ b/libedataserver/e-transliterator.c
@@ -0,0 +1,155 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+
+/**
+ * SECTION: e-transliterator
+ * @include: libedataserver/libedataserver.h
+ * @short_description: Collation services for locale sensitive sorting
+ *
+ * The #ETransliterator is a wrapper object around ICU collation services and
+ * provides features to sort words in locale specific ways. The transliterator
+ * also provides some API for determining features of the active alphabet
+ * in the user's locale, and which words should be sorted under which
+ * letter in the user's alphabet.
+ */
+
+#ifdef HAVE_CONFIG_H
+#  include <config.h>
+#endif
+
+#include <stdio.h>
+#include <string.h>
+
+#include "e-transliterator.h"
+#include "e-transliterator-private.h"
+
+G_DEFINE_BOXED_TYPE (ETransliterator,
+                    e_transliterator,
+                    e_transliterator_ref,
+                    e_transliterator_unref)
+
+struct _ETransliterator
+{
+       ECxxTransliterator *transliterator;
+
+       volatile gint       ref_count;
+};
+
+/*****************************************************
+ *                        API                        *
+ *****************************************************/
+
+/**
+ * e_transliterator_new:
+ * @id: The id of the transliterator to create.
+ *
+ * Creates a new #ETransliterator for the given @id,
+ * IDs are defined by ICU libraries and can be of the
+ * form "Any-Latin", "Han-Latin" etc.
+ *
+ * Transliterator services exist for all script types
+ * to convert into Latin, however the same is not true
+ * for other scripts (i.e. you cannot transliterate
+ * Greek into Chinese or Japanese).
+ *
+ * For more details on ICU transliteration services,
+ * visit this link:
+ *     http://userguide.icu-project.org/transforms/general
+ *
+ * Returns: (transfer full): A newly created #ETransliterator.
+ *
+ * Since: 3.12
+ */
+ETransliterator *
+e_transliterator_new (const gchar     *id)
+{
+       ETransliterator *transliterator;
+
+       transliterator = g_slice_new0 (ETransliterator);
+       transliterator->transliterator = _e_transliterator_cxx_new (id);
+       transliterator->ref_count = 1;
+
+       return transliterator;
+}
+
+/**
+ * e_transliterator_ref:
+ * @transliterator: An #ETransliterator
+ *
+ * Increases the reference count of @transliterator.
+ *
+ * Returns: (transfer full): @transliterator
+ *
+ * Since: 3.12
+ */
+ETransliterator *
+e_transliterator_ref (ETransliterator *transliterator)
+{
+       g_return_val_if_fail (transliterator != NULL, NULL);
+
+       g_atomic_int_inc (&transliterator->ref_count);
+
+       return transliterator;
+}
+
+/**
+ * e_transliterator_unref:
+ * @transliterator: An #ETransliterator
+ *
+ * Decreases the reference count of @transliterator.
+ * If the reference count reaches 0 then the transliterator is freed
+ *
+ * Since: 3.12
+ */
+void
+e_transliterator_unref (ETransliterator *transliterator)
+{
+       g_return_if_fail (transliterator != NULL);
+
+       if (g_atomic_int_dec_and_test (&transliterator->ref_count)) {
+
+               if (transliterator->transliterator)
+                       _e_transliterator_cxx_free (transliterator->transliterator);
+
+               g_slice_free (ETransliterator, transliterator);
+       }
+}
+
+/**
+ * e_transliterator_transliterate:
+ * @transliterator: An #ETransliterator
+ * @str: The string to transliterate
+ *
+ * Transliterates @str according to the transliteration service
+ * chosen and passed to e_transliterator_new().
+ *
+ * Returns: (transfer full): The newly created transliteration of @str
+ *
+ * Since: 3.12
+ */
+gchar *
+e_transliterator_transliterate (ETransliterator *transliterator,
+                               const gchar     *str)
+{
+       g_return_val_if_fail (transliterator != NULL, NULL);
+
+       return _e_transliterator_cxx_transliterate (transliterator->transliterator, str);
+}
diff --git a/libedataserver/e-transliterator.h b/libedataserver/e-transliterator.h
new file mode 100644
index 0000000..9ed33fe
--- /dev/null
+++ b/libedataserver/e-transliterator.h
@@ -0,0 +1,55 @@
+/* -*- Mode: C; tab-width: 8; indent-tabs-mode: t; c-basic-offset: 8 -*- */
+/*
+ * Copyright (C) 2013 Intel Corporation
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU Lesser General Public
+ * License as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ * General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301, USA.
+ *
+ * Author: Tristan Van Berkom <tristanvb openismus com>
+ */
+#if !defined (__LIBEDATASERVER_H_INSIDE__) && !defined (LIBEDATASERVER_COMPILATION)
+#error "Only <libedataserver/libedataserver.h> should be included directly."
+#endif
+
+#include <glib.h>
+#include <libedataserver/e-source-enumtypes.h>
+#include <libedataserver/e-data-server-util.h>
+
+#ifndef E_TRANSLITERATOR_H
+#define E_TRANSLITERATOR_H
+
+#define E_TYPE_TRANSLITERATOR (e_transliterator_get_type ())
+
+G_BEGIN_DECLS
+
+/**
+ * ETransliterator:
+ *
+ * An opaque object used for string transliterations.
+ *
+ * Since: 3.12
+ */
+typedef struct _ETransliterator ETransliterator;
+
+GType                e_transliterator_get_type         (void);
+ETransliterator     *e_transliterator_new              (const gchar     *id);
+ETransliterator     *e_transliterator_ref              (ETransliterator *transliterator);
+void                 e_transliterator_unref            (ETransliterator *transliterator);
+gchar               *e_transliterator_transliterate    (ETransliterator *transliterator,
+                                                       const gchar     *str);
+
+
+G_END_DECLS
+
+#endif /* E_TRANSLITERATOR_H */
diff --git a/libedataserver/libedataserver.h b/libedataserver/libedataserver.h
index f33f7c6..4298313 100644
--- a/libedataserver/libedataserver.h
+++ b/libedataserver/libedataserver.h
@@ -24,6 +24,7 @@
 #include <libedataserver/e-categories.h>
 #include <libedataserver/e-client.h>
 #include <libedataserver/e-collator.h>
+#include <libedataserver/e-transliterator.h>
 #include <libedataserver/e-credentials.h>
 #include <libedataserver/e-data-server-util.h>
 #include <libedataserver/e-debug-log.h>
diff --git a/tests/libebook/client/test-book-client-cursor-operations.c 
b/tests/libebook/client/test-book-client-cursor-operations.c
index dabd043..bdd5713 100644
--- a/tests/libebook/client/test-book-client-cursor-operations.c
+++ b/tests/libebook/client/test-book-client-cursor-operations.c
@@ -2444,6 +2444,47 @@ main (gint argc,
                cursor_closure_position (closure, 13, 6, TRUE);
                cursor_closure_add (closure, "/EBookClientCursor/SearchExpression/EffectsPosition%s", 
base_params[i].base_path);
 
+               /* Supports transliterated queries */
+               closure = cursor_closure_new (&base_params[i], "POSIX");
+               cursor_closure_position (closure, 20, 0, TRUE);
+
+               cursor_closure_step (closure,
+                                    E_BOOK_CURSOR_STEP_MOVE | E_BOOK_CURSOR_STEP_FETCH,
+                                    E_BOOK_CURSOR_ORIGIN_CURRENT,
+                                    10, /* Count */
+                                    10, /* Expected results */
+                                    11, 2,  6,  3,  8, 1,  5,  4,  7,  15);
+               cursor_closure_position (closure, 20, 10, TRUE);
+               cursor_closure_set_sexp (closure,
+                                        e_book_query_field_test (
+                                               E_CONTACT_EMAIL,
+                                               E_BOOK_QUERY_TRANSLIT_BEGINS_WITH,
+                                               "几"));
+
+               /* In POSIX Locale, the 10th contact out of 20 becomes the 2nd out of 3 contacts
+                * which start with the transliterated '几' sound (which transliterates to 'ji').
+                *
+                * Our three matching contacts are:
+                *   sorted-9: jim morrison com
+                *   sorted-4: 警察 brown org (transliterates to "jing cha brown org")
+                *   sorted-5: 救命 brown com (transliterates to "jiu ming brown com")
+                *
+                * All of these emails begin with 'ji'
+                */
+               cursor_closure_position (closure, 3, 2, TRUE);
+
+               cursor_closure_set_sexp (closure,
+                                        e_book_query_field_test (
+                                               E_CONTACT_EMAIL,
+                                               E_BOOK_QUERY_TRANSLIT_BEGINS_WITH,
+                                               "ji"));
+
+               /* The '几' sound transliterates to 'ji', so we should have the same results & position.
+                */
+               cursor_closure_position (closure, 3, 2, TRUE);
+               cursor_closure_add (closure, "/EBookClientCursor/SearchExpression/Translit/Email/Prefix/ji%s",
+                                   base_params[i].base_path);
+
                /****************************************************
                 *             Address Book Modifications           *
                 ****************************************************/
diff --git a/tests/libebook/client/test-book-client-custom-summary.c 
b/tests/libebook/client/test-book-client-custom-summary.c
index ec3f59c..4940369 100644
--- a/tests/libebook/client/test-book-client-custom-summary.c
+++ b/tests/libebook/client/test-book-client-custom-summary.c
@@ -46,12 +46,15 @@ setup_custom_book (ESource *scratch,
        e_source_backend_summary_setup_set_indexed_fields (
                setup,
                E_CONTACT_EMAIL, E_BOOK_INDEX_PREFIX,
+               E_CONTACT_EMAIL, E_BOOK_INDEX_TRANSLIT,
                E_CONTACT_TEL, E_BOOK_INDEX_SUFFIX,
                E_CONTACT_TEL, E_BOOK_INDEX_PHONE,
                E_CONTACT_FULL_NAME, E_BOOK_INDEX_PREFIX,
                E_CONTACT_FULL_NAME, E_BOOK_INDEX_SUFFIX,
+               E_CONTACT_FULL_NAME, E_BOOK_INDEX_TRANSLIT,
                E_CONTACT_FAMILY_NAME, E_BOOK_INDEX_PREFIX,
                E_CONTACT_FAMILY_NAME, E_BOOK_INDEX_SUFFIX,
+               E_CONTACT_FAMILY_NAME, E_BOOK_INDEX_TRANSLIT,
                0);
 }
 
@@ -79,7 +82,7 @@ static GOptionEntry entries[] = {
 #  define CHECK_UNSUPPORTED_ERROR(data) (((ClientTestData *)(data))->phone_number_query != FALSE)
 #endif
 
-#define N_CONTACTS 15
+#define N_CONTACTS 16
 
 typedef struct {
        ETestServerClosure parent;
@@ -1186,6 +1189,104 @@ main (gint argc,
                        suites[i].direct,
                        suites[i].custom,
                        FALSE);
+
+               /*********************************************
+                *      TRANSLITERATION QUERIES FOLLOW       *
+                *********************************************/
+
+               /* We have one contact that is "Jim Morrison" and
+                * another which is "警察 懂吗" (which transliterates
+                * to "Jing cha Dong ma", which means "Police do you understand").
+                *
+                * This test tries to fetch 2 contacts beginning with '几' which
+                * transliterates to 'jǐ'.
+                *
+                * So here we assert that when searching for contacts which begin
+                * with a transliterated "几" match both "Jim Morrison" and
+                * "警察 懂吗"
+                */
+               add_client_test (
+                       suites[i].prefix,
+                       "/Transliterated/Prefix/FullName/几",
+                       suites[i].func,
+                       e_book_query_field_test (
+                               E_CONTACT_FULL_NAME,
+                               E_BOOK_QUERY_TRANSLIT_BEGINS_WITH,
+                               "几"),
+                       2,
+                       suites[i].direct,
+                       suites[i].custom,
+                       FALSE);
+
+               /* Same results as above, only the query input is provided in Latin script */
+               add_client_test (
+                       suites[i].prefix,
+                       "/Transliterated/Prefix/FullName/Ji",
+                       suites[i].func,
+                       e_book_query_field_test (
+                               E_CONTACT_FULL_NAME,
+                               E_BOOK_QUERY_TRANSLIT_BEGINS_WITH,
+                               "Ji"),
+                       2,
+                       suites[i].direct,
+                       suites[i].custom,
+                       FALSE);
+
+               /* Check if anything contains "Ma", this should
+                * only find one match for "警察 懂吗" */
+               add_client_test (
+                       suites[i].prefix,
+                       "/Transliterated/Contains/FullName/Ma",
+                       suites[i].func,
+                       e_book_query_field_test (
+                               E_CONTACT_FULL_NAME,
+                               E_BOOK_QUERY_TRANSLIT_CONTAINS,
+                               "Ma"),
+                       1,
+                       suites[i].direct,
+                       suites[i].custom,
+                       FALSE);
+
+
+               add_client_test (
+                       suites[i].prefix,
+                       "/Transliterated/Contains/FullName/Ma",
+                       suites[i].func,
+                       e_book_query_field_test (
+                               E_CONTACT_FULL_NAME,
+                               E_BOOK_QUERY_TRANSLIT_CONTAINS,
+                               "Ma"),
+                       1,
+                       suites[i].direct,
+                       suites[i].custom,
+                       FALSE);
+
+               add_client_test (
+                       suites[i].prefix,
+                       "/Transliterated/Suffix/FullName/Cha",
+                       suites[i].func,
+                       e_book_query_field_test (
+                               E_CONTACT_FULL_NAME,
+                               E_BOOK_QUERY_TRANSLIT_ENDS_WITH,
+                               "Cha"),
+                       1,
+                       suites[i].direct,
+                       suites[i].custom,
+                       FALSE);
+
+               add_client_test (
+                       suites[i].prefix,
+                       "/Transliterated/Prefix/Email/几",
+                       suites[i].func,
+                       e_book_query_field_test (
+                               E_CONTACT_EMAIL,
+                               E_BOOK_QUERY_TRANSLIT_BEGINS_WITH,
+                               "几"),
+                       2,
+                       suites[i].direct,
+                       suites[i].custom,
+                       FALSE);
+
        }
 
        ret = e_test_server_utils_run ();
diff --git a/tests/libebook/data/vcards/custom-13.vcf b/tests/libebook/data/vcards/custom-13.vcf
index 878def9..f13426d 100644
--- a/tests/libebook/data/vcards/custom-13.vcf
+++ b/tests/libebook/data/vcards/custom-13.vcf
@@ -1,6 +1,6 @@
 BEGIN:VCARD
 UID:custom-13
-N:Murphey;Eddie
-TEL;HOME:408 765-5050
-EMAIL;TYPE=home,work:eddie murphey usa
+N:警察;懂吗
+TEL;HOME:1.800 555 PONY
+EMAIL;TYPE=home,work:警察 china net
 END:VCARD
diff --git a/tests/libebook/data/vcards/custom-14.vcf b/tests/libebook/data/vcards/custom-14.vcf
index d064757..5b549e1 100644
--- a/tests/libebook/data/vcards/custom-14.vcf
+++ b/tests/libebook/data/vcards/custom-14.vcf
@@ -1,6 +1,6 @@
 BEGIN:VCARD
 UID:custom-14
-N:Turner;Tina
-TEL;HOME:+1 408 765-5050
-EMAIL;TYPE=home,work:tina turner net
+N:Murphey;Eddie
+TEL;HOME:408 765-5050
+EMAIL;TYPE=home,work:eddie murphey usa
 END:VCARD
diff --git a/tests/libebook/data/vcards/custom-15.vcf b/tests/libebook/data/vcards/custom-15.vcf
index 4e72cf3..3e59b7e 100644
--- a/tests/libebook/data/vcards/custom-15.vcf
+++ b/tests/libebook/data/vcards/custom-15.vcf
@@ -1,6 +1,6 @@
 BEGIN:VCARD
 UID:custom-15
-N:Crawley;Alister
-TEL;HOME:+49 408 765-5050
-EMAIL;TYPE=home,work:777 starry sect
+N:Turner;Tina
+TEL;HOME:+1 408 765-5050
+EMAIL;TYPE=home,work:tina turner net
 END:VCARD
diff --git a/tests/libebook/data/vcards/custom-16.vcf b/tests/libebook/data/vcards/custom-16.vcf
new file mode 100644
index 0000000..75efcb3
--- /dev/null
+++ b/tests/libebook/data/vcards/custom-16.vcf
@@ -0,0 +1,6 @@
+BEGIN:VCARD
+UID:custom-16
+N:Crawley;Alister
+TEL;HOME:+49 408 765-5050
+EMAIL;TYPE=home,work:777 starry sect
+END:VCARD
diff --git a/tests/libebook/data/vcards/sorted-4.vcf b/tests/libebook/data/vcards/sorted-4.vcf
index 61a0c56..6e1b581 100644
--- a/tests/libebook/data/vcards/sorted-4.vcf
+++ b/tests/libebook/data/vcards/sorted-4.vcf
@@ -1,6 +1,7 @@
 BEGIN:VCARD
 UID:sorted-4
 N:bat;First Name
+EMAIL:警察 brown org
 TEL;TYPE=work,pref:12 245999
 EMAIL:big bobby brown org
 END:VCARD
diff --git a/tests/libebook/data/vcards/sorted-5.vcf b/tests/libebook/data/vcards/sorted-5.vcf
index 8a8993f..a3cec4f 100644
--- a/tests/libebook/data/vcards/sorted-5.vcf
+++ b/tests/libebook/data/vcards/sorted-5.vcf
@@ -2,5 +2,5 @@ BEGIN:VCARD
 UID:sorted-5
 N:bäd;First Name
 TEL;HOME:049-2459-4393
-EMAIL;TYPE=home,work:james brown com
+EMAIL;TYPE=home,work:救命 brown com
 END:VCARD
diff --git a/tests/libebook/data/vcards/sorted-9.vcf b/tests/libebook/data/vcards/sorted-9.vcf
index 9a825d6..a5ed36b 100644
--- a/tests/libebook/data/vcards/sorted-9.vcf
+++ b/tests/libebook/data/vcards/sorted-9.vcf
@@ -2,5 +2,5 @@ BEGIN:VCARD
 UID:sorted-9
 N:côté;First Name
 TEL;HOME:514-845-8436
-EMAIL;TYPE=home,work:pink pony com
+EMAIL;TYPE=home,work:jim morrison com
 END:VCARD



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