uint32_t seq1, seq2;
struct mail *mail;
struct index_mail *imail;
+ struct mail_thread_context *thread_ctx;
const char *error;
ctx->have_seqsets = TRUE;
break;
case SEARCH_UIDSET:
+ case SEARCH_INTHREAD:
case SEARCH_FLAGS:
case SEARCH_KEYWORDS:
case SEARCH_MODSEQ:
switch (arg->type) {
case SEARCH_UIDSET:
+ case SEARCH_INTHREAD:
return seq_range_exists(&arg->value.seqset, rec->uid);
case SEARCH_FLAGS:
/* recent flag shouldn't be set, but indexes from v1.0.x
}
}
+static int search_build_subthread(struct mail_thread_iterate_context *iter,
+ ARRAY_TYPE(seq_range) *uids)
+{
+ struct mail_thread_iterate_context *child_iter;
+ const struct mail_thread_child_node *node;
+ int ret = 0;
+
+ while ((node = mail_thread_iterate_next(iter, &child_iter)) != NULL) {
+ if (child_iter != NULL) {
+ if (search_build_subthread(child_iter, uids) < 0)
+ ret = -1;
+ }
+ seq_range_array_add(uids, 0, node->uid);
+ }
+ if (mail_thread_iterate_deinit(&iter) < 0)
+ ret = -1;
+ return ret;
+}
+
+static int search_build_inthread_result(struct index_search_context *ctx,
+ struct mail_search_arg *arg)
+{
+ struct mail_thread_iterate_context *iter, *child_iter;
+ const struct mail_thread_child_node *node;
+ const ARRAY_TYPE(seq_range) *search_uids;
+ ARRAY_TYPE(seq_range) thread_uids;
+ int ret;
+
+ p_array_init(&arg->value.seqset, ctx->mail_ctx.args->pool, 64);
+ if (mailbox_search_result_build(ctx->mail_ctx.transaction,
+ arg->value.search_args,
+ MAILBOX_SEARCH_RESULT_FLAG_UPDATE |
+ MAILBOX_SEARCH_RESULT_FLAG_QUEUE_SYNC,
+ &arg->value.search_result) < 0)
+ return -1;
+ if (ctx->thread_ctx == NULL) {
+ /* failed earlier */
+ return -1;
+ }
+
+ search_uids = mailbox_search_result_get(arg->value.search_result);
+ if (array_count(search_uids) == 0) {
+ /* search found nothing - no threads can match */
+ return 0;
+ }
+
+ t_array_init(&thread_uids, 128);
+ iter = mail_thread_iterate_init(ctx->thread_ctx,
+ arg->value.thread_type, FALSE);
+ while ((node = mail_thread_iterate_next(iter, &child_iter)) != NULL) {
+ seq_range_array_add(&thread_uids, 0, node->uid);
+ if (child_iter != NULL) {
+ if (search_build_subthread(child_iter,
+ &thread_uids) < 0)
+ ret = -1;
+ }
+ if (seq_range_array_have_common(&thread_uids, search_uids)) {
+ /* yes, we want this thread */
+ seq_range_array_merge(&arg->value.seqset, &thread_uids);
+ }
+ array_clear(&thread_uids);
+ }
+ if (mail_thread_iterate_deinit(&iter) < 0)
+ ret = -1;
+ return ret;
+}
+
+static int search_build_inthreads(struct index_search_context *ctx,
+ struct mail_search_arg *arg)
+{
+ int ret = 0;
+
+ for (; arg != NULL; arg = arg->next) {
+ switch (arg->type) {
+ case SEARCH_OR:
+ case SEARCH_SUB:
+ if (search_build_inthreads(ctx, arg->value.subargs) < 0)
+ ret = -1;
+ break;
+ case SEARCH_INTHREAD:
+ if (search_build_inthread_result(ctx, arg) < 0)
+ ret = -1;
+ break;
+ default:
+ break;
+ }
+ }
+ return ret;
+}
+
struct mail_search_context *
index_storage_search_init(struct mailbox_transaction_context *_t,
struct mail_search_args *args,
sizeof(void *), 5);
mail_search_args_reset(ctx->mail_ctx.args->args, TRUE);
+ if (args->have_inthreads) {
+ if (mail_thread_init(_t->box, FALSE, NULL,
+ &ctx->thread_ctx) < 0)
+ ctx->failed = TRUE;
+ if (search_build_inthreads(ctx, args->args) < 0)
+ ctx->failed = TRUE;
+ }
search_get_seqset(ctx, args->args);
(void)mail_search_args_foreach(args->args, search_init_arg, ctx);
if (ctx->mail_ctx.sort_program != NULL)
index_sort_program_deinit(&ctx->mail_ctx.sort_program);
+ if (ctx->thread_ctx != NULL)
+ mail_thread_deinit(&ctx->thread_ctx);
array_free(&ctx->mail_ctx.results);
array_free(&ctx->mail_ctx.module_contexts);
i_free(ctx);
case SEARCH_FLAGS:
case SEARCH_KEYWORDS:
case SEARCH_MODSEQ:
+ case SEARCH_INTHREAD:
break;
case SEARCH_ALL:
case SEARCH_UIDSET:
return ARG_NEW_HEADER(SEARCH_HEADER, key);
}
break;
+ case 'I':
+ if (strcmp(str, "INTHREAD") == 0) {
+ /* <algorithm> <search key> */
+ enum mail_thread_type thread_type;
+ const char *str;
+
+ if ((*args)->type != IMAP_ARG_ATOM) {
+ data->error = "Invalid parameter for INTHREAD";
+ return FALSE;
+ }
+
+ str = IMAP_ARG_STR_NONULL(*args);
+ if (!mail_thread_type_parse(str, &thread_type)) {
+ data->error = "Unknown thread algorithm";
+ return FALSE;
+ }
+ *args += 1;
+
+ *next_sarg = search_arg_new(data->pool,
+ SEARCH_INTHREAD);
+ (*next_sarg)->value.thread_type = thread_type;
+ subargs = &(*next_sarg)->value.subargs;
+ return search_arg_build(data, args, subargs);
+ }
+ break;
case 'K':
if (strcmp(str, "KEYWORD") == 0) {
return ARG_NEW_STR(SEARCH_KEYWORDS);
bool change_uidsets,
const ARRAY_TYPE(seq_range) *search_saved_uidset)
{
+ struct mail_search_args *thread_args;
const char *keywords[2];
for (; arg != NULL; arg = arg->next) {
keywords);
break;
+ case SEARCH_INTHREAD:
+ thread_args = arg->value.search_args;
+ if (thread_args == NULL) {
+ arg->value.search_args = thread_args =
+ p_new(args->pool,
+ struct mail_search_args, 1);
+ thread_args->pool = args->pool;
+ thread_args->args = arg->value.subargs;
+ thread_args->charset = args->charset;
+ thread_args->simplified = TRUE;
+ /* simplification should have unnested all
+ inthreads, so we'll assume that
+ have_inthreads=FALSE */
+ }
+ thread_args->refcount++;
+ thread_args->box = args->box;
+ /* fall through */
case SEARCH_SUB:
case SEARCH_OR:
mail_search_args_init_sub(args, arg->value.subargs,
break;
mailbox_keywords_free(args->box, &arg->value.keywords);
break;
+ case SEARCH_INTHREAD:
+ i_assert(arg->value.search_args->refcount > 0);
+ arg->value.search_args->refcount--;
+ arg->value.search_args->box = NULL;
+ if (args->refcount == 0 &&
+ arg->value.search_result != NULL) {
+ mailbox_search_result_free(
+ &arg->value.search_result);
+ }
+ /* fall through */
case SEARCH_SUB:
case SEARCH_OR:
mail_search_args_deinit_sub(args, arg->value.subargs);
break;
case SEARCH_SUB:
case SEARCH_OR:
+ case SEARCH_INTHREAD:
mail_search_args_seq2uid_sub(args, arg->value.subargs,
uids);
break;
continue;
}
- if (args->type == SEARCH_SUB || args->type == SEARCH_OR) {
+ if (args->type == SEARCH_SUB ||
+ args->type == SEARCH_OR ||
+ args->type == SEARCH_INTHREAD) {
mail_search_args_simplify_sub(args->value.subargs,
- args->type == SEARCH_SUB);
+ args->type != SEARCH_OR);
}
/* merge all flags arguments */
}
}
+static bool
+mail_search_args_unnest_inthreads(struct mail_search_args *args,
+ struct mail_search_arg **argp,
+ bool parent_inthreads, bool parent_and)
+{
+ struct mail_search_arg *arg, *thread_arg, *or_arg;
+ bool child_inthreads = FALSE, non_inthreads = FALSE;
+
+ for (arg = *argp; arg != NULL; arg = arg->next) {
+ switch (arg->type) {
+ case SEARCH_SUB:
+ case SEARCH_OR:
+ if (!mail_search_args_unnest_inthreads(args,
+ &arg->value.subargs, parent_inthreads,
+ arg->type != SEARCH_OR)) {
+ arg->result = 1;
+ child_inthreads = TRUE;
+ } else {
+ arg->result = 0;
+ non_inthreads = TRUE;
+ }
+ break;
+ case SEARCH_INTHREAD:
+ if (mail_search_args_unnest_inthreads(args,
+ &arg->value.subargs, TRUE, TRUE)) {
+ /* children converted to SEARCH_INTHREADs */
+ arg->type = SEARCH_SUB;
+ }
+ args->have_inthreads = TRUE;
+ arg->result = 1;
+ child_inthreads = TRUE;
+ break;
+ default:
+ arg->result = 0;
+ non_inthreads = TRUE;
+ break;
+ }
+ }
+
+ if (!parent_inthreads || !child_inthreads || !non_inthreads)
+ return FALSE;
+
+ /* put all non-INTHREADs under a single INTHREAD */
+ thread_arg = p_new(args->pool, struct mail_search_arg, 1);
+ thread_arg->type = SEARCH_INTHREAD;
+
+ while (*argp != NULL) {
+ arg = *argp;
+ argp = &(*argp)->next;
+
+ if (arg->result == 0) {
+ /* not an INTHREAD or a SUB/OR with only INTHREADs */
+ arg->next = thread_arg->value.subargs;
+ thread_arg->value.subargs = arg;
+ }
+ }
+ if (!parent_and) {
+ /* We want to OR the args */
+ or_arg = p_new(args->pool, struct mail_search_arg, 1);
+ or_arg->type = SEARCH_OR;
+ or_arg->value.subargs = thread_arg->value.subargs;
+ thread_arg->value.subargs = or_arg;
+ }
+ return TRUE;
+}
+
void mail_search_args_simplify(struct mail_search_args *args)
{
args->simplified = TRUE;
+
mail_search_args_simplify_sub(args->args, TRUE);
+ if (mail_search_args_unnest_inthreads(args, &args->args,
+ FALSE, TRUE)) {
+ /* we may have added some extra SUBs that could be dropped */
+ mail_search_args_simplify_sub(args->args, TRUE);
+ }
}
#include "seq-range-array.h"
#include "mail-types.h"
+#include "mail-thread.h"
enum mail_search_arg_type {
SEARCH_OR,
SEARCH_TEXT_FAST,
/* extensions */
- SEARCH_MODSEQ
+ SEARCH_MODSEQ,
+ SEARCH_INTHREAD
};
enum mail_search_arg_flag {
time_t time;
uoff_t size;
enum mail_flags flags;
+ enum mail_search_arg_flag search_flags;
+ enum mail_thread_type thread_type;
struct mail_keywords *keywords;
struct mail_search_modseq *modseq;
- enum mail_search_arg_flag search_flags;
+ struct mail_search_args *search_args;
+ struct mail_search_result *search_result;
} value;
void *context;