};
static struct sort_name sort_names[] = {
- { MAIL_SORT_ARRIVAL, "arrival" },
- { MAIL_SORT_CC, "cc" },
- { MAIL_SORT_DATE, "date" },
- { MAIL_SORT_FROM, "from" },
- { MAIL_SORT_SIZE, "size" },
- { MAIL_SORT_SUBJECT, "subject" },
- { MAIL_SORT_TO, "to" },
-
- { MAIL_SORT_END, NULL }
+ { MAIL_SORT_ARRIVAL, "arrival" },
+ { MAIL_SORT_CC, "cc" },
+ { MAIL_SORT_DATE, "date" },
+ { MAIL_SORT_FROM, "from" },
+ { MAIL_SORT_SIZE, "size" },
+ { MAIL_SORT_SUBJECT, "subject" },
+ { MAIL_SORT_TO, "to" },
+ { MAIL_SORT_SEARCH_SCORE, "x-score" },
+
+ { MAIL_SORT_END, NULL }
};
static int
case MAIL_FETCH_FROM_ENVELOPE:
case MAIL_FETCH_UIDL_FILE_NAME:
case MAIL_FETCH_UIDL_BACKEND:
+ case MAIL_FETCH_SEARCH_SCORE:
*value_r = "";
return 0;
case MAIL_FETCH_HEADER_MD5:
};
ARRAY_DEFINE_TYPE(mail_sort_node_size, struct mail_sort_node_size);
+struct mail_sort_node_float {
+ uint32_t seq;
+ float num;
+};
+ARRAY_DEFINE_TYPE(mail_sort_node_float, struct mail_sort_node_float);
+
struct sort_cmp_context {
struct mail_search_sort_program *program;
struct mail *mail;
node->size = 0;
}
+static float index_sort_get_score(struct mail *mail)
+{
+ const char *str;
+
+ if (mail_get_special(mail, MAIL_FETCH_SEARCH_SCORE, &str) < 0)
+ return 0;
+ else
+ return strtod(str, NULL);
+}
+
+static void
+index_sort_list_add_score(struct mail_search_sort_program *program,
+ struct mail *mail)
+{
+ ARRAY_TYPE(mail_sort_node_float) *nodes = program->context;
+ struct mail_sort_node_float *node;
+
+ node = array_append_space(nodes);
+ node->seq = mail->seq;
+ node->num = index_sort_get_score(mail);
+}
+
void index_sort_list_add(struct mail_search_sort_program *program,
struct mail *mail)
{
program->context = NULL;
}
+static int sort_node_float_cmp(const void *p1, const void *p2)
+{
+ struct sort_cmp_context *ctx = &static_node_cmp_context;
+ const struct mail_sort_node_float *n1 = p1, *n2 = p2;
+
+ if (n1->num < n2->num)
+ return !ctx->reverse ? -1 : 1;
+ if (n1->num > n2->num)
+ return !ctx->reverse ? 1 : -1;
+
+ return index_sort_node_cmp_type(ctx->mail,
+ ctx->program->sort_program + 1,
+ n1->seq, n2->seq);
+}
+
+static void
+index_sort_list_finish_float(struct mail_search_sort_program *program)
+{
+ ARRAY_TYPE(mail_sort_node_float) *nodes = program->context;
+ struct mail_sort_node_float *float_nodes;
+ unsigned int count;
+
+ float_nodes = array_get_modifiable(nodes, &count);
+ qsort(float_nodes, count, sizeof(struct mail_sort_node_float),
+ sort_node_float_cmp);
+ memcpy(&program->seqs, nodes, sizeof(program->seqs));
+ i_free(nodes);
+ program->context = NULL;
+}
+
void index_sort_list_finish(struct mail_search_sort_program *program)
{
memset(&static_node_cmp_context, 0, sizeof(static_node_cmp_context));
program->sort_list_finish = index_sort_list_finish_string;
index_sort_list_init_string(program);
break;
+ case MAIL_SORT_SEARCH_SCORE: {
+ ARRAY_TYPE(mail_sort_node_float) *nodes;
+
+ nodes = i_malloc(sizeof(*nodes));
+ i_array_init(nodes, 128);
+ program->sort_list_add = index_sort_list_add_score;
+ program->sort_list_finish = index_sort_list_finish_float;
+ program->context = nodes;
+ break;
+ }
default:
i_unreached();
}
enum mail_sort_type sort_type;
time_t time1, time2;
uoff_t size1, size2;
+ float float1, float2;
int ret = 0;
sort_type = *sort_program & MAIL_SORT_MASK;
ret = size1 < size2 ? -1 :
(size1 > size2 ? 1 : 0);
break;
+ case MAIL_SORT_SEARCH_SCORE:
+ mail_set_seq(mail, seq1);
+ float1 = index_sort_get_score(mail);
+ mail_set_seq(mail, seq2);
+ float2 = index_sort_get_score(mail);
+
+ ret = float1 < float2 ? -1 :
+ (float1 > float2 ? 1 : 0);
+ break;
case MAIL_SORT_END:
return seq1 < seq2 ? -1 :
(seq1 > seq2 ? 1 : 0);
enum mail_sort_type {
/* Maximum size for sort program (each one separately + END) */
-#define MAX_SORT_PROGRAM_SIZE (7 + 1)
+#define MAX_SORT_PROGRAM_SIZE (8 + 1)
MAIL_SORT_ARRIVAL = 0x0001,
MAIL_SORT_CC = 0x0002,
MAIL_SORT_SIZE = 0x0010,
MAIL_SORT_SUBJECT = 0x0020,
MAIL_SORT_TO = 0x0040,
+ MAIL_SORT_SEARCH_SCORE = 0x0080,
MAIL_SORT_MASK = 0x0fff,
MAIL_SORT_FLAG_REVERSE = 0x1000, /* reverse this mask type */
MAIL_FETCH_HEADER_MD5 = 0x00010000,
MAIL_FETCH_UIDL_FILE_NAME = 0x00020000,
MAIL_FETCH_UIDL_BACKEND = 0x00040000,
- MAIL_FETCH_MAILBOX_NAME = 0x00080000
+ MAIL_FETCH_MAILBOX_NAME = 0x00080000,
+ MAIL_FETCH_SEARCH_SCORE = 0x00100000
};
enum mailbox_transaction_flags {
solr_quote_str(str, backend->box->storage->user);
t_array_init(&uids, 1);
- if (solr_connection_select(solr_conn, str_c(str), &uids) < 0)
+ if (solr_connection_select(solr_conn, str_c(str), &uids, NULL) < 0)
return -1;
uidvals = array_get(&uids, &count);
static int fts_backend_solr_lookup(struct fts_backend_lookup_context *ctx,
ARRAY_TYPE(seq_range) *definite_uids,
- ARRAY_TYPE(seq_range) *maybe_uids)
+ ARRAY_TYPE(seq_range) *maybe_uids,
+ ARRAY_TYPE(fts_score_map) *scores)
{
struct mailbox *box = ctx->backend->box;
const struct fts_backend_lookup_field *fields;
mailbox_get_status(box, STATUS_UIDVALIDITY, &status);
str = t_str_new(256);
- str_printfa(str, "fl=uid&rows=%u&q=", status.uidnext);
+ str_printfa(str, "fl=uid,score&rows=%u&sort=uid%%20asc&q=",
+ status.uidnext);
/* build a lucene search query from the fields */
fields = array_get(&ctx->fields, &count);
solr_quote_str(str, box->storage->user);
array_clear(maybe_uids);
- return solr_connection_select(solr_conn, str_c(str), definite_uids);
+ return solr_connection_select(solr_conn, str_c(str),
+ definite_uids, scores);
}
struct fts_backend fts_backend_solr = {
/* curl: 7.16.0 curl_multi_timeout */
#include "lib.h"
+#include "array.h"
#include "str.h"
#include "strescape.h"
#include "solr-connection.h"
enum solr_xml_content_state content_state;
int depth;
+ uint32_t uid;
+ float score;
+
ARRAY_TYPE(seq_range) *uids;
+ ARRAY_TYPE(fts_score_map) *scores;
};
struct solr_connection_post {
ctx->state++;
break;
case SOLR_XML_RESPONSE_STATE_RESULT:
- if (strcmp(name, "doc") == 0)
+ if (strcmp(name, "doc") == 0) {
ctx->state++;
+ ctx->uid = 0;
+ ctx->score = 0;
+ }
break;
case SOLR_XML_RESPONSE_STATE_DOC:
name_attr = attrs_get_name(attrs);
}
}
+static void solr_lookup_add_doc(struct solr_lookup_xml_context *ctx)
+{
+ struct fts_score_map *score;
+
+ if (ctx->uid == 0) {
+ i_error("fts_solr: missing uid");
+ return;
+ }
+
+ seq_range_array_add(ctx->uids, 0, ctx->uid);
+ if (ctx->scores != NULL && ctx->score != 0) {
+ score = array_append_space(ctx->scores);
+ score->uid = ctx->uid;
+ score->score = ctx->score;
+ }
+}
+
static void solr_lookup_xml_end(void *context, const char *name ATTR_UNUSED)
{
struct solr_lookup_xml_context *ctx = context;
i_assert(ctx->depth >= (int)ctx->state);
if (ctx->depth == (int)ctx->state) {
+ if (ctx->state == SOLR_XML_RESPONSE_STATE_DOC)
+ solr_lookup_add_doc(ctx);
ctx->state--;
ctx->content_state = SOLR_XML_CONTENT_STATE_NONE;
}
i_error("fts_solr: received invalid uid");
break;
}
- seq_range_array_add(ctx->uids, 0, uid);
+ ctx->uid = uid;
break;
case SOLR_XML_CONTENT_STATE_SCORE:
- /* FIXME */
+ T_BEGIN {
+ ctx->score = strtod(t_strndup(str, len), NULL);
+ } T_END;
break;
}
}
int solr_connection_select(struct solr_connection *conn, const char *query,
- ARRAY_TYPE(seq_range) *uids)
+ ARRAY_TYPE(seq_range) *uids,
+ ARRAY_TYPE(fts_score_map) *scores)
{
struct solr_lookup_xml_context solr_lookup_context;
string_t *str;
memset(&solr_lookup_context, 0, sizeof(solr_lookup_context));
solr_lookup_context.uids = uids;
+ solr_lookup_context.scores = scores;
i_free_and_null(conn->http_failure);
conn->xml_failed = FALSE;
#define SOLR_CONNECTION_H
#include "seq-range-array.h"
+#include "fts-api.h"
struct solr_connection *solr_connection_init(const char *url, bool debug);
void solr_connection_deinit(struct solr_connection *conn);
const char *str);
int solr_connection_select(struct solr_connection *conn, const char *query,
- ARRAY_TYPE(seq_range) *uids);
+ ARRAY_TYPE(seq_range) *uids,
+ ARRAY_TYPE(fts_score_map) *scores);
int solr_connection_post(struct solr_connection *conn, const char *cmd);
struct solr_connection_post *
int (*lookup2)(struct fts_backend_lookup_context *ctx,
ARRAY_TYPE(seq_range) *definite_uids,
- ARRAY_TYPE(seq_range) *maybe_uids);
+ ARRAY_TYPE(seq_range) *maybe_uids,
+ ARRAY_TYPE(fts_score_map) *scores);
};
enum fts_backend_flags {
int fts_backend_lookup_deinit(struct fts_backend_lookup_context **_ctx,
ARRAY_TYPE(seq_range) *definite_uids,
- ARRAY_TYPE(seq_range) *maybe_uids)
+ ARRAY_TYPE(seq_range) *maybe_uids,
+ ARRAY_TYPE(fts_score_map) *scores)
{
struct fts_backend_lookup_context *ctx = *_ctx;
int ret;
*_ctx = NULL;
- if (ctx->backend->v.lookup2 != NULL)
- ret = ctx->backend->v.lookup2(ctx, definite_uids, maybe_uids);
- else
+ if (ctx->backend->v.lookup2 != NULL) {
+ ret = ctx->backend->v.lookup2(ctx, definite_uids, maybe_uids,
+ scores);
+ } else {
+ array_clear(scores);
ret = fts_backend_lookup_old(ctx, definite_uids, maybe_uids);
+ }
pool_unref(&ctx->pool);
return ret;
}
FTS_LOOKUP_FLAG_INVERT = 0x04
};
+struct fts_score_map {
+ uint32_t uid;
+ float score;
+};
+ARRAY_DEFINE_TYPE(fts_score_map, struct fts_score_map);
+
struct fts_backend *
fts_backend_init(const char *backend_name, struct mailbox *box);
void fts_backend_deinit(struct fts_backend **backend);
/* Finish the lookup and return found UIDs. */
int fts_backend_lookup_deinit(struct fts_backend_lookup_context **ctx,
ARRAY_TYPE(seq_range) *definite_uids,
- ARRAY_TYPE(seq_range) *maybe_uids);
+ ARRAY_TYPE(seq_range) *maybe_uids,
+ ARRAY_TYPE(fts_score_map) *scores);
#endif
i_array_init(&fctx->definite_seqs, 64);
i_array_init(&fctx->maybe_seqs, 64);
+ i_array_init(&fctx->score_map, 64);
/* start lookup with the best arg */
T_BEGIN {
have_seqs = TRUE;
fts_backend_lookup_deinit(&fctx->lookup_ctx_fast,
&fctx->definite_seqs,
- &fctx->maybe_seqs);
+ &fctx->maybe_seqs,
+ &fctx->score_map);
}
if (fctx->fbox->backend_fast->locked)
fts_backend_unlock(fctx->fbox->backend_fast);
} else if (!have_seqs) {
fts_backend_lookup_deinit(&fctx->lookup_ctx_substr,
&fctx->definite_seqs,
- &fctx->maybe_seqs);
+ &fctx->maybe_seqs,
+ &fctx->score_map);
} else {
/* have to merge the results */
ARRAY_TYPE(seq_range) tmp_def, tmp_maybe;
+ ARRAY_TYPE(fts_score_map) tmp_scores;
i_array_init(&tmp_def, 64);
i_array_init(&tmp_maybe, 64);
+ i_array_init(&tmp_scores, 64);
+ /* FIXME: for now we just ignore the other scores,
+ since squat doesn't support it anyway */
fts_backend_lookup_deinit(&fctx->lookup_ctx_substr,
- &tmp_def, &tmp_maybe);
+ &tmp_def, &tmp_maybe,
+ &tmp_scores);
fts_filter_uids(&fctx->definite_seqs, &tmp_def,
&fctx->maybe_seqs, &tmp_maybe);
array_free(&tmp_def);
array_free(&tmp_maybe);
+ array_free(&tmp_scores);
}
if (fctx->fbox->backend_substr->locked)
fts_backend_unlock(fctx->fbox->backend_substr);
#define FTS_SEARCH_NONBLOCK_COUNT 10
#define FTS_BUILD_NOTIFY_INTERVAL_SECS 10
+struct fts_mail {
+ union mail_module_context module_ctx;
+ char score[30];
+};
+
struct fts_storage_build_context {
struct mail_search_context *search_ctx;
struct mail_search_args *search_args;
union mailbox_transaction_module_context module_ctx;
struct fts_storage_build_context *build_ctx;
+ ARRAY_TYPE(fts_score_map) *score_map;
struct mail *mail;
uint32_t last_uid;
struct mail_search_args *args,
const enum mail_sort_type *sort_program)
{
+ struct fts_transaction_context *ft = FTS_CONTEXT(t);
struct fts_mailbox *fbox = FTS_CONTEXT(t->box);
struct mail_search_context *ctx;
struct fts_search_context *fctx;
if (fbox->backend_substr == NULL && fbox->backend_fast == NULL)
return ctx;
+ ft->score_map = &fctx->score_map;
+
fts_search_analyze(fctx);
(void)fts_try_build_init(fctx);
return ctx;
static int fts_mailbox_search_deinit(struct mail_search_context *ctx)
{
+ struct fts_transaction_context *ft = FTS_CONTEXT(ctx->transaction);
struct fts_mailbox *fbox = FTS_CONTEXT(ctx->transaction->box);
struct fts_search_context *fctx = FTS_CONTEXT(ctx);
+ if (ft->score_map == &fctx->score_map)
+ ft->score_map = NULL;
+
if (fctx->build_ctx != NULL) {
/* the search was cancelled */
fts_build_deinit(&fctx->build_ctx);
array_free(&fctx->definite_seqs);
if (array_is_created(&fctx->maybe_seqs))
array_free(&fctx->maybe_seqs);
+ if (array_is_created(&fctx->score_map))
+ array_free(&fctx->score_map);
i_free(fctx);
return fbox->module_ctx.super.search_deinit(ctx);
}
static void fts_mail_expunge(struct mail *_mail)
{
struct mail_private *mail = (struct mail_private *)_mail;
- union mail_module_context *fmail = FTS_MAIL_CONTEXT(mail);
+ struct fts_mail *fmail = FTS_MAIL_CONTEXT(mail);
struct fts_mailbox *fbox = FTS_CONTEXT(_mail->box);
struct fts_transaction_context *ft = FTS_CONTEXT(_mail->transaction);
if (fbox->backend_fast != NULL)
fts_backend_expunge(fbox->backend_fast, _mail);
- fmail->super.expunge(_mail);
+ fmail->module_ctx.super.expunge(_mail);
+}
+
+static int fts_score_cmp(const void *key, const void *data)
+{
+ const uint32_t *uid = key;
+ const struct fts_score_map *score = data;
+
+ return *uid < score->uid ? -1 :
+ (*uid > score->uid ? 1 : 0);
+}
+
+static int fts_mail_get_special(struct mail *_mail, enum mail_fetch_field field,
+ const char **value_r)
+{
+ struct mail_private *mail = (struct mail_private *)_mail;
+ struct fts_mail *fmail = FTS_MAIL_CONTEXT(mail);
+ struct fts_transaction_context *ft = FTS_CONTEXT(_mail->transaction);
+ const struct fts_score_map *scores;
+ unsigned int count;
+
+ if (field != MAIL_FETCH_SEARCH_SCORE || ft->score_map == NULL ||
+ !array_is_created(ft->score_map))
+ scores = NULL;
+ else {
+ scores = array_get(ft->score_map, &count);
+ scores = bsearch(&_mail->uid, scores, count, sizeof(*scores),
+ fts_score_cmp);
+ }
+ if (scores != NULL) {
+ i_assert(scores->uid == _mail->uid);
+ i_snprintf(fmail->score, sizeof(fmail->score),
+ "%f", scores->score);
+ *value_r = fmail->score;
+ return 0;
+ }
+
+ return fmail->module_ctx.super.get_special(_mail, field, value_r);
}
static struct mail *
struct mailbox_header_lookup_ctx *wanted_headers)
{
struct fts_mailbox *fbox = FTS_CONTEXT(t->box);
- union mail_module_context *fmail;
+ struct fts_mail *fmail;
struct mail *_mail;
struct mail_private *mail;
if (fbox->backend_substr != NULL || fbox->backend_fast != NULL) {
mail = (struct mail_private *)_mail;
- fmail = p_new(mail->pool, union mail_module_context, 1);
- fmail->super = mail->v;
+ fmail = p_new(mail->pool, struct fts_mail, 1);
+ fmail->module_ctx.super = mail->v;
mail->v.expunge = fts_mail_expunge;
- MODULE_CONTEXT_SET_SELF(mail, fts_mail_module, fmail);
+ mail->v.get_special = fts_mail_get_special;
+ MODULE_CONTEXT_SET(mail, fts_mail_module, fmail);
}
return _mail;
}
struct fts_backend_lookup_context *lookup_ctx_substr, *lookup_ctx_fast;
ARRAY_TYPE(seq_range) definite_seqs, maybe_seqs;
+ ARRAY_TYPE(fts_score_map) score_map;
unsigned int definite_idx, maybe_idx;
struct fts_backend *build_backend;