lib/parse-sexp: support and, not, and or.

All operations and (Xapian) fields will eventually have an entry in
the prefixes table. The flags field is just a placeholder for now, but
will eventually distinguish between various kinds of prefixes.
This commit is contained in:
David Bremner 2021-08-24 08:17:19 -07:00
parent a2785c3919
commit f83cd2a05a
3 changed files with 109 additions and 14 deletions

View file

@ -51,7 +51,9 @@ subqueries.
(for most fields) or *or*. See :any:`fields` for more information. (for most fields) or *or*. See :any:`fields` for more information.
``(`` *operator* |q1| |q2| ... |qn| ``)`` ``(`` *operator* |q1| |q2| ... |qn| ``)``
Combine queries |q1| to |qn|. See :any:`operators` for more information. Combine queries |q1| to |qn|. Currently supported operators are
``and``, ``or``, and ``not``. ``(not`` |q1| ... |qn| ``)`` is equivalent
to ``(and (not`` |q1| ``) ... (not`` |qn| ``))``.
``(`` *modifier* |q1| |q2| ... |qn| ``)`` ``(`` *modifier* |q1| |q2| ... |qn| ``)``
Combine queries |q1| to |qn|, and reinterpret the result (e.g. as a regular expression). Combine queries |q1| to |qn|, and reinterpret the result (e.g. as a regular expression).
@ -62,11 +64,6 @@ subqueries.
FIELDS FIELDS
`````` ``````
.. _operators:
OPERATORS
`````````
.. _modifiers: .. _modifiers:
MODIFIERS MODIFIERS
@ -82,6 +79,13 @@ EXAMPLES
Match all messages containing "added", but also those containing "add", "additional", Match all messages containing "added", but also those containing "add", "additional",
"Additional", "adds", etc... via stemming. "Additional", "adds", etc... via stemming.
``(and Bob Marley)``
Match messages containing words "Bob" and "Marley", or their stems
The words need not be adjacent.
``(not Bob Marley)``
Match messages containing neither "Bob" nor "Marley", nor their stems,
.. |q1| replace:: :math:`q_1` .. |q1| replace:: :math:`q_1`
.. |q2| replace:: :math:`q_2` .. |q2| replace:: :math:`q_2`
.. |qn| replace:: :math:`q_n` .. |qn| replace:: :math:`q_n`

View file

@ -7,12 +7,69 @@
/* _sexp is used for file scope symbols to avoid clashing with /* _sexp is used for file scope symbols to avoid clashing with
* definitions from sexp.h */ * definitions from sexp.h */
typedef enum {
SEXP_FLAG_NONE = 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 },
{ }
};
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);
}
/* Here we expect the s-expression to be a proper list, with first /* Here we expect the s-expression to be a proper list, with first
* element defining and operation, or as a special case the empty * element defining and operation, or as a special case the empty
* list */ * list */
static notmuch_status_t 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) Xapian::Query &output)
{ {
@ -32,11 +89,20 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const sexp_t *sx,
return NOTMUCH_STATUS_SUCCESS; return NOTMUCH_STATUS_SUCCESS;
} }
if (sx->list->ty == SEXP_VALUE) if (sx->list->ty == SEXP_LIST) {
_notmuch_database_log (notmuch, "unknown prefix '%s'\n", sx->list->val);
else
_notmuch_database_log (notmuch, "unexpected list in field/operation position\n", _notmuch_database_log (notmuch, "unexpected list in field/operation position\n",
sx->list->val); 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) {
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; return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
} }
@ -54,6 +120,6 @@ _notmuch_sexp_string_to_xapian_query (notmuch_database_t *notmuch, const char *q
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
} }
return _sexp_to_xapian_query (notmuch, sx, output); return _sexp_to_xapian_query (notmuch, NULL, sx, output);
} }
#endif #endif

View file

@ -9,9 +9,34 @@ fi
add_email_corpus add_email_corpus
test_begin_subtest "all messages: ()" for query in '()' '(not)' '(and)' '(or ())' '(or (not))' '(or (and))' \
notmuch search '*' > EXPECTED '(or (and) (or) (not (and)))'; do
notmuch search --query=sexp "()" > OUTPUT test_begin_subtest "all messages: $query"
notmuch search '*' > EXPECTED
notmuch search --query=sexp "$query" > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
done
for query in '(or)' '(not ())' '(not (not))' '(not (and))' \
'(not (or (and) (or) (not (and))))'; do
test_begin_subtest "no messages: $query"
notmuch search --query=sexp "$query" > OUTPUT
test_expect_equal_file /dev/null OUTPUT
done
test_begin_subtest "and of exact terms"
notmuch search --query=sexp '(and "wonderful" "wizard")' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "or of exact terms"
notmuch search --query=sexp '(or "php" "wizard")' | notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
thread:XXX 2010-12-29 [1/1] François Boulogne; [aur-general] Guidelines: cp, mkdir vs install (inbox unread)
thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea! (inbox unread)
EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "single term in body" test_begin_subtest "single term in body"