]> git.notmuchmail.org Git - notmuch/blob - lib/regexp-fields.cc
emacs: Add new option notmuch-search-hide-excluded
[notmuch] / lib / regexp-fields.cc
1 /* regexp-fields.cc - field processor glue for regex supporting fields
2  *
3  * This file is part of notmuch.
4  *
5  * Copyright © 2015 Austin Clements
6  * Copyright © 2016 David Bremner
7  *
8  * This program is free software: you can redistribute it and/or modify
9  * it under the terms of the GNU General Public License as published by
10  * the Free Software Foundation, either version 3 of the License, or
11  * (at your option) any later version.
12  *
13  * This program is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16  * GNU General Public License for more details.
17  *
18  * You should have received a copy of the GNU General Public License
19  * along with this program.  If not, see https://www.gnu.org/licenses/ .
20  *
21  * Author: Austin Clements <aclements@csail.mit.edu>
22  *                David Bremner <david@tethera.net>
23  */
24
25 #include "regexp-fields.h"
26 #include "notmuch-private.h"
27 #include "database-private.h"
28 #include "xapian-extra.h"
29
30 notmuch_status_t
31 compile_regex (regex_t &regexp, const char *str, std::string &msg)
32 {
33     int err = regcomp (&regexp, str, REG_EXTENDED | REG_NOSUB);
34
35     if (err != 0) {
36         size_t len = regerror (err, &regexp, NULL, 0);
37         char *buffer = new char[len];
38         msg = "Regexp error: ";
39         (void) regerror (err, &regexp, buffer, len);
40         msg.append (buffer, len);
41         delete[] buffer;
42
43         return NOTMUCH_STATUS_ILLEGAL_ARGUMENT;
44     }
45     return NOTMUCH_STATUS_SUCCESS;
46 }
47
48 RegexpPostingSource::RegexpPostingSource (Xapian::valueno slot, const std::string &regexp)
49     : slot_ (slot)
50 {
51     std::string msg;
52     notmuch_status_t status = compile_regex (regexp_, regexp.c_str (), msg);
53
54     if (status)
55         throw Xapian::QueryParserError (msg);
56 }
57
58 RegexpPostingSource::~RegexpPostingSource ()
59 {
60     regfree (&regexp_);
61 }
62
63 void
64 RegexpPostingSource::init (const Xapian::Database &db)
65 {
66     db_ = db;
67     it_ = db_.valuestream_begin (slot_);
68     end_ = db.valuestream_end (slot_);
69     started_ = false;
70 }
71
72 Xapian::doccount
73 RegexpPostingSource::get_termfreq_min () const
74 {
75     return 0;
76 }
77
78 Xapian::doccount
79 RegexpPostingSource::get_termfreq_est () const
80 {
81     return get_termfreq_max () / 2;
82 }
83
84 Xapian::doccount
85 RegexpPostingSource::get_termfreq_max () const
86 {
87     return db_.get_value_freq (slot_);
88 }
89
90 Xapian::docid
91 RegexpPostingSource::get_docid () const
92 {
93     return it_.get_docid ();
94 }
95
96 bool
97 RegexpPostingSource::at_end () const
98 {
99     return it_ == end_;
100 }
101
102 void
103 RegexpPostingSource::next (unused (double min_wt))
104 {
105     if (started_ && ! at_end ())
106         ++it_;
107     started_ = true;
108
109     for (; ! at_end (); ++it_) {
110         std::string value = *it_;
111         if (regexec (&regexp_, value.c_str (), 0, NULL, 0) == 0)
112             break;
113     }
114 }
115
116 void
117 RegexpPostingSource::skip_to (Xapian::docid did, unused (double min_wt))
118 {
119     started_ = true;
120     it_.skip_to (did);
121     for (; ! at_end (); ++it_) {
122         std::string value = *it_;
123         if (regexec (&regexp_, value.c_str (), 0, NULL, 0) == 0)
124             break;
125     }
126 }
127
128 bool
129 RegexpPostingSource::check (Xapian::docid did, unused (double min_wt))
130 {
131     started_ = true;
132     if (! it_.check (did) || at_end ())
133         return false;
134     return (regexec (&regexp_, (*it_).c_str (), 0, NULL, 0) == 0);
135 }
136
137 static inline Xapian::valueno
138 _find_slot (std::string prefix)
139 {
140     if (prefix == "from")
141         return NOTMUCH_VALUE_FROM;
142     else if (prefix == "subject")
143         return NOTMUCH_VALUE_SUBJECT;
144     else if (prefix == "mid")
145         return NOTMUCH_VALUE_MESSAGE_ID;
146     else
147         return Xapian::BAD_VALUENO;
148 }
149
150 RegexpFieldProcessor::RegexpFieldProcessor (std::string field_,
151                                             notmuch_field_flag_t options_,
152                                             Xapian::QueryParser &parser_,
153                                             notmuch_database_t *notmuch_)
154     : slot (_find_slot (field_)),
155     field (field_),
156     term_prefix (_find_prefix (field_.c_str ())),
157     options (options_),
158     parser (parser_),
159     notmuch (notmuch_)
160 {
161 };
162
163 notmuch_status_t
164 _notmuch_regexp_to_query (notmuch_database_t *notmuch, Xapian::valueno slot, std::string field,
165                           std::string regexp_str,
166                           Xapian::Query &output, std::string &msg)
167 {
168     regex_t regexp;
169     notmuch_status_t status;
170
171     status = compile_regex (regexp, regexp_str.c_str (), msg);
172     if (status) {
173         _notmuch_database_log_append (notmuch, "error compiling regex %s", msg.c_str ());
174         return status;
175     }
176
177     if (slot == Xapian::BAD_VALUENO)
178         slot = _find_slot (field);
179
180     if (slot == Xapian::BAD_VALUENO) {
181         std::string term_prefix = _find_prefix (field.c_str ());
182         std::vector<std::string> terms;
183
184         for (Xapian::TermIterator it = notmuch->xapian_db->allterms_begin (term_prefix);
185              it != notmuch->xapian_db->allterms_end (); ++it) {
186             if (regexec (&regexp, (*it).c_str () + term_prefix.size (),
187                          0, NULL, 0) == 0)
188                 terms.push_back (*it);
189         }
190         output = Xapian::Query (Xapian::Query::OP_OR, terms.begin (), terms.end ());
191     } else {
192         RegexpPostingSource *postings = new RegexpPostingSource (slot, regexp_str);
193         output = Xapian::Query (postings->release ());
194     }
195     return NOTMUCH_STATUS_SUCCESS;
196 }
197
198 Xapian::Query
199 RegexpFieldProcessor::operator() (const std::string & str)
200 {
201     if (str.empty ()) {
202         if (options & NOTMUCH_FIELD_PROBABILISTIC) {
203             return Xapian::Query (Xapian::Query::OP_AND_NOT,
204                                   xapian_query_match_all (),
205                                   Xapian::Query (Xapian::Query::OP_WILDCARD, term_prefix));
206         } else {
207             return Xapian::Query (term_prefix);
208         }
209     }
210
211     if (str.at (0) == '/') {
212         if (str.length () > 1 && str.at (str.size () - 1) == '/') {
213             Xapian::Query query;
214             std::string regexp_str = str.substr (1, str.size () - 2);
215             std::string msg;
216             notmuch_status_t status;
217
218             status = _notmuch_regexp_to_query (notmuch, slot, field, regexp_str, query, msg);
219             if (status)
220                 throw Xapian::QueryParserError (msg);
221             return query;
222         } else {
223             throw Xapian::QueryParserError ("unmatched regex delimiter in '" + str + "'");
224         }
225     } else {
226         if (options & NOTMUCH_FIELD_PROBABILISTIC) {
227             /* TODO replace this with a nicer API level triggering of
228              * phrase parsing, when possible */
229             std::string query_str;
230
231             if ((str.at (0) != '(' || *str.rbegin () != ')') &&
232                 (*str.rbegin () != '*' || str.find (' ') != std::string::npos))
233                 query_str = '"' + str + '"';
234             else
235                 query_str = str;
236
237             return parser.parse_query (query_str, NOTMUCH_QUERY_PARSER_FLAGS, term_prefix);
238         } else {
239             /* Boolean prefix */
240             std::string query_str;
241             std::string term;
242
243             if (str.length () > 1 && str.at (str.size () - 1) == '/')
244                 query_str = str.substr (0, str.size () - 1);
245             else
246                 query_str = str;
247
248             term = term_prefix + query_str;
249             return Xapian::Query (term);
250         }
251     }
252 }