]> git.cworth.org Git - notmuch/blobdiff - lib/parse-sexp.cc
lib/parse-sexp: support phrase queries.
[notmuch] / lib / parse-sexp.cc
index f031d7904a4bf8246979c819882736b9346f36ed..0917f50566ac5104b5a9839825e05e4e27b319ea 100644 (file)
 
 #if HAVE_SFSEXP
 #include "sexp.h"
-
+#include "unicode-util.h"
 
 /* _sexp is used for file scope symbols to avoid clashing with
  * definitions from sexp.h */
 
+typedef enum {
+    SEXP_FLAG_NONE     = 0,
+    SEXP_FLAG_FIELD    = 1 << 0,
+} _sexp_flag_t;
+
+typedef struct  {
+    const char *name;
+    Xapian::Query::op xapian_op;
+    Xapian::Query initial;
+    _sexp_flag_t flags;
+} _sexp_prefix_t;
+
+static _sexp_prefix_t prefixes[] =
+{
+    { "and",            Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+      SEXP_FLAG_NONE },
+    { "not",            Xapian::Query::OP_AND_NOT,      Xapian::Query::MatchAll,
+      SEXP_FLAG_NONE },
+    { "or",             Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
+      SEXP_FLAG_NONE },
+    { "subject",        Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
+      SEXP_FLAG_FIELD },
+    { }
+};
+
+static notmuch_status_t _sexp_to_xapian_query (notmuch_database_t *notmuch,
+                                              const _sexp_prefix_t *parent,
+                                              const sexp_t *sx,
+                                              Xapian::Query &output);
+
+static notmuch_status_t
+_sexp_combine_query (notmuch_database_t *notmuch,
+                    const _sexp_prefix_t *parent,
+                    Xapian::Query::op operation,
+                    Xapian::Query left,
+                    const sexp_t *sx,
+                    Xapian::Query &output)
+{
+    Xapian::Query subquery;
+
+    notmuch_status_t status;
+
+    /* if we run out elements, return accumulator */
+
+    if (! sx) {
+       output = left;
+       return NOTMUCH_STATUS_SUCCESS;
+    }
+
+    status = _sexp_to_xapian_query (notmuch, parent, sx, subquery);
+    if (status)
+       return status;
+
+    return _sexp_combine_query (notmuch,
+                               parent,
+                               operation,
+                               Xapian::Query (operation, left, subquery),
+                               sx->next, output);
+}
+
+static notmuch_status_t
+_sexp_parse_phrase (std::string term_prefix, const char *phrase, Xapian::Query &output)
+{
+    Xapian::Utf8Iterator p (phrase);
+    Xapian::Utf8Iterator end;
+    std::vector<std::string> terms;
+
+    while (p != end) {
+       Xapian::Utf8Iterator start;
+       while (p != end && ! Xapian::Unicode::is_wordchar (*p))
+           p++;
+
+       if (p == end)
+           break;
+
+       start = p;
+
+       while (p != end && Xapian::Unicode::is_wordchar (*p))
+           p++;
+
+       if (p != start) {
+           std::string word (start, p);
+           word = Xapian::Unicode::tolower (word);
+           terms.push_back (term_prefix + word);
+       }
+    }
+    output = Xapian::Query (Xapian::Query::OP_PHRASE, terms.begin (), terms.end ());
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
 /* Here we expect the s-expression to be a proper list, with first
  * element defining and operation, or as a special case the empty
  * list */
 
 static notmuch_status_t
-_sexp_to_xapian_query (notmuch_database_t *notmuch, const sexp_t *sx,
+_sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent, const sexp_t *sx,
                       Xapian::Query &output)
 {
 
     if (sx->ty == SEXP_VALUE) {
        std::string term = Xapian::Unicode::tolower (sx->val);
        Xapian::Stem stem = *(notmuch->stemmer);
-       if (sx->aty == SEXP_BASIC)
-           term = "Z" + stem (term);
-
-       output = Xapian::Query (term);
-       return NOTMUCH_STATUS_SUCCESS;
+       std::string term_prefix = parent ? _find_prefix (parent->name) : "";
+       if (sx->aty == SEXP_BASIC && unicode_word_utf8 (sx->val)) {
+           output = Xapian::Query ("Z" + term_prefix + stem (term));
+           return NOTMUCH_STATUS_SUCCESS;
+       } else {
+           return _sexp_parse_phrase (term_prefix, sx->val, output);
+       }
     }
 
     /* Empty list */
@@ -32,11 +124,29 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const sexp_t *sx,
        return NOTMUCH_STATUS_SUCCESS;
     }
 
-    if (sx->list->ty == SEXP_VALUE)
-       _notmuch_database_log (notmuch, "unknown prefix '%s'\n", sx->list->val);
-    else
+    if (sx->list->ty == SEXP_LIST) {
        _notmuch_database_log (notmuch, "unexpected list in field/operation position\n",
                               sx->list->val);
+       return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+    }
+
+    for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) {
+       if (strcmp (prefix->name, sx->list->val) == 0) {
+           if (prefix->flags & SEXP_FLAG_FIELD) {
+               if (parent) {
+                   _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
+                                          prefix->name, parent->name);
+                   return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
+               }
+               parent = prefix;
+           }
+
+           return _sexp_combine_query (notmuch, parent, prefix->xapian_op, prefix->initial,
+                                       sx->list->next, output);
+       }
+    }
+
+    _notmuch_database_log (notmuch, "unknown prefix '%s'\n", sx->list->val);
 
     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
 }
@@ -54,6 +164,6 @@ _notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *q
        return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
     }
 
-    return _sexp_to_xapian_query (notmuch, sx, output);
+    return _sexp_to_xapian_query (notmuch, NULL, sx, output);
 }
 #endif