From: Timo Sirainen Date: Tue, 2 Dec 2025 16:47:43 +0000 (+0200) Subject: imapc: Implement SORT optimization X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=97adde09653753ec9269d34d17a051585bd2ce05;p=thirdparty%2Fdovecot%2Fcore.git imapc: Implement SORT optimization --- diff --git a/src/lib-imap-client/imapc-settings.c b/src/lib-imap-client/imapc-settings.c index efb57f3c7b..851b883f0d 100644 --- a/src/lib-imap-client/imapc-settings.c +++ b/src/lib-imap-client/imapc-settings.c @@ -111,6 +111,7 @@ const struct imapc_capability_name imapc_capability_names[] = { { "ID", IMAPC_CAPABILITY_ID }, { "SAVEDATE", IMAPC_CAPABILITY_SAVEDATE }, { "METADATA", IMAPC_CAPABILITY_METADATA }, + { "SORT", IMAPC_CAPABILITY_SORT }, { "IMAP4REV1", IMAPC_CAPABILITY_IMAP4REV1 }, { "IMAP4REV2", IMAPC_CAPABILITY_IMAP4REV2 }, diff --git a/src/lib-imap-client/imapc-settings.h b/src/lib-imap-client/imapc-settings.h index 0df5d9b2ba..da4db8887c 100644 --- a/src/lib-imap-client/imapc-settings.h +++ b/src/lib-imap-client/imapc-settings.h @@ -45,6 +45,7 @@ enum imapc_capability { IMAPC_CAPABILITY_ID = 0x4000, IMAPC_CAPABILITY_SAVEDATE = 0x8000, IMAPC_CAPABILITY_METADATA = 0x10000, + IMAPC_CAPABILITY_SORT = 0x20000, IMAPC_CAPABILITY_IMAP4REV2 = 0x20000000, IMAPC_CAPABILITY_IMAP4REV1 = 0x40000000, diff --git a/src/lib-storage/index/imapc/imapc-mailbox.c b/src/lib-storage/index/imapc/imapc-mailbox.c index 474d58e95e..e44a493a9a 100644 --- a/src/lib-storage/index/imapc/imapc-mailbox.c +++ b/src/lib-storage/index/imapc/imapc-mailbox.c @@ -840,6 +840,19 @@ static void imapc_untagged_search(const struct imapc_untagged_reply *reply, imapc_search_reply_search(reply->args, mbox); } +static void imapc_untagged_sort(const struct imapc_untagged_reply *reply, + struct imapc_mailbox *mbox) +{ + if (mbox == NULL) + return; + if (!IMAPC_MAILBOX_IS_FULLY_SELECTED(mbox)) { + /* SELECTing another mailbox - this SORT is still for the + previous selected mailbox. */ + return; + } + imapc_search_reply_sort(reply->args, mbox); +} + static void imapc_untagged_esearch(const struct imapc_untagged_reply *reply, struct imapc_mailbox *mbox) { @@ -1003,6 +1016,8 @@ void imapc_mailbox_register_callbacks(struct imapc_mailbox *mbox) imapc_untagged_expunge); imapc_mailbox_register_untagged(mbox, "SEARCH", imapc_untagged_search); + imapc_mailbox_register_untagged(mbox, "SORT", + imapc_untagged_sort); imapc_mailbox_register_untagged(mbox, "ESEARCH", imapc_untagged_esearch); imapc_mailbox_register_resp_text(mbox, "UIDVALIDITY", diff --git a/src/lib-storage/index/imapc/imapc-search.c b/src/lib-storage/index/imapc/imapc-search.c index fc52a6035e..21b0e366bf 100644 --- a/src/lib-storage/index/imapc/imapc-search.c +++ b/src/lib-storage/index/imapc/imapc-search.c @@ -6,31 +6,100 @@ #include "imap-seqset.h" #include "imap-util.h" #include "mail-search.h" +#include "mail-storage-private.h" #include "imapc-msgmap.h" #include "imapc-storage.h" #include "imapc-search.h" +#include "index-sort.h" #define IMAPC_SEARCHCTX(obj) \ MODULE_CONTEXT(obj, imapc_storage_module) +static bool +imapc_build_search_query_args(struct imapc_mailbox *mbox, + const struct mail_search_arg *args, + bool parent_or, string_t *str); + +static bool +imapc_build_sort_query(struct imapc_mailbox *mbox, + const struct mail_search_args *args, + const enum mail_sort_type *sort_program, + const char **query_r) +{ + string_t *str = t_str_new(128); + const char *charset = "UTF-8"; + unsigned int i; + + if ((mbox->capabilities & IMAPC_CAPABILITY_SORT) == 0) { + /* SORT command passthrough not possible */ + return FALSE; + } + + str_append(str, "UID SORT ("); + for (i = 0; sort_program[i] != MAIL_SORT_END; i++) { + if ((sort_program[i] & MAIL_SORT_FLAG_REVERSE) != 0) + str_append(str, "REVERSE "); + switch (sort_program[i] & MAIL_SORT_MASK) { + case MAIL_SORT_ARRIVAL: + str_append(str, "ARRIVAL"); + break; + case MAIL_SORT_CC: + str_append(str, "CC"); + break; + case MAIL_SORT_DATE: + str_append(str, "DATE"); + break; + case MAIL_SORT_FROM: + str_append(str, "FROM"); + break; + case MAIL_SORT_SIZE: + str_append(str, "SIZE"); + break; + case MAIL_SORT_SUBJECT: + str_append(str, "SUBJECT"); + break; + case MAIL_SORT_TO: + str_append(str, "TO"); + break; + case MAIL_SORT_DISPLAYFROM: + case MAIL_SORT_DISPLAYTO: + case MAIL_SORT_RELEVANCY: + case MAIL_SORT_POP3_ORDER: + return FALSE; + default: + i_unreached(); + } + if (sort_program[i+1] != MAIL_SORT_END) + str_append_c(str, ' '); + } + str_append(str, ") "); + str_append(str, charset); + str_append_c(str, ' '); + + if (args->args == NULL) + str_append(str, "ALL"); + else if (!imapc_build_search_query_args(mbox, args->args, FALSE, str)) + return FALSE; + *query_r = str_c(str); + return TRUE; +} + + struct imapc_search_context { union mail_search_module_context module_ctx; ARRAY_TYPE(seq_range) rseqs; + ARRAY_TYPE(uint32_t) sorted_uids; struct seq_range_iter iter; unsigned int n; bool finished; bool success; + bool sorted; }; static MODULE_CONTEXT_DEFINE_INIT(imapc_storage_module, &mail_storage_module_register); -static bool -imapc_build_search_query_args(struct imapc_mailbox *mbox, - const struct mail_search_arg *args, - bool parent_or, string_t *str); - static bool imapc_search_is_fast_local(const struct mail_search_arg *args) { const struct mail_search_arg *arg; @@ -227,16 +296,23 @@ imapc_search_init(struct mailbox_transaction_context *t, struct imapc_command *cmd; const char *search_query; - ctx = index_storage_search_init(t, args, sort_program, - wanted_fields, wanted_headers); - - if (!imapc_build_search_query(mbox, args, &search_query)) { - /* can't optimize this with SEARCH */ - return ctx; + if (sort_program != NULL && + imapc_build_sort_query(mbox, args, sort_program, &search_query)) { + ctx = index_storage_search_init(t, args, NULL, + wanted_fields, wanted_headers); + ictx = i_new(struct imapc_search_context, 1); + ictx->sorted = TRUE; + } else { + ctx = index_storage_search_init(t, args, sort_program, + wanted_fields, wanted_headers); + if (!imapc_build_search_query(mbox, args, &search_query)) { + /* can't optimize this with SEARCH */ + return ctx; + } + ictx = i_new(struct imapc_search_context, 1); } - - ictx = i_new(struct imapc_search_context, 1); i_array_init(&ictx->rseqs, 64); + i_array_init(&ictx->sorted_uids, 64); MODULE_CONTEXT_SET(ctx, imapc_storage_module, ictx); cmd = imapc_client_mailbox_cmd(mbox->client_box, @@ -266,10 +342,20 @@ static void imapc_search_set_matches(struct mail_search_arg *args) bool imapc_search_next_update_seq(struct mail_search_context *ctx) { struct imapc_search_context *ictx = IMAPC_SEARCHCTX(ctx); + const uint32_t *uidp; if (ictx == NULL || !ictx->success) return index_storage_search_next_update_seq(ctx); + if (ictx->sorted) { + while (ictx->n < array_count(&ictx->sorted_uids)) { + uidp = array_idx(&ictx->sorted_uids, ictx->n++); + if (mail_index_lookup_seq(ctx->transaction->view, *uidp, &ctx->seq)) + return TRUE; + } + return FALSE; + } + if (!seq_range_array_iter_nth(&ictx->iter, ictx->n++, &ctx->seq)) return FALSE; ctx->progress_cur = ctx->seq; @@ -287,6 +373,7 @@ int imapc_search_deinit(struct mail_search_context *ctx) if (!ictx->success) ret = -1; array_free(&ictx->rseqs); + array_free(&ictx->sorted_uids); i_free(ictx); } if (index_storage_search_deinit(ctx) < 0) @@ -303,7 +390,7 @@ void imapc_search_reply_search(const struct imap_arg *args, const char *atom; uint32_t uid, rseq; - if (mbox->search_ctx == NULL) { + if (mbox->search_ctx == NULL || mbox->search_ctx->sorted) { e_error(event, "Unexpected SEARCH reply"); return; } @@ -338,3 +425,25 @@ void imapc_search_reply_esearch(const struct imap_arg *args, imap_seq_set_nostar_parse(atom, &mbox->search_ctx->rseqs) < 0)) e_error(event, "Invalid ESEARCH reply"); } + +void imapc_search_reply_sort(const struct imap_arg *args, + struct imapc_mailbox *mbox) +{ + struct event *event = mbox->box.event; + const char *atom; + uint32_t uid; + + if (mbox->search_ctx == NULL || !mbox->search_ctx->sorted) { + e_error(event, "Unexpected SORT reply"); + return; + } + + for (unsigned int i = 0; args[i].type != IMAP_ARG_EOL; i++) { + if (!imap_arg_get_atom(&args[i], &atom) || + str_to_uint32(atom, &uid) < 0 || uid == 0) { + e_error(event, "Invalid SORT reply"); + break; + } + array_push_back(&mbox->search_ctx->sorted_uids, &uid); + } +} diff --git a/src/lib-storage/index/imapc/imapc-search.h b/src/lib-storage/index/imapc/imapc-search.h index 0966569ca5..3bce160664 100644 --- a/src/lib-storage/index/imapc/imapc-search.h +++ b/src/lib-storage/index/imapc/imapc-search.h @@ -14,5 +14,7 @@ void imapc_search_reply_search(const struct imap_arg *args, struct imapc_mailbox *mbox); void imapc_search_reply_esearch(const struct imap_arg *args, struct imapc_mailbox *mbox); +void imapc_search_reply_sort(const struct imap_arg *args, + struct imapc_mailbox *mbox); #endif