]> git.notmuchmail.org Git - notmuch/blob - lib/parse-sexp.cc
06825dc477377d3b4570a760abffce8d626cfe0c
[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 /* sexp_binding structs attach name to a sexp and a defining
11  * context. The latter allows lazy evaluation of parameters whose
12  * definition contains other parameters.  Lazy evaluation is needed
13  * because a primary goal of macros is to change the parent field for
14  * a sexp.
15  */
16
17 typedef struct sexp_binding {
18     const char *name;
19     const sexp_t *sx;
20     const struct sexp_binding *context;
21     const struct sexp_binding *next;
22 } _sexp_binding_t;
23
24 typedef enum {
25     SEXP_FLAG_NONE      = 0,
26     SEXP_FLAG_FIELD     = 1 << 0,
27     SEXP_FLAG_BOOLEAN   = 1 << 1,
28     SEXP_FLAG_SINGLE    = 1 << 2,
29     SEXP_FLAG_WILDCARD  = 1 << 3,
30     SEXP_FLAG_REGEX     = 1 << 4,
31     SEXP_FLAG_DO_REGEX  = 1 << 5,
32     SEXP_FLAG_EXPAND    = 1 << 6,
33     SEXP_FLAG_DO_EXPAND = 1 << 7,
34     SEXP_FLAG_ORPHAN    = 1 << 8,
35     SEXP_FLAG_RANGE     = 1 << 9,
36 } _sexp_flag_t;
37
38 /*
39  * define bitwise operators to hide casts */
40
41 inline _sexp_flag_t
42 operator| (_sexp_flag_t a, _sexp_flag_t b)
43 {
44     return static_cast<_sexp_flag_t>(
45         static_cast<unsigned>(a) | static_cast<unsigned>(b));
46 }
47
48 inline _sexp_flag_t
49 operator& (_sexp_flag_t a, _sexp_flag_t b)
50 {
51     return static_cast<_sexp_flag_t>(
52         static_cast<unsigned>(a) & static_cast<unsigned>(b));
53 }
54
55 typedef struct  {
56     const char *name;
57     Xapian::Query::op xapian_op;
58     Xapian::Query initial;
59     _sexp_flag_t flags;
60 } _sexp_prefix_t;
61
62 static _sexp_prefix_t prefixes[] =
63 {
64     { "and",            Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
65       SEXP_FLAG_NONE },
66     { "attachment",     Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
67       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
68     { "body",           Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
69       SEXP_FLAG_FIELD },
70     { "date",           Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
71       SEXP_FLAG_RANGE },
72     { "from",           Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
73       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
74     { "folder",         Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
75       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
76     { "id",             Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
77       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
78     { "infix",          Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
79       SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN },
80     { "is",             Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
81       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
82     { "lastmod",           Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
83       SEXP_FLAG_RANGE },
84     { "matching",       Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
85       SEXP_FLAG_DO_EXPAND },
86     { "mid",            Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
87       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
88     { "mimetype",       Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
89       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
90     { "not",            Xapian::Query::OP_AND_NOT,      Xapian::Query::MatchAll,
91       SEXP_FLAG_NONE },
92     { "of",             Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
93       SEXP_FLAG_DO_EXPAND },
94     { "or",             Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
95       SEXP_FLAG_NONE },
96     { "path",           Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
97       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
98     { "property",       Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
99       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
100     { "query",          Xapian::Query::OP_INVALID,      Xapian::Query::MatchNothing,
101       SEXP_FLAG_SINGLE | SEXP_FLAG_ORPHAN },
102     { "regex",          Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
103       SEXP_FLAG_SINGLE | SEXP_FLAG_DO_REGEX },
104     { "rx",             Xapian::Query::OP_INVALID,      Xapian::Query::MatchAll,
105       SEXP_FLAG_SINGLE | SEXP_FLAG_DO_REGEX },
106     { "starts-with",    Xapian::Query::OP_WILDCARD,     Xapian::Query::MatchAll,
107       SEXP_FLAG_SINGLE },
108     { "subject",        Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
109       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
110     { "tag",            Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
111       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
112     { "thread",         Xapian::Query::OP_OR,           Xapian::Query::MatchNothing,
113       SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
114     { "to",             Xapian::Query::OP_AND,          Xapian::Query::MatchAll,
115       SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD | SEXP_FLAG_EXPAND },
116     { }
117 };
118
119 static notmuch_status_t _sexp_to_xapian_query (notmuch_database_t *notmuch,
120                                                const _sexp_prefix_t *parent,
121                                                const _sexp_binding_t *env,
122                                                const sexp_t *sx,
123                                                Xapian::Query &output);
124
125 static notmuch_status_t
126 _sexp_combine_query (notmuch_database_t *notmuch,
127                      const _sexp_prefix_t *parent,
128                      const _sexp_binding_t *env,
129                      Xapian::Query::op operation,
130                      Xapian::Query left,
131                      const sexp_t *sx,
132                      Xapian::Query &output)
133 {
134     Xapian::Query subquery;
135
136     notmuch_status_t status;
137
138     /* if we run out elements, return accumulator */
139
140     if (! sx) {
141         output = left;
142         return NOTMUCH_STATUS_SUCCESS;
143     }
144
145     status = _sexp_to_xapian_query (notmuch, parent, env, sx, subquery);
146     if (status)
147         return status;
148
149     return _sexp_combine_query (notmuch,
150                                 parent,
151                                 env,
152                                 operation,
153                                 Xapian::Query (operation, left, subquery),
154                                 sx->next, output);
155 }
156
157 static notmuch_status_t
158 _sexp_parse_phrase (std::string term_prefix, const char *phrase, Xapian::Query &output)
159 {
160     Xapian::Utf8Iterator p (phrase);
161     Xapian::Utf8Iterator end;
162     std::vector<std::string> terms;
163
164     while (p != end) {
165         Xapian::Utf8Iterator start;
166         while (p != end && ! Xapian::Unicode::is_wordchar (*p))
167             p++;
168
169         if (p == end)
170             break;
171
172         start = p;
173
174         while (p != end && Xapian::Unicode::is_wordchar (*p))
175             p++;
176
177         if (p != start) {
178             std::string word (start, p);
179             word = Xapian::Unicode::tolower (word);
180             terms.push_back (term_prefix + word);
181         }
182     }
183     output = Xapian::Query (Xapian::Query::OP_PHRASE, terms.begin (), terms.end ());
184     return NOTMUCH_STATUS_SUCCESS;
185 }
186
187 static notmuch_status_t
188 _sexp_parse_wildcard (notmuch_database_t *notmuch,
189                       const _sexp_prefix_t *parent,
190                       unused(const _sexp_binding_t *env),
191                       std::string match,
192                       Xapian::Query &output)
193 {
194
195     std::string term_prefix = parent ? _notmuch_database_prefix (notmuch, parent->name) : "";
196
197     if (parent && ! (parent->flags & SEXP_FLAG_WILDCARD)) {
198         _notmuch_database_log (notmuch, "'%s' does not support wildcard queries\n", parent->name);
199         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
200     }
201
202     output = Xapian::Query (Xapian::Query::OP_WILDCARD,
203                             term_prefix + Xapian::Unicode::tolower (match));
204     return NOTMUCH_STATUS_SUCCESS;
205 }
206
207 static notmuch_status_t
208 _sexp_parse_one_term (notmuch_database_t *notmuch, std::string term_prefix, const sexp_t *sx,
209                       Xapian::Query &output)
210 {
211     Xapian::Stem stem = *(notmuch->stemmer);
212
213     if (sx->aty == SEXP_BASIC && unicode_word_utf8 (sx->val)) {
214         std::string term = Xapian::Unicode::tolower (sx->val);
215
216         output = Xapian::Query ("Z" + term_prefix + stem (term));
217         return NOTMUCH_STATUS_SUCCESS;
218     } else {
219         return _sexp_parse_phrase (term_prefix, sx->val, output);
220     }
221
222 }
223
224 notmuch_status_t
225 _sexp_parse_regex (notmuch_database_t *notmuch,
226                    const _sexp_prefix_t *prefix, const _sexp_prefix_t *parent,
227                    unused(const _sexp_binding_t *env),
228                    std::string val, Xapian::Query &output)
229 {
230     if (! parent) {
231         _notmuch_database_log (notmuch, "illegal '%s' outside field\n",
232                                prefix->name);
233         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
234     }
235
236     if (! (parent->flags & SEXP_FLAG_REGEX)) {
237         _notmuch_database_log (notmuch, "'%s' not supported in field '%s'\n",
238                                prefix->name, parent->name);
239         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
240     }
241
242     std::string msg; /* ignored */
243
244     return _notmuch_regexp_to_query (notmuch, Xapian::BAD_VALUENO, parent->name,
245                                      val, output, msg);
246 }
247
248
249 static notmuch_status_t
250 _sexp_expand_query (notmuch_database_t *notmuch,
251                     const _sexp_prefix_t *prefix, const _sexp_prefix_t *parent,
252                     unused(const _sexp_binding_t *env), const sexp_t *sx, Xapian::Query &output)
253 {
254     Xapian::Query subquery;
255     notmuch_status_t status;
256     std::string msg;
257
258     if (! (parent->flags & SEXP_FLAG_EXPAND)) {
259         _notmuch_database_log (notmuch, "'%s' unsupported inside '%s'\n", prefix->name, parent->name);
260         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
261     }
262
263     status = _sexp_combine_query (notmuch, NULL, NULL, prefix->xapian_op, prefix->initial, sx,
264                                   subquery);
265     if (status)
266         return status;
267
268     status = _notmuch_query_expand (notmuch, parent->name, subquery, output, msg);
269     if (status) {
270         _notmuch_database_log (notmuch, "error expanding query %s\n", msg.c_str ());
271     }
272     return status;
273 }
274
275 static notmuch_status_t
276 _sexp_parse_infix (notmuch_database_t *notmuch, const sexp_t *sx, Xapian::Query &output)
277 {
278     try {
279         output = notmuch->query_parser->parse_query (sx->val, NOTMUCH_QUERY_PARSER_FLAGS);
280     } catch (const Xapian::QueryParserError &error) {
281         _notmuch_database_log (notmuch, "Syntax error in infix query: %s\n", sx->val);
282         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
283     } catch (const Xapian::Error &error) {
284         if (! notmuch->exception_reported) {
285             _notmuch_database_log (notmuch,
286                                    "A Xapian exception occurred parsing query: %s\n",
287                                    error.get_msg ().c_str ());
288             _notmuch_database_log_append (notmuch,
289                                           "Query string was: %s\n",
290                                           sx->val);
291             notmuch->exception_reported = true;
292             return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
293         }
294     }
295     return NOTMUCH_STATUS_SUCCESS;
296 }
297
298 static notmuch_status_t
299 _sexp_parse_header (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
300                     const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
301 {
302     _sexp_prefix_t user_prefix;
303
304     user_prefix.name = sx->list->val;
305     user_prefix.flags = SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD;
306
307     if (parent) {
308         _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
309                                sx->list->val, parent->name);
310         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
311     }
312
313     parent = &user_prefix;
314
315     return _sexp_combine_query (notmuch, parent, env, Xapian::Query::OP_AND, Xapian::Query::MatchAll,
316                                 sx->list->next, output);
317 }
318
319 static _sexp_binding_t *
320 _sexp_bind (void *ctx, const _sexp_binding_t *env, const char *name, const sexp_t *sx, const
321             _sexp_binding_t *context)
322 {
323     _sexp_binding_t *binding = talloc (ctx, _sexp_binding_t);
324
325     binding->name = talloc_strdup (ctx, name);
326     binding->sx = sx;
327     binding->context = context;
328     binding->next = env;
329     return binding;
330 }
331
332 static notmuch_status_t
333 maybe_apply_macro (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
334                    const _sexp_binding_t *env, const sexp_t *sx, const sexp_t *args,
335                    Xapian::Query &output)
336 {
337     const sexp_t *params, *param, *arg, *body;
338     void *local = talloc_new (notmuch);
339     _sexp_binding_t *new_env = NULL;
340     notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
341
342     if (sx->list->ty != SEXP_VALUE || strcmp (sx->list->val, "macro") != 0) {
343         status = NOTMUCH_STATUS_IGNORED;
344         goto DONE;
345     }
346
347     params = sx->list->next;
348
349     if (! params || (params->ty != SEXP_LIST)) {
350         _notmuch_database_log (notmuch, "missing (possibly empty) list of arguments to macro\n");
351         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
352     }
353
354     body = params->next;
355
356     if (! body) {
357         _notmuch_database_log (notmuch, "missing body of macro\n");
358         status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
359         goto DONE;
360     }
361
362     for (param = params->list, arg = args;
363          param && arg;
364          param = param->next, arg = arg->next) {
365         if (param->ty != SEXP_VALUE || param->aty != SEXP_BASIC) {
366             _notmuch_database_log (notmuch, "macro parameters must be unquoted atoms\n");
367             status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
368             goto DONE;
369         }
370         new_env = _sexp_bind (local, new_env, param->val, arg, env);
371     }
372
373     if (param && ! arg) {
374         _notmuch_database_log (notmuch, "too few arguments to macro\n");
375         status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
376         goto DONE;
377     }
378
379     if (! param && arg) {
380         _notmuch_database_log (notmuch, "too many arguments to macro\n");
381         status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
382         goto DONE;
383     }
384
385     status = _sexp_to_xapian_query (notmuch, parent, new_env, body, output);
386
387   DONE:
388     if (local)
389         talloc_free (local);
390
391     return status;
392 }
393
394 static notmuch_status_t
395 maybe_saved_squery (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
396                     const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
397 {
398     char *key;
399     char *expansion = NULL;
400     notmuch_status_t status;
401     sexp_t *saved_sexp;
402     void *local = talloc_new (notmuch);
403     char *buf;
404
405     key = talloc_asprintf (local, "squery.%s", sx->list->val);
406     if (! key) {
407         status = NOTMUCH_STATUS_OUT_OF_MEMORY;
408         goto DONE;
409     }
410
411     status = notmuch_database_get_config (notmuch, key, &expansion);
412     if (status)
413         goto DONE;
414     if (EMPTY_STRING (expansion)) {
415         status = NOTMUCH_STATUS_IGNORED;
416         goto DONE;
417     }
418
419     buf = talloc_strdup (local, expansion);
420     /* XXX TODO: free this memory */
421     saved_sexp = parse_sexp (buf, strlen (expansion));
422     if (! saved_sexp) {
423         _notmuch_database_log (notmuch, "invalid saved s-expression query: '%s'\n", expansion);
424         status = NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
425         goto DONE;
426     }
427
428     status = maybe_apply_macro (notmuch, parent, env, saved_sexp, sx->list->next, output);
429     if (status == NOTMUCH_STATUS_IGNORED)
430         status =  _sexp_to_xapian_query (notmuch, parent, env, saved_sexp, output);
431
432   DONE:
433     if (local)
434         talloc_free (local);
435
436     return status;
437 }
438
439 static notmuch_status_t
440 _sexp_expand_param (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
441                     const _sexp_binding_t *env, const char *name,
442                     Xapian::Query &output)
443 {
444     for (; env; env = env->next) {
445         if (strcmp (name, env->name) == 0) {
446             return _sexp_to_xapian_query (notmuch, parent, env->context, env->sx,
447                                           output);
448         }
449     }
450     _notmuch_database_log (notmuch, "undefined parameter %s\n", name);
451     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
452 }
453
454 static notmuch_status_t
455 _sexp_parse_range (notmuch_database_t *notmuch,  const _sexp_prefix_t *prefix,
456                    const sexp_t *sx, Xapian::Query &output)
457 {
458     const char *from, *to;
459     std::string msg;
460
461     /* empty range matches everything */
462     if (! sx) {
463         output = Xapian::Query::MatchAll;
464         return NOTMUCH_STATUS_SUCCESS;
465     }
466
467     if (sx->ty == SEXP_LIST) {
468         _notmuch_database_log (notmuch, "expected atom as first argument of '%s'\n", prefix->name);
469         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
470     }
471
472     from = sx->val;
473     to = from;
474
475     if (sx->next) {
476         if (sx->next->ty == SEXP_LIST) {
477             _notmuch_database_log (notmuch, "expected atom as second argument of '%s'\n",
478                                    prefix->name);
479             return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
480         }
481
482         if (sx->next->next) {
483             _notmuch_database_log (notmuch, "'%s' expects maximum of two arguments\n", prefix->name);
484             return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
485         }
486
487         to = sx->next->val;
488     }
489
490     if (strcmp (prefix->name, "date") == 0) {
491         notmuch_status_t status;
492         status = _notmuch_date_strings_to_query (NOTMUCH_VALUE_TIMESTAMP, from, to, output, msg);
493         if (status) {
494             if (! msg.empty ())
495                 _notmuch_database_log (notmuch, "%s\n", msg.c_str ());
496         }
497         return status;
498     }
499
500     if (strcmp (prefix->name, "lastmod") == 0) {
501         long from_idx, to_idx;
502
503         try {
504             from_idx = std::stol (from);
505         } catch (std::logic_error &e) {
506             _notmuch_database_log (notmuch, "bad 'from' revision: '%s'\n", from);
507             return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
508         }
509
510         try {
511             to_idx = std::stol (to);
512         } catch (std::logic_error &e) {
513             _notmuch_database_log (notmuch, "bad 'to' revision: '%s'\n", to);
514             return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
515         }
516
517         output = Xapian::Query (Xapian::Query::OP_VALUE_RANGE, NOTMUCH_VALUE_LAST_MOD,
518                                 Xapian::sortable_serialise (from_idx),
519                                 Xapian::sortable_serialise (to_idx));
520         return NOTMUCH_STATUS_SUCCESS;
521     }
522
523     _notmuch_database_log (notmuch, "unimplimented range prefix: '%s'\n", prefix->name);
524     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
525 }
526
527 /* Here we expect the s-expression to be a proper list, with first
528  * element defining and operation, or as a special case the empty
529  * list */
530
531 static notmuch_status_t
532 _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
533                        const _sexp_binding_t *env, const sexp_t *sx, Xapian::Query &output)
534 {
535     notmuch_status_t status;
536
537     if (sx->ty == SEXP_VALUE && sx->aty == SEXP_BASIC && sx->val[0] == ',') {
538         return _sexp_expand_param (notmuch, parent, env, sx->val + 1, output);
539     }
540
541     if (sx->ty == SEXP_VALUE) {
542         std::string term_prefix = parent ? _notmuch_database_prefix (notmuch, parent->name) : "";
543
544         if (sx->aty == SEXP_BASIC && strcmp (sx->val, "*") == 0) {
545             return _sexp_parse_wildcard (notmuch, parent, env, "", output);
546         }
547
548         if (parent && (parent->flags & SEXP_FLAG_BOOLEAN)) {
549             output = Xapian::Query (term_prefix + sx->val);
550             return NOTMUCH_STATUS_SUCCESS;
551         }
552
553         if (parent) {
554             return _sexp_parse_one_term (notmuch, term_prefix, sx, output);
555         } else {
556             Xapian::Query accumulator;
557             for (_sexp_prefix_t *prefix = prefixes; prefix->name; prefix++) {
558                 if (prefix->flags & SEXP_FLAG_FIELD) {
559                     Xapian::Query subquery;
560                     term_prefix = _notmuch_database_prefix (notmuch, prefix->name);
561                     status = _sexp_parse_one_term (notmuch, term_prefix, sx, subquery);
562                     if (status)
563                         return status;
564                     accumulator = Xapian::Query (Xapian::Query::OP_OR, accumulator, subquery);
565                 }
566             }
567             output = accumulator;
568             return NOTMUCH_STATUS_SUCCESS;
569         }
570     }
571
572     /* Empty list */
573     if (! sx->list) {
574         output = Xapian::Query::MatchAll;
575         return NOTMUCH_STATUS_SUCCESS;
576     }
577
578     if (sx->list->ty == SEXP_LIST) {
579         _notmuch_database_log (notmuch, "unexpected list in field/operation position\n",
580                                sx->list->val);
581         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
582     }
583
584     status = maybe_saved_squery (notmuch, parent, env, sx, output);
585     if (status != NOTMUCH_STATUS_IGNORED)
586         return status;
587
588     /* Check for user defined field */
589     if (_notmuch_string_map_get (notmuch->user_prefix, sx->list->val)) {
590         return _sexp_parse_header (notmuch, parent, env, sx, output);
591     }
592
593     if (strcmp (sx->list->val, "macro") == 0) {
594         _notmuch_database_log (notmuch, "macro definition not permitted here\n");
595         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
596     }
597
598     for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) {
599         if (strcmp (prefix->name, sx->list->val) == 0) {
600             if (prefix->flags & (SEXP_FLAG_FIELD | SEXP_FLAG_RANGE)) {
601                 if (parent) {
602                     _notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
603                                            prefix->name, parent->name);
604                     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
605                 }
606                 parent = prefix;
607             }
608
609             if (parent && (prefix->flags & SEXP_FLAG_ORPHAN)) {
610                 _notmuch_database_log (notmuch, "'%s' not supported inside '%s'\n",
611                                        prefix->name, parent->name);
612                 return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
613             }
614
615             if ((prefix->flags & SEXP_FLAG_SINGLE) &&
616                 (! sx->list->next || sx->list->next->next || sx->list->next->ty != SEXP_VALUE)) {
617                 _notmuch_database_log (notmuch, "'%s' expects single atom as argument\n",
618                                        prefix->name);
619                 return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
620             }
621
622             if (prefix->flags & SEXP_FLAG_RANGE)
623                 return _sexp_parse_range (notmuch, prefix, sx->list->next, output);
624
625             if (strcmp (prefix->name, "infix") == 0) {
626                 return _sexp_parse_infix (notmuch, sx->list->next, output);
627             }
628
629             if (strcmp (prefix->name, "query") == 0) {
630                 return _notmuch_query_name_to_query (notmuch, sx->list->next->val, output);
631             }
632
633             if (prefix->xapian_op == Xapian::Query::OP_WILDCARD)
634                 return _sexp_parse_wildcard (notmuch, parent, env, sx->list->next->val, output);
635
636             if (prefix->flags & SEXP_FLAG_DO_REGEX) {
637                 return _sexp_parse_regex (notmuch, prefix, parent, env, sx->list->next->val, output);
638             }
639
640             if (prefix->flags & SEXP_FLAG_DO_EXPAND) {
641                 return _sexp_expand_query (notmuch, prefix, parent, env, sx->list->next, output);
642             }
643
644             return _sexp_combine_query (notmuch, parent, env, prefix->xapian_op, prefix->initial,
645                                         sx->list->next, output);
646         }
647     }
648
649     _notmuch_database_log (notmuch, "unknown prefix '%s'\n", sx->list->val);
650     return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
651 }
652
653 notmuch_status_t
654 _notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *querystr,
655                                       Xapian::Query &output)
656 {
657     const sexp_t *sx = NULL;
658     char *buf = talloc_strdup (notmuch, querystr);
659
660     sx = parse_sexp (buf, strlen (querystr));
661     if (! sx) {
662         _notmuch_database_log (notmuch, "invalid s-expression: '%s'\n", querystr);
663         return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
664     }
665
666     return _sexp_to_xapian_query (notmuch, NULL, NULL, sx, output);
667 }
668 #endif