]> git.notmuchmail.org Git - notmuch/blob - lib/parse-sexp.cc
lib/parse-sexp: support phrase queries.
[notmuch] / lib / parse-sexp.cc
1 #include "database-private.h"
2
3 #if HAVE_SFSEXP
4 #include "sexp.h"
5 #include "unicode-util.h"
6
7 /* _sexp is used for file scope symbols to avoid clashing with
8  * definitions from sexp.h */
9
10 typedef enum {
11     SEXP_FLAG_NONE      = 0,
12     SEXP_FLAG_FIELD     = 1 << 0,
13 } _sexp_flag_t;
14
15 typedef struct  {
16     const char *name;
17     Xapian::Query::op xapian_op;
18     Xapian::Query initial;
19     _sexp_flag_t flags;
20 } _sexp_prefix_t;
21
22 static _sexp_prefix_t prefixes[] =
23 {
24     { "and",            Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
25       SEXP_FLAG_NONE },
26     { "not",            Xapian::Query::OP_AND_NOT,      Xapian::Query::MatchAll,
27       SEXP_FLAG_NONE },
28     { "or",             Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
29       SEXP_FLAG_NONE },
30     { "subject",        Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
31       SEXP_FLAG_FIELD },
32     { }
33 };
34
35 static notmuch_status_t _sexp_to_xapian_query (notmuch_database_t *notmuch,
36                                                const _sexp_prefix_t *parent,
37                                                const sexp_t *sx,
38                                                Xapian::Query &output);
39
40 static notmuch_status_t
41 _sexp_combine_query (notmuch_database_t *notmuch,
42                      const _sexp_prefix_t *parent,
43                      Xapian::Query::op operation,
44                      Xapian::Query left,
45                      const sexp_t *sx,
46                      Xapian::Query &output)
47 {
48     Xapian::Query subquery;
49
50     notmuch_status_t status;
51
52     /* if we run out elements, return accumulator */
53
54     if (! sx) {
55         output = left;
56         return NOTMUCH_STATUS_SUCCESS;
57     }
58
59     status = _sexp_to_xapian_query (notmuch, parent, sx, subquery);
60     if (status)
61         return status;
62
63     return _sexp_combine_query (notmuch,
64                                 parent,
65                                 operation,
66                                 Xapian::Query (operation, left, subquery),
67                                 sx->next, output);
68 }
69
70 static notmuch_status_t
71 _sexp_parse_phrase (std::string term_prefix, const char *phrase, Xapian::Query &output)
72 {
73     Xapian::Utf8Iterator p (phrase);
74     Xapian::Utf8Iterator end;
75     std::vector<std::string> terms;
76
77     while (p != end) {
78         Xapian::Utf8Iterator start;
79         while (p != end && ! Xapian::Unicode::is_wordchar (*p))
80             p++;
81
82         if (p == end)
83             break;
84
85         start = p;
86
87         while (p != end && Xapian::Unicode::is_wordchar (*p))
88             p++;
89
90         if (p != start) {
91             std::string word (start, p);
92             word = Xapian::Unicode::tolower (word);
93             terms.push_back (term_prefix + word);
94         }
95     }
96     output = Xapian::Query (Xapian::Query::OP_PHRASE, terms.begin (), terms.end ());
97     return NOTMUCH_STATUS_SUCCESS;
98 }
99
100 /* Here we expect the s-expression to be a proper list, with first
101  * element defining and operation, or as a special case the empty
102  * list */
103
104 static notmuch_status_t
105 _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent, const sexp_t *sx,
106                        Xapian::Query &output)
107 {
108
109     if (sx->ty == SEXP_VALUE) {
110         std::string term = Xapian::Unicode::tolower (sx->val);
111         Xapian::Stem stem = *(notmuch->stemmer);
112         std::string term_prefix = parent ? _find_prefix (parent->name) : "";
113         if (sx->aty == SEXP_BASIC && unicode_word_utf8 (sx->val)) {
114             output = Xapian::Query ("Z" + term_prefix + stem (term));
115             return NOTMUCH_STATUS_SUCCESS;
116         } else {
117             return _sexp_parse_phrase (term_prefix, sx->val, output);
118         }
119     }
120
121     /* Empty list */
122     if (! sx->list) {
123         output = Xapian::Query::MatchAll;
124         return NOTMUCH_STATUS_SUCCESS;
125     }
126
127     if (sx->list->ty == SEXP_LIST) {
128         _notmuch_database_log (notmuch, "unexpected list in field/operation position\n",
129                                sx->list->val);
130         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
131     }
132
133     for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) {
134         if (strcmp (prefix->name, sx->list->val) == 0) {
135             if (prefix->flags & SEXP_FLAG_FIELD) {
136                 if (parent) {
137                     _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
138                                            prefix->name, parent->name);
139                     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
140                 }
141                 parent = prefix;
142             }
143
144             return _sexp_combine_query (notmuch, parent, prefix->xapian_op, prefix->initial,
145                                         sx->list->next, output);
146         }
147     }
148
149     _notmuch_database_log (notmuch, "unknown prefix '%s'\n", sx->list->val);
150
151     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
152 }
153
154 notmuch_status_t
155 _notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
156                                       Xapian::Query &output)
157 {
158     const sexp_t *sx = NULL;
159     char *buf = talloc_strdup (notmuch, querystr);
160
161     sx = parse_sexp (buf, strlen (querystr));
162     if (! sx) {
163         _notmuch_database_log (notmuch, "invalid s-expression: '%s'\n", querystr);
164         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
165     }
166
167     return _sexp_to_xapian_query (notmuch, NULL, sx, output);
168 }
169 #endif