From a1d139de4d92ae2cdee14d78bd2d66dc2c548714 Mon Sep 17 00:00:00 2001 From: David Bremner Date: Sat, 9 Apr 2022 16:45:48 -0300 Subject: [PATCH] lib: add sexp: prefix to Xapian (infix) query parser. This is analogous to the "infix" prefix provided by the s-expression based query parser. --- doc/man7/notmuch-search-terms.rst | 17 ++++++++++++- lib/Makefile.local | 3 ++- lib/prefix.cc | 5 ++++ lib/sexp-fp.cc | 40 ++++++++++++++++++++++++++++++ lib/sexp-fp.h | 41 +++++++++++++++++++++++++++++++ test/T081-sexpr-search.sh | 25 +++++++++++++++++++ 6 files changed, 129 insertions(+), 2 deletions(-) create mode 100644 lib/sexp-fp.cc create mode 100644 lib/sexp-fp.h diff --git a/doc/man7/notmuch-search-terms.rst b/doc/man7/notmuch-search-terms.rst index f8ad1edb..4f616b7e 100644 --- a/doc/man7/notmuch-search-terms.rst +++ b/doc/man7/notmuch-search-terms.rst @@ -169,6 +169,12 @@ property:= can be present on a given message with several different values. See :any:`notmuch-properties(7)` for more details. +sexp: + The **sexp:** prefix allows subqueries in the format + documented in :any:`notmuch-sexp-queries(7)`. Note that subqueries containing + spaces must be quoted, and any embedded double quotes must be escaped + (see :any:`quoting`). + User defined prefixes are also supported, see :any:`notmuch-config(1)` for details. @@ -257,7 +263,7 @@ Boolean Probabilistic **body:**, **to:**, **attachment:**, **mimetype:** Special - **from:**, **query:**, **subject:** + **from:**, **query:**, **subject:**, **sexp:** Terms and phrases ----------------- @@ -297,6 +303,8 @@ Both of these will match a subject "Free Delicious Pizza" while will not. +.. _quoting: + Quoting ------- @@ -324,6 +332,13 @@ e.g. % notmuch search 'folder:"/^.*/(Junk|Spam)$/"' % notmuch search 'thread:"{from:mallory and date:2009}" and thread:{to:mallory}' +Double quotes within query strings need to be doubled to escape them. + +:: + + % notmuch search 'tag:"""quoted tag"""' + % notmuch search 'sexp:"(or ""wizard"" ""php"")"' + DATE AND TIME SEARCH ==================== diff --git a/lib/Makefile.local b/lib/Makefile.local index 1378a74b..6d67a2a4 100644 --- a/lib/Makefile.local +++ b/lib/Makefile.local @@ -64,7 +64,8 @@ libnotmuch_cxx_srcs = \ $(dir)/prefix.cc \ $(dir)/open.cc \ $(dir)/init.cc \ - $(dir)/parse-sexp.cc + $(dir)/parse-sexp.cc \ + $(dir)/sexp-fp.cc libnotmuch_modules := $(libnotmuch_c_srcs:.c=.o) $(libnotmuch_cxx_srcs:.cc=.o) diff --git a/lib/prefix.cc b/lib/prefix.cc index 857c05b9..06e2333a 100644 --- a/lib/prefix.cc +++ b/lib/prefix.cc @@ -3,6 +3,7 @@ #include "thread-fp.h" #include "regexp-fields.h" #include "parse-time-vrp.h" +#include "sexp-fp.h" typedef struct { const char *name; @@ -60,6 +61,8 @@ prefix_t prefix_table[] = { NOTMUCH_FIELD_PROCESSOR }, { "query", NULL, NOTMUCH_FIELD_EXTERNAL | NOTMUCH_FIELD_PROCESSOR }, + { "sexp", NULL, NOTMUCH_FIELD_EXTERNAL | + NOTMUCH_FIELD_PROCESSOR }, { "from", "XFROM", NOTMUCH_FIELD_EXTERNAL | NOTMUCH_FIELD_PROBABILISTIC | NOTMUCH_FIELD_PROCESSOR }, @@ -138,6 +141,8 @@ _setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch) fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release (); else if (STRNCMP_LITERAL (prefix->name, "thread") == 0) fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release (); + else if (STRNCMP_LITERAL (prefix->name, "sexp") == 0) + fp = (new SexpFieldProcessor (notmuch))->release (); else fp = (new RegexpFieldProcessor (prefix->name, prefix->flags, *notmuch->query_parser, notmuch))->release (); diff --git a/lib/sexp-fp.cc b/lib/sexp-fp.cc new file mode 100644 index 00000000..ed26f6ec --- /dev/null +++ b/lib/sexp-fp.cc @@ -0,0 +1,40 @@ +/* sexp-fp.cc - "sexp:" field processor glue + * + * This file is part of notmuch. + * + * Copyright © 2022 David Bremner + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/ . + * + * Author: David Bremner + */ + +#include "database-private.h" +#include "sexp-fp.h" +#include + +Xapian::Query +SexpFieldProcessor::operator() (const std::string & query_string) +{ + notmuch_status_t status; + Xapian::Query output; + + status = _notmuch_sexp_string_to_xapian_query (notmuch, query_string.c_str (), output); + if (status) { + throw Xapian::QueryParserError ("error parsing " + query_string); + } + + return output; + +} diff --git a/lib/sexp-fp.h b/lib/sexp-fp.h new file mode 100644 index 00000000..341dfa7e --- /dev/null +++ b/lib/sexp-fp.h @@ -0,0 +1,41 @@ +/* sexp-fp.h - sexp field processor glue + * + * This file is part of notmuch. + * + * Copyright © 2022 David Bremner + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see https://www.gnu.org/licenses/ . + * + * Author: David Bremner + */ + +#ifndef NOTMUCH_SEXP_FP_H +#define NOTMUCH_SEXP_FP_H + +#include +#include "notmuch.h" + +class SexpFieldProcessor : public Xapian::FieldProcessor { +protected: + notmuch_database_t *notmuch; + +public: + SexpFieldProcessor (notmuch_database_t *notmuch_) : notmuch (notmuch_) + { + }; + + Xapian::Query operator() (const std::string & query_string); +}; + +#endif /* NOTMUCH_SEXP_FP_H */ diff --git a/test/T081-sexpr-search.sh b/test/T081-sexpr-search.sh index 07b12619..da819190 100755 --- a/test/T081-sexpr-search.sh +++ b/test/T081-sexpr-search.sh @@ -46,6 +46,14 @@ thread:XXX 2009-11-18 [1/3] Carl Worth| Jan Janak; [notmuch] What a great idea EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "or of exact terms via field processor" +notmuch search 'sexp:"(or ""php"" ""wizard"")"' | notmuch_search_sanitize > OUTPUT +cat < 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_begin_subtest "single term in body" notmuch search --query=sexp 'wizard' | notmuch_search_sanitize>OUTPUT cat < EXPECTED @@ -714,6 +722,11 @@ notmuch search property:foo=bar > EXPECTED notmuch search --query=sexp '(property (rx foo=.*))' > OUTPUT test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "regexp 'property' search via field processor" +notmuch search property:foo=bar > EXPECTED +notmuch search 'sexp:"(property (rx foo=.*))"' > OUTPUT +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "anchored 'tag' search" notmuch search tag:signed > EXPECTED notmuch search --query=sexp '(tag (rx ^si))' > OUTPUT @@ -750,6 +763,13 @@ thread:XXX 2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packa EOF test_expect_equal_file EXPECTED OUTPUT +test_begin_subtest "Compound subquery via field processor" +notmuch search 'sexp:"(thread (of (from keithp) (subject Maildir)))"' | notmuch_search_sanitize > OUTPUT +cat< EXPECTED +thread:XXX 2009-11-18 [7/7] Lars Kellogg-Stedman, Mikhail Gusarov, Keith Packard, Carl Worth; [notmuch] Working with Maildir storage? (inbox signed unread) +EOF +test_expect_equal_file EXPECTED OUTPUT + test_begin_subtest "empty subquery" notmuch search --query=sexp '(thread (of))' 1>OUTPUT 2>&1 notmuch search '*' > EXPECTED @@ -976,6 +996,11 @@ 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 via field processor" +grep -Ril List-Id ${MAIL_DIR} | sort | notmuch_dir_sanitize > EXPECTED +notmuch search --output=files '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