Re: [PATCH 9/9] add has: query prefix to search for specific properties
[notmuch-archives.git] / 79 / e4b6617250b82db7a1f1172501bc74440d785b
1 Return-Path: <amdragon@mit.edu>\r
2 X-Original-To: notmuch@notmuchmail.org\r
3 Delivered-To: notmuch@notmuchmail.org\r
4 Received: from localhost (localhost [127.0.0.1])\r
5         by olra.theworths.org (Postfix) with ESMTP id 13FF3429E42\r
6         for <notmuch@notmuchmail.org>; Thu, 24 Oct 2013 08:19:50 -0700 (PDT)\r
7 X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
8 X-Spam-Flag: NO\r
9 X-Spam-Score: -0.7\r
10 X-Spam-Level: \r
11 X-Spam-Status: No, score=-0.7 tagged_above=-999 required=5\r
12         tests=[RCVD_IN_DNSWL_LOW=-0.7] autolearn=disabled\r
13 Received: from olra.theworths.org ([127.0.0.1])\r
14         by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
15         with ESMTP id si1eabb+EMCc for <notmuch@notmuchmail.org>;\r
16         Thu, 24 Oct 2013 08:19:44 -0700 (PDT)\r
17 Received: from dmz-mailsec-scanner-6.mit.edu (dmz-mailsec-scanner-6.mit.edu\r
18         [18.7.68.35])\r
19         by olra.theworths.org (Postfix) with ESMTP id EC773429E50\r
20         for <notmuch@notmuchmail.org>; Thu, 24 Oct 2013 08:19:26 -0700 (PDT)\r
21 X-AuditID: 12074423-b7fc98e0000009a2-ab-52693a7eb51b\r
22 Received: from mailhub-auth-3.mit.edu ( [18.9.21.43])\r
23         by dmz-mailsec-scanner-6.mit.edu (Symantec Messaging Gateway) with SMTP\r
24         id 3D.39.02466.E7A39625; Thu, 24 Oct 2013 11:19:26 -0400 (EDT)\r
25 Received: from outgoing.mit.edu (outgoing-auth-1.mit.edu [18.9.28.11])\r
26         by mailhub-auth-3.mit.edu (8.13.8/8.9.2) with ESMTP id r9OFJF1u030433; \r
27         Thu, 24 Oct 2013 11:19:15 -0400\r
28 Received: from drake.dyndns.org\r
29         (216-15-114-40.c3-0.arl-ubr1.sbo-arl.ma.cable.rcn.com\r
30         [216.15.114.40]) (authenticated bits=0)\r
31         (User authenticated as amdragon@ATHENA.MIT.EDU)\r
32         by outgoing.mit.edu (8.13.8/8.12.4) with ESMTP id r9OFJCXv012921\r
33         (version=TLSv1/SSLv3 cipher=AES256-SHA bits=256 verify=NOT);\r
34         Thu, 24 Oct 2013 11:19:14 -0400\r
35 Received: from amthrax by drake.dyndns.org with local (Exim 4.77)\r
36         (envelope-from <amdragon@mit.edu>)\r
37         id 1VZMgq-0006e2-RH; Thu, 24 Oct 2013 11:19:12 -0400\r
38 From: Austin Clements <amdragon@MIT.EDU>\r
39 To: notmuch@notmuchmail.org\r
40 Subject: [PATCH v2 08/11] search: Add stable queries to thread search results\r
41 Date: Thu, 24 Oct 2013 11:19:08 -0400\r
42 Message-Id: <1382627951-25252-9-git-send-email-amdragon@mit.edu>\r
43 X-Mailer: git-send-email 1.8.4.rc3\r
44 In-Reply-To: <1382627951-25252-1-git-send-email-amdragon@mit.edu>\r
45 References: <1382627951-25252-1-git-send-email-amdragon@mit.edu>\r
46 MIME-Version: 1.0\r
47 Content-Type: text/plain; charset=UTF-8\r
48 Content-Transfer-Encoding: 8bit\r
49 X-Brightmail-Tracker:\r
50  H4sIAAAAAAAAA+NgFuphleLIzCtJLcpLzFFi42IR4hTV1q2zygwyeDhJwqJpurPF6rk8Ftdv\r
51         zmR2YPbYOesuu8et+6/ZPZ6tusUcwBzFZZOSmpNZllqkb5fAldGzag9TwWfXiv6HW1gaGH+b\r
52         dzFyckgImEgcazzHDmGLSVy4t56ti5GLQ0hgH6PEwoMtrBDORkaJ7/PeMEI4d5gkjr39BlU2\r
53         l1Hi7LKjbCD9bAIaEtv2L2cEsUUEpCV23p3NCmIzC0RLHLk8A6xGWMBX4vDRc8wgNouAqsSt\r
54         s59ZQGxeAQeJ659eMULcoSSx8NQ2sF5OAUeJn6+vgcWFgGp+/T4DVS8ocXLmEyCbA2i+usT6\r
55         eUIQq+QlmrfOZp7AKDQLSdUshKpZSKoWMDKvYpRNya3SzU3MzClOTdYtTk7My0st0jXTy80s\r
56         0UtNKd3ECAp0dhflHYx/DiodYhTgYFTi4dX4kB4kxJpYVlyZe4hRkoNJSZT3nmlmkBBfUn5K\r
57         ZUZicUZ8UWlOavEhRgkOZiUR3ml6QDnelMTKqtSifJiUNAeLkjjvLQ77ICGB9MSS1OzU1ILU\r
58         IpisDAeHkgTvP0ugRsGi1PTUirTMnBKENBMHJ8hwHqDh90BqeIsLEnOLM9Mh8qcYFaXEeXmB\r
59         qURIACSRUZoH1wtLRK8YxYFeEeb9ANLOA0xicN2vgAYzAQ2esiQNZHBJIkJKqoHxMuv2BSLZ\r
60         kZ/nzZ1/SW3Bg01Hwg4nO+bMDdW/V8+04UBxMpP14xcMj/IEl52uVDi9+eSRKH5uy6r27LqG\r
61         18UXk+9lKBkqFF+Jc3We9GU2z3I5NsYtiyeot79rtIy4Nm/yy3cPT0z4XfjRervBBxPzjV8D\r
62         pOPU/5k8YbyRrBXNHGBbor2RrXKyEktxRqKhFnNRcSIARMMbFx8DAAA=\r
63 X-BeenThere: notmuch@notmuchmail.org\r
64 X-Mailman-Version: 2.1.13\r
65 Precedence: list\r
66 List-Id: "Use and development of the notmuch mail system."\r
67         <notmuch.notmuchmail.org>\r
68 List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
69         <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
70 List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
71 List-Post: <mailto:notmuch@notmuchmail.org>\r
72 List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
73 List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
74         <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
75 X-List-Received-Date: Thu, 24 Oct 2013 15:19:50 -0000\r
76 \r
77 These queries will match exactly the set of messages currently in the\r
78 thread, even if more messages later arrive.  Two queries are provided:\r
79 one for matched messages and one for unmatched messages.\r
80 \r
81 This can be used to fix race conditions with tagging threads from\r
82 search results.  While tagging based on a thread: query can affect\r
83 messages that arrived after the search, tagging based on stable\r
84 queries affects only the messages the user was shown in the search UI.\r
85 \r
86 Since we want clients to be able to depend on the presence of these\r
87 queries, this ushers in schema version 2.\r
88 ---\r
89  devel/schemata       | 22 ++++++++++++++++++--\r
90  notmuch-client.h     |  2 +-\r
91  notmuch-search.c     | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++\r
92  test/json            |  2 ++\r
93  test/missing-headers |  6 ++++--\r
94  test/sexp            |  4 ++--\r
95  6 files changed, 88 insertions(+), 7 deletions(-)\r
96 \r
97 diff --git a/devel/schemata b/devel/schemata\r
98 index cdd0e43..41dc4a6 100644\r
99 --- a/devel/schemata\r
100 +++ b/devel/schemata\r
101 @@ -14,7 +14,17 @@ are interleaved. Keys are printed as keywords (symbols preceded by a\r
102  colon), e.g. (:id "123" :time 54321 :from "foobar"). Null is printed as\r
103  nil, true as t and false as nil.\r
104  \r
105 -This is version 1 of the structured output format.\r
106 +This is version 2 of the structured output format.\r
107 +\r
108 +Version history\r
109 +---------------\r
110 +\r
111 +v1\r
112 +- First versioned schema release.\r
113 +- Added part.content-length and part.content-transfer-encoding fields.\r
114 +\r
115 +v2\r
116 +- Added the thread_summary.query field.\r
117  \r
118  Common non-terminals\r
119  --------------------\r
120 @@ -145,7 +155,15 @@ thread_summary = {\r
121      authors:        string,   # comma-separated names with | between\r
122                                # matched and unmatched\r
123      subject:        string,\r
124 -    tags:           [string*]\r
125 +    tags:           [string*],\r
126 +\r
127 +    # Two stable query strings identifying exactly the matched and\r
128 +    # unmatched messages currently in this thread.  The messages\r
129 +    # matched by these queries will not change even if more messages\r
130 +    # arrive in the thread.  If there are no matched or unmatched\r
131 +    # messages, the corresponding query will be null (there is no\r
132 +    # query that matches nothing).  (Added in schema version 2.)\r
133 +    query:          [string|null, string|null],\r
134  }\r
135  \r
136  notmuch reply schema\r
137 diff --git a/notmuch-client.h b/notmuch-client.h\r
138 index 4ecb3ae..278b498 100644\r
139 --- a/notmuch-client.h\r
140 +++ b/notmuch-client.h\r
141 @@ -138,7 +138,7 @@ chomp_newline (char *str)\r
142   * this.  New (required) map fields can be added without increasing\r
143   * this.\r
144   */\r
145 -#define NOTMUCH_FORMAT_CUR 1\r
146 +#define NOTMUCH_FORMAT_CUR 2\r
147  /* The minimum supported structured output format version.  Requests\r
148   * for format versions below this will return an error. */\r
149  #define NOTMUCH_FORMAT_MIN 1\r
150 diff --git a/notmuch-search.c b/notmuch-search.c\r
151 index d9d39ec..7c973b3 100644\r
152 --- a/notmuch-search.c\r
153 +++ b/notmuch-search.c\r
154 @@ -20,6 +20,7 @@\r
155  \r
156  #include "notmuch-client.h"\r
157  #include "sprinter.h"\r
158 +#include "string-util.h"\r
159  \r
160  typedef enum {\r
161      OUTPUT_SUMMARY,\r
162 @@ -46,6 +47,45 @@ sanitize_string (const void *ctx, const char *str)\r
163      return out;\r
164  }\r
165  \r
166 +/* Return two stable query strings that identify exactly the matched\r
167 + * and unmatched messages currently in thread.  If there are no\r
168 + * matched or unmatched messages, the returned buffers will be\r
169 + * NULL. */\r
170 +static int\r
171 +get_thread_query (notmuch_thread_t *thread,\r
172 +                 char **matched_out, char **unmatched_out)\r
173 +{\r
174 +    notmuch_messages_t *messages;\r
175 +    char *escaped = NULL;\r
176 +    size_t escaped_len = 0;\r
177 +\r
178 +    *matched_out = *unmatched_out = NULL;\r
179 +\r
180 +    for (messages = notmuch_thread_get_messages (thread);\r
181 +        notmuch_messages_valid (messages);\r
182 +        notmuch_messages_move_to_next (messages))\r
183 +    {\r
184 +       notmuch_message_t *message = notmuch_messages_get (messages);\r
185 +       const char *mid = notmuch_message_get_message_id (message);\r
186 +       /* Determine which query buffer to extend */\r
187 +       char **buf = notmuch_message_get_flag (\r
188 +           message, NOTMUCH_MESSAGE_FLAG_MATCH) ? matched_out : unmatched_out;\r
189 +       /* Add this message's id: query.  Since "id" is an exclusive\r
190 +        * prefix, it is implicitly 'or'd together, so we only need to\r
191 +        * join queries with a space. */\r
192 +       if (make_boolean_term (thread, "id", mid, &escaped, &escaped_len) < 0)\r
193 +           return -1;\r
194 +       if (*buf)\r
195 +           *buf = talloc_asprintf_append_buffer (*buf, " %s", escaped);\r
196 +       else\r
197 +           *buf = talloc_strdup (thread, escaped);\r
198 +       if (!*buf)\r
199 +           return -1;\r
200 +    }\r
201 +    talloc_free (escaped);\r
202 +    return 0;\r
203 +}\r
204 +\r
205  static int\r
206  do_search_threads (sprinter_t *format,\r
207                    notmuch_query_t *query,\r
208 @@ -131,6 +171,25 @@ do_search_threads (sprinter_t *format,\r
209                 format->string (format, authors);\r
210                 format->map_key (format, "subject");\r
211                 format->string (format, subject);\r
212 +               if (notmuch_format_version >= 2) {\r
213 +                   char *matched_query, *unmatched_query;\r
214 +                   if (get_thread_query (thread, &matched_query,\r
215 +                                         &unmatched_query) < 0) {\r
216 +                       fprintf (stderr, "Out of memory\n");\r
217 +                       return 1;\r
218 +                   }\r
219 +                   format->map_key (format, "query");\r
220 +                   format->begin_list (format);\r
221 +                   if (matched_query)\r
222 +                       format->string (format, matched_query);\r
223 +                   else\r
224 +                       format->null (format);\r
225 +                   if (unmatched_query)\r
226 +                       format->string (format, unmatched_query);\r
227 +                   else\r
228 +                       format->null (format);\r
229 +                   format->end (format);\r
230 +               }\r
231             }\r
232  \r
233             talloc_free (ctx_quote);\r
234 diff --git a/test/json b/test/json\r
235 index b87b7f6..e07a290 100755\r
236 --- a/test/json\r
237 +++ b/test/json\r
238 @@ -26,6 +26,7 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\",\r
239   \"total\": 1,\r
240   \"authors\": \"Notmuch Test Suite\",\r
241   \"subject\": \"json-search-subject\",\r
242 + \"query\": [\"id:$gen_msg_id\", null],\r
243   \"tags\": [\"inbox\",\r
244   \"unread\"]}]"\r
245  \r
246 @@ -59,6 +60,7 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\",\r
247   \"total\": 1,\r
248   \"authors\": \"Notmuch Test Suite\",\r
249   \"subject\": \"json-search-utf8-body-sübjéct\",\r
250 + \"query\": [\"id:$gen_msg_id\", null],\r
251   \"tags\": [\"inbox\",\r
252   \"unread\"]}]"\r
253  \r
254 diff --git a/test/missing-headers b/test/missing-headers\r
255 index f14b878..43e861b 100755\r
256 --- a/test/missing-headers\r
257 +++ b/test/missing-headers\r
258 @@ -43,7 +43,8 @@ test_expect_equal_json "$output" '\r
259          ],\r
260          "thread": "XXX",\r
261          "timestamp": 978709437,\r
262 -        "total": 1\r
263 +        "total": 1,\r
264 +        "query": ["id:notmuch-sha1-7a6e4eac383ef958fcd3ebf2143db71b8ff01161", null]\r
265      },\r
266      {\r
267          "authors": "Notmuch Test Suite",\r
268 @@ -56,7 +57,8 @@ test_expect_equal_json "$output" '\r
269          ],\r
270          "thread": "XXX",\r
271          "timestamp": 0,\r
272 -        "total": 1\r
273 +        "total": 1,\r
274 +        "query": ["id:notmuch-sha1-ca55943aff7a72baf2ab21fa74fab3d632401334", null]\r
275      }\r
276  ]'\r
277  \r
278 diff --git a/test/sexp b/test/sexp\r
279 index 492a82f..be815e1 100755\r
280 --- a/test/sexp\r
281 +++ b/test/sexp\r
282 @@ -19,7 +19,7 @@ test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :f\r
283  test_begin_subtest "Search message: sexp"\r
284  add_message "[subject]=\"sexp-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"sexp-search-message\""\r
285  output=$(notmuch search --format=sexp "sexp-search-message" | notmuch_search_sanitize)\r
286 -test_expect_equal "$output" "((:thread \"0000000000000002\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-subject\" :tags (\"inbox\" \"unread\")))"\r
287 +test_expect_equal "$output" "((:thread \"0000000000000002\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-subject\" :query (\"id:$gen_msg_id\" nil) :tags (\"inbox\" \"unread\")))"\r
288  \r
289  test_begin_subtest "Show message: sexp, utf-8"\r
290  add_message "[subject]=\"sexp-show-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-show-méssage\""\r
291 @@ -44,7 +44,7 @@ test_expect_equal "$output" "((((:id \"$id\" :match t :excluded nil :filename \"\r
292  test_begin_subtest "Search message: sexp, utf-8"\r
293  add_message "[subject]=\"sexp-search-utf8-body-sübjéct\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"jsön-search-méssage\""\r
294  output=$(notmuch search --format=sexp "jsön-search-méssage" | notmuch_search_sanitize)\r
295 -test_expect_equal "$output" "((:thread \"0000000000000005\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-utf8-body-sübjéct\" :tags (\"inbox\" \"unread\")))"\r
296 +test_expect_equal "$output" "((:thread \"0000000000000005\" :timestamp 946728000 :date_relative \"2000-01-01\" :matched 1 :total 1 :authors \"Notmuch Test Suite\" :subject \"sexp-search-utf8-body-sübjéct\" :query (\"id:$gen_msg_id\" nil) :tags (\"inbox\" \"unread\")))"\r
297  \r
298  \r
299  test_done\r
300 -- \r
301 1.8.4.rc3\r
302 \r