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