lib/parse-sexp: parse user headers

One subtle aspect is the replacement of _find_prefix with
_notmuch_database_prefix, which understands user headers. Otherwise
the code mainly consists of creating a fake prefix record (since the
user prefixes are not in the prefix table) and error handling.
This commit is contained in:
David Bremner 2021-08-24 08:17:35 -07:00
parent cc5992a304
commit a07ef8abf5
3 changed files with 78 additions and 4 deletions

View file

@ -224,6 +224,10 @@ EXAMPLES
``(not (to *))`` ``(not (to *))``
Match messages with an empty or invalid 'To' and 'Cc' field. Match messages with an empty or invalid 'To' and 'Cc' field.
``(List *)``
Match messages with a non-empty List-Id header, assuming
configuration ``index.header.List=List-Id``
NOTES NOTES
===== =====

View file

@ -166,7 +166,7 @@ _sexp_parse_wildcard (notmuch_database_t *notmuch,
Xapian::Query &output) Xapian::Query &output)
{ {
std::string term_prefix = parent ? _find_prefix (parent->name) : ""; std::string term_prefix = parent ? _notmuch_database_prefix (notmuch, parent->name) : "";
if (parent && ! (parent->flags & SEXP_FLAG_WILDCARD)) { if (parent && ! (parent->flags & SEXP_FLAG_WILDCARD)) {
_notmuch_database_log (notmuch, "'%s' does not support wildcard queries\n", parent->name); _notmuch_database_log (notmuch, "'%s' does not support wildcard queries\n", parent->name);
@ -272,6 +272,27 @@ _sexp_parse_infix (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
return NOTMUCH_STATUS_SUCCESS; return NOTMUCH_STATUS_SUCCESS;
} }
static notmuch_status_t
_sexp_parse_header (notmuch_database_t *notmuch, const _sexp_prefix_t *parent,
const sexp_t *sx, Xapian::Query &output)
{
_sexp_prefix_t user_prefix;
user_prefix.name = sx->list->val;
user_prefix.flags = SEXP_FLAG_FIELD | SEXP_FLAG_WILDCARD;
if (parent) {
_notmuch_database_log (notmuch, "nested field: '%s' inside '%s'\n",
sx->list->val, parent->name);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
}
parent = &user_prefix;
return _sexp_combine_query (notmuch, parent, Xapian::Query::OP_AND, Xapian::Query::MatchAll,
sx->list->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 */
@ -281,7 +302,7 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent
Xapian::Query &output) Xapian::Query &output)
{ {
if (sx->ty == SEXP_VALUE) { if (sx->ty == SEXP_VALUE) {
std::string term_prefix = parent ? _find_prefix (parent->name) : ""; std::string term_prefix = parent ? _notmuch_database_prefix (notmuch, parent->name) : "";
if (sx->aty == SEXP_BASIC && strcmp (sx->val, "*") == 0) { if (sx->aty == SEXP_BASIC && strcmp (sx->val, "*") == 0) {
return _sexp_parse_wildcard (notmuch, parent, "", output); return _sexp_parse_wildcard (notmuch, parent, "", output);
@ -291,6 +312,7 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent
output = Xapian::Query (term_prefix + sx->val); output = Xapian::Query (term_prefix + sx->val);
return NOTMUCH_STATUS_SUCCESS; return NOTMUCH_STATUS_SUCCESS;
} }
if (parent) { if (parent) {
return _sexp_parse_one_term (notmuch, term_prefix, sx, output); return _sexp_parse_one_term (notmuch, term_prefix, sx, output);
} else { } else {
@ -299,7 +321,7 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent
if (prefix->flags & SEXP_FLAG_FIELD) { if (prefix->flags & SEXP_FLAG_FIELD) {
notmuch_status_t status; notmuch_status_t status;
Xapian::Query subquery; Xapian::Query subquery;
term_prefix = _find_prefix (prefix->name); term_prefix = _notmuch_database_prefix (notmuch, prefix->name);
status = _sexp_parse_one_term (notmuch, term_prefix, sx, subquery); status = _sexp_parse_one_term (notmuch, term_prefix, sx, subquery);
if (status) if (status)
return status; return status;
@ -323,6 +345,11 @@ _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;
} }
/* Check for user defined field */
if (_notmuch_string_map_get (notmuch->user_prefix, sx->list->val)) {
return _sexp_parse_header (notmuch, parent, sx, output);
}
for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) { for (_sexp_prefix_t *prefix = prefixes; prefix && prefix->name; prefix++) {
if (strcmp (prefix->name, sx->list->val) == 0) { if (strcmp (prefix->name, sx->list->val) == 0) {
if (prefix->flags & SEXP_FLAG_FIELD) { if (prefix->flags & SEXP_FLAG_FIELD) {
@ -362,7 +389,6 @@ _sexp_to_xapian_query (notmuch_database_t *notmuch, const _sexp_prefix_t *parent
} }
_notmuch_database_log (notmuch, "unknown prefix '%s'\n", sx->list->val); _notmuch_database_log (notmuch, "unknown prefix '%s'\n", sx->list->val);
return NOTMUCH_STATUS_BAD_QUERY_SYNTAX; return NOTMUCH_STATUS_BAD_QUERY_SYNTAX;
} }

View file

@ -732,4 +732,48 @@ 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 notmuch search --query=sexp '(and (infix "date:2009-11-18..2009-11-18") (infix "tag:unread"))' > OUTPUT
test_expect_equal_file EXPECTED OUTPUT test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "user header (unknown header)"
notmuch search --query=sexp '(FooBar)' >& OUTPUT
cat <<EOF > EXPECTED
notmuch search: Syntax error in query
unknown prefix 'FooBar'
EOF
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "adding user header"
test_expect_code 0 "notmuch config set index.header.List \"List-Id\""
test_begin_subtest "reindexing"
test_expect_code 0 'notmuch reindex "*"'
test_begin_subtest "wildcard search for user header"
grep -Ril List-Id ${MAIL_DIR} | sort | notmuch_dir_sanitize > EXPECTED
notmuch search --output=files --query=sexp '(List *)' | sort | notmuch_dir_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "wildcard search for user header 2"
grep -Ril List-Id ${MAIL_DIR} | sort | notmuch_dir_sanitize > EXPECTED
notmuch search --output=files --query=sexp '(List (starts-with not))' | sort | notmuch_dir_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "search for user header"
notmuch search List:notmuch | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(List notmuch)' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "search for user header (list token)"
notmuch search List:notmuch | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(List notmuch.notmuchmail.org)' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "search for user header (quoted string)"
notmuch search 'List:"notmuch notmuchmail org"' | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(List "notmuch notmuchmail org")' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_begin_subtest "search for user header (atoms)"
notmuch search 'List:"notmuch notmuchmail org"' | notmuch_search_sanitize > EXPECTED
notmuch search --query=sexp '(List notmuch notmuchmail org)' | notmuch_search_sanitize > OUTPUT
test_expect_equal_file EXPECTED OUTPUT
test_done test_done