mirror of
https://git.notmuchmail.org/git/notmuch
synced 2024-11-24 11:58:10 +01:00
search: Add stable queries to thread search results
These queries will match exactly the set of messages currently in the thread, even if more messages later arrive. Two queries are provided: one for matched messages and one for unmatched messages. This can be used to fix race conditions with tagging threads from search results. While tagging based on a thread: query can affect messages that arrived after the search, tagging based on stable queries affects only the messages the user was shown in the search UI. Since we want clients to be able to depend on the presence of these queries, this ushers in schema version 2.
This commit is contained in:
parent
730b8f61e0
commit
abeac48522
6 changed files with 88 additions and 7 deletions
|
@ -14,7 +14,17 @@ are interleaved. Keys are printed as keywords (symbols preceded by a
|
||||||
colon), e.g. (:id "123" :time 54321 :from "foobar"). Null is printed as
|
colon), e.g. (:id "123" :time 54321 :from "foobar"). Null is printed as
|
||||||
nil, true as t and false as nil.
|
nil, true as t and false as nil.
|
||||||
|
|
||||||
This is version 1 of the structured output format.
|
This is version 2 of the structured output format.
|
||||||
|
|
||||||
|
Version history
|
||||||
|
---------------
|
||||||
|
|
||||||
|
v1
|
||||||
|
- First versioned schema release.
|
||||||
|
- Added part.content-length and part.content-transfer-encoding fields.
|
||||||
|
|
||||||
|
v2
|
||||||
|
- Added the thread_summary.query field.
|
||||||
|
|
||||||
Common non-terminals
|
Common non-terminals
|
||||||
--------------------
|
--------------------
|
||||||
|
@ -145,7 +155,15 @@ thread_summary = {
|
||||||
authors: string, # comma-separated names with | between
|
authors: string, # comma-separated names with | between
|
||||||
# matched and unmatched
|
# matched and unmatched
|
||||||
subject: string,
|
subject: string,
|
||||||
tags: [string*]
|
tags: [string*],
|
||||||
|
|
||||||
|
# Two stable query strings identifying exactly the matched and
|
||||||
|
# unmatched messages currently in this thread. The messages
|
||||||
|
# matched by these queries will not change even if more messages
|
||||||
|
# arrive in the thread. If there are no matched or unmatched
|
||||||
|
# messages, the corresponding query will be null (there is no
|
||||||
|
# query that matches nothing). (Added in schema version 2.)
|
||||||
|
query: [string|null, string|null],
|
||||||
}
|
}
|
||||||
|
|
||||||
notmuch reply schema
|
notmuch reply schema
|
||||||
|
|
|
@ -138,7 +138,7 @@ chomp_newline (char *str)
|
||||||
* this. New (required) map fields can be added without increasing
|
* this. New (required) map fields can be added without increasing
|
||||||
* this.
|
* this.
|
||||||
*/
|
*/
|
||||||
#define NOTMUCH_FORMAT_CUR 1
|
#define NOTMUCH_FORMAT_CUR 2
|
||||||
/* The minimum supported structured output format version. Requests
|
/* The minimum supported structured output format version. Requests
|
||||||
* for format versions below this will return an error. */
|
* for format versions below this will return an error. */
|
||||||
#define NOTMUCH_FORMAT_MIN 1
|
#define NOTMUCH_FORMAT_MIN 1
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
|
|
||||||
#include "notmuch-client.h"
|
#include "notmuch-client.h"
|
||||||
#include "sprinter.h"
|
#include "sprinter.h"
|
||||||
|
#include "string-util.h"
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
OUTPUT_SUMMARY,
|
OUTPUT_SUMMARY,
|
||||||
|
@ -46,6 +47,45 @@ sanitize_string (const void *ctx, const char *str)
|
||||||
return out;
|
return out;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Return two stable query strings that identify exactly the matched
|
||||||
|
* and unmatched messages currently in thread. If there are no
|
||||||
|
* matched or unmatched messages, the returned buffers will be
|
||||||
|
* NULL. */
|
||||||
|
static int
|
||||||
|
get_thread_query (notmuch_thread_t *thread,
|
||||||
|
char **matched_out, char **unmatched_out)
|
||||||
|
{
|
||||||
|
notmuch_messages_t *messages;
|
||||||
|
char *escaped = NULL;
|
||||||
|
size_t escaped_len = 0;
|
||||||
|
|
||||||
|
*matched_out = *unmatched_out = NULL;
|
||||||
|
|
||||||
|
for (messages = notmuch_thread_get_messages (thread);
|
||||||
|
notmuch_messages_valid (messages);
|
||||||
|
notmuch_messages_move_to_next (messages))
|
||||||
|
{
|
||||||
|
notmuch_message_t *message = notmuch_messages_get (messages);
|
||||||
|
const char *mid = notmuch_message_get_message_id (message);
|
||||||
|
/* Determine which query buffer to extend */
|
||||||
|
char **buf = notmuch_message_get_flag (
|
||||||
|
message, NOTMUCH_MESSAGE_FLAG_MATCH) ? matched_out : unmatched_out;
|
||||||
|
/* Add this message's id: query. Since "id" is an exclusive
|
||||||
|
* prefix, it is implicitly 'or'd together, so we only need to
|
||||||
|
* join queries with a space. */
|
||||||
|
if (make_boolean_term (thread, "id", mid, &escaped, &escaped_len) < 0)
|
||||||
|
return -1;
|
||||||
|
if (*buf)
|
||||||
|
*buf = talloc_asprintf_append_buffer (*buf, " %s", escaped);
|
||||||
|
else
|
||||||
|
*buf = talloc_strdup (thread, escaped);
|
||||||
|
if (!*buf)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
talloc_free (escaped);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
static int
|
static int
|
||||||
do_search_threads (sprinter_t *format,
|
do_search_threads (sprinter_t *format,
|
||||||
notmuch_query_t *query,
|
notmuch_query_t *query,
|
||||||
|
@ -131,6 +171,25 @@ do_search_threads (sprinter_t *format,
|
||||||
format->string (format, authors);
|
format->string (format, authors);
|
||||||
format->map_key (format, "subject");
|
format->map_key (format, "subject");
|
||||||
format->string (format, subject);
|
format->string (format, subject);
|
||||||
|
if (notmuch_format_version >= 2) {
|
||||||
|
char *matched_query, *unmatched_query;
|
||||||
|
if (get_thread_query (thread, &matched_query,
|
||||||
|
&unmatched_query) < 0) {
|
||||||
|
fprintf (stderr, "Out of memory\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
format->map_key (format, "query");
|
||||||
|
format->begin_list (format);
|
||||||
|
if (matched_query)
|
||||||
|
format->string (format, matched_query);
|
||||||
|
else
|
||||||
|
format->null (format);
|
||||||
|
if (unmatched_query)
|
||||||
|
format->string (format, unmatched_query);
|
||||||
|
else
|
||||||
|
format->null (format);
|
||||||
|
format->end (format);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
talloc_free (ctx_quote);
|
talloc_free (ctx_quote);
|
||||||
|
|
|
@ -26,6 +26,7 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\",
|
||||||
\"total\": 1,
|
\"total\": 1,
|
||||||
\"authors\": \"Notmuch Test Suite\",
|
\"authors\": \"Notmuch Test Suite\",
|
||||||
\"subject\": \"json-search-subject\",
|
\"subject\": \"json-search-subject\",
|
||||||
|
\"query\": [\"id:$gen_msg_id\", null],
|
||||||
\"tags\": [\"inbox\",
|
\"tags\": [\"inbox\",
|
||||||
\"unread\"]}]"
|
\"unread\"]}]"
|
||||||
|
|
||||||
|
@ -59,6 +60,7 @@ test_expect_equal_json "$output" "[{\"thread\": \"XXX\",
|
||||||
\"total\": 1,
|
\"total\": 1,
|
||||||
\"authors\": \"Notmuch Test Suite\",
|
\"authors\": \"Notmuch Test Suite\",
|
||||||
\"subject\": \"json-search-utf8-body-sübjéct\",
|
\"subject\": \"json-search-utf8-body-sübjéct\",
|
||||||
|
\"query\": [\"id:$gen_msg_id\", null],
|
||||||
\"tags\": [\"inbox\",
|
\"tags\": [\"inbox\",
|
||||||
\"unread\"]}]"
|
\"unread\"]}]"
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,8 @@ test_expect_equal_json "$output" '
|
||||||
],
|
],
|
||||||
"thread": "XXX",
|
"thread": "XXX",
|
||||||
"timestamp": 978709437,
|
"timestamp": 978709437,
|
||||||
"total": 1
|
"total": 1,
|
||||||
|
"query": ["id:notmuch-sha1-7a6e4eac383ef958fcd3ebf2143db71b8ff01161", null]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"authors": "Notmuch Test Suite",
|
"authors": "Notmuch Test Suite",
|
||||||
|
@ -56,7 +57,8 @@ test_expect_equal_json "$output" '
|
||||||
],
|
],
|
||||||
"thread": "XXX",
|
"thread": "XXX",
|
||||||
"timestamp": 0,
|
"timestamp": 0,
|
||||||
"total": 1
|
"total": 1,
|
||||||
|
"query": ["id:notmuch-sha1-ca55943aff7a72baf2ab21fa74fab3d632401334", null]
|
||||||
}
|
}
|
||||||
]'
|
]'
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ test_expect_equal "$output" "((((:id \"${gen_msg_id}\" :match t :excluded nil :f
|
||||||
test_begin_subtest "Search message: sexp"
|
test_begin_subtest "Search message: sexp"
|
||||||
add_message "[subject]=\"sexp-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"sexp-search-message\""
|
add_message "[subject]=\"sexp-search-subject\"" "[date]=\"Sat, 01 Jan 2000 12:00:00 -0000\"" "[body]=\"sexp-search-message\""
|
||||||
output=$(notmuch search --format=sexp "sexp-search-message" | notmuch_search_sanitize)
|
output=$(notmuch search --format=sexp "sexp-search-message" | notmuch_search_sanitize)
|
||||||
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\")))"
|
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\")))"
|
||||||
|
|
||||||
test_begin_subtest "Show message: sexp, utf-8"
|
test_begin_subtest "Show message: sexp, utf-8"
|
||||||
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\""
|
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\""
|
||||||
|
@ -44,7 +44,7 @@ test_expect_equal "$output" "((((:id \"$id\" :match t :excluded nil :filename \"
|
||||||
test_begin_subtest "Search message: sexp, utf-8"
|
test_begin_subtest "Search message: sexp, utf-8"
|
||||||
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\""
|
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\""
|
||||||
output=$(notmuch search --format=sexp "jsön-search-méssage" | notmuch_search_sanitize)
|
output=$(notmuch search --format=sexp "jsön-search-méssage" | notmuch_search_sanitize)
|
||||||
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\")))"
|
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\")))"
|
||||||
|
|
||||||
|
|
||||||
test_done
|
test_done
|
||||||
|
|
Loading…
Reference in a new issue