lib/parse-sexp: support infix subqueries

This is necessary so that programs can take infix syntax queries from
a user and use the sexp query syntax to construct e.g. a refinement of
that query.
This commit is contained in:
David Bremner 2021-08-24 08:17:34 -07:00
parent afe85e6578
commit cc5992a304
3 changed files with 84 additions and 0 deletions

View file

@ -144,6 +144,10 @@ MODIFIERS
*Modifiers* refer to any prefixes (first elements of compound queries) *Modifiers* refer to any prefixes (first elements of compound queries)
that are neither operators nor fields. that are neither operators nor fields.
``(infix`` *atom* ``)``
Interpret *atom* as an infix notmuch query (see
:any:`notmuch-search-terms(7)`). Not supported inside fields.
``(matching`` |q1| |q2| ... |qn| ``)`` ``(of`` |q1| |q2| ... |qn| ``)`` ``(matching`` |q1| |q2| ... |qn| ``)`` ``(of`` |q1| |q2| ... |qn| ``)``
Match all messages have the same values of the current field as Match all messages have the same values of the current field as
those matching all of |q1| ... |qn|. Supported in most term [#not-path]_ or those matching all of |q1| ... |qn|. Supported in most term [#not-path]_ or
@ -187,6 +191,9 @@ EXAMPLES
``(id 1234@invalid blah@test)`` ``(id 1234@invalid blah@test)``
Matches Message-Id "1234@invalid" *or* Message-Id "blah@test" Matches Message-Id "1234@invalid" *or* Message-Id "blah@test"
``(and (infix "date:2009-11-18..2009-11-18") (tag unread))``
Match messages in the given date range with tag unread.
``(starts-with prelim)`` ``(starts-with prelim)``
Match any words starting with "prelim". Match any words starting with "prelim".

View file

@ -57,6 +57,8 @@ static _sexp_prefix_t prefixes[] =
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND }, SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
{ "id", Xapian::Query::OP_OR, Xapian::Query::MatchNothing, { "id", Xapian::Query::OP_OR, Xapian::Query::MatchNothing,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX }, SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX },
{ "infix", Xapian::Query::OP_INVALID, Xapian::Query::MatchAll,
SEXP_FLAG_SINGLE },
{ "is", Xapian::Query::OP_AND, Xapian::Query::MatchAll, { "is", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND }, SEXP_FLAG_FIELD | SEXP_FLAG_BOOLEAN | SEXP_FLAG_WILDCARD | SEXP_FLAG_REGEX | SEXP_FLAG_EXPAND },
{ "matching", Xapian::Query::OP_AND, Xapian::Query::MatchAll, { "matching", Xapian::Query::OP_AND, Xapian::Query::MatchAll,
@ -242,6 +244,34 @@ _sexp_expand_query (notmuch_database_t *notmuch,
return status; return status;
} }
static notmuch_status_t
_sexp_parse_infix (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
const sexp_t *sx, Xapian::Query &output)
{
if (parent) {
_notmuch_database_log (notmuch, "'infix' not supported inside '%s'\n", parent->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
try {
output = notmuch->query_parser->parse_query (sx->val, NOTMUCH_QUERY_PARSER_FLAGS);
} catch (const Xapian::QueryParserError &error) {
_notmuch_database_log (notmuch, "Syntax error in infix query: %s\n", sx->val);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
} catch (const Xapian::Error &error) {
if (! notmuch->exception_reported) {
_notmuch_database_log (notmuch,
"A Xapian exception occurred parsing query: %s\n",
error.get_msg ().c_str ());
_notmuch_database_log_append (notmuch,
"Query string was: %s\n",
sx->val);
notmuch->exception_reported = true;
return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
}
}
return NOTMUCH_STATUS_SUCCESS;
}
/* 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 */
@ -311,6 +341,10 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
} }
if (strcmp (prefix->name, "infix") == 0) {
return _sexp_parse_infix (notmuch, parent, sx->list->next, output);
}
if (prefix->xapian_op == Xapian::Query::OP_WILDCARD) if (prefix->xapian_op == Xapian::Query::OP_WILDCARD)
return _sexp_parse_wildcard (notmuch, parent, sx->list->next->val, output); return _sexp_parse_wildcard (notmuch, parent, sx->list->next->val, output);

View file

@ -689,4 +689,47 @@ id:cf0c4d610911171136h1713aa59w9cf9aa31f052ad0a@mail.gmail.com
EOF EOF
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "infix query"
notmuch search to:searchbyto | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(infix "to:searchbyto")' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "bad infix query 1"
notmuch search --query=sexp '(infix "from:/unbalanced")' 2>&1| notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
Syntax error in infix query: from:/unbalanced
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "bad infix query 2"
notmuch search --query=sexp '(infix "thread:{unbalanced")' 2>&1| notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
Syntax error in infix query: thread:{unbalanced
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "bad infix query 3: bad nesting"
notmuch search --query=sexp '(subject (infix "tag:inbox"))' 2>&1| notmuch_search_sanitize > OUTPUT
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
'infix' not supported inside 'subject'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "infix query that matches no messages"
notmuch search --query=sexp '(and (infix "from:keithp") (infix "to:keithp"))' > OUTPUT
test_expect_equal_file /dev/null OUTPUT
test_begin_subtest "compound infix query"
notmuch search date:2009-11-18..2009-11-18 and tag:unread > EXPECTED
notmuch search --query=sexp '(infix "date:2009-11-18..2009-11-18 and tag:unread")' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "compound infix query 2"
notmuch search date:2009-11-18..2009-11-18 and tag:unread > EXPECTED
notmuch search --query=sexp '(and (infix "date:2009-11-18..2009-11-18") (infix "tag:unread"))' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_done test_done