]> git.ipfire.org Git - thirdparty/dovecot/core.git/commitdiff
imapc: Implement SORT optimization
authorTimo Sirainen <timo.sirainen@open-xchange.com>
Tue, 2 Dec 2025 16:47:43 +0000 (18:47 +0200)
committerTimo Sirainen <timo.sirainen@open-xchange.com>
Wed, 3 Dec 2025 21:13:13 +0000 (23:13 +0200)
src/lib-imap-client/imapc-settings.c
src/lib-imap-client/imapc-settings.h
src/lib-storage/index/imapc/imapc-mailbox.c
src/lib-storage/index/imapc/imapc-search.c
src/lib-storage/index/imapc/imapc-search.h

index efb57f3c7b9391d4904429dabc1b4dd589e68695..851b883f0d4393bfadd0585486258c60d2d9ff43 100644 (file)
@@ -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 },
index 0df5d9b2bae0b4a33f46ef152847eb60f43ac336..da4db8887cbaf99610610cfa4ae6fc7b7a3a059b 100644 (file)
@@ -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,
index 474d58e95eecd209a0ae002ba004e9cdd18ed31f..e44a493a9a34e65332ff5690d18e2d2ba53e5e89 100644 (file)
@@ -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",
index fc52a6035ec5f5f134979bf359f6762e055e5fd2..21b0e366bf048d63021ff0b4c55c91b093b025ad 100644 (file)
 #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);
+       }
+}
index 0966569ca55826221e10ef88aeb70ac479b5a80d..3bce160664c759657cfb51673dfa0932a56bb31b 100644 (file)
@@ -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