summaryrefslogtreecommitdiffstats
path: root/src/plugins/virtual/virtual-search.c
blob: ee04a7405338dfe41f2112c7164d164f6f0e647a (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
/* Copyright (c) 2008-2018 Dovecot authors, see the included COPYING file */

#include "lib.h"
#include "array.h"
#include "mail-search.h"
#include "index-search-private.h"
#include "virtual-storage.h"


enum virtual_search_state {
	VIRTUAL_SEARCH_STATE_BUILD,
	VIRTUAL_SEARCH_STATE_RETURN,
	VIRTUAL_SEARCH_STATE_SORT,
	VIRTUAL_SEARCH_STATE_SORT_DONE
};

struct virtual_search_record {
	uint32_t mailbox_id;
	uint32_t real_uid;
	uint32_t virtual_seq;
};

struct virtual_search_context {
	union mail_search_module_context module_ctx;

	ARRAY_TYPE(seq_range) result;
	struct seq_range_iter result_iter;
	ARRAY(struct virtual_search_record) records;

	enum virtual_search_state search_state;
	unsigned int next_result_n;
	unsigned int next_record_idx;
};

static int virtual_search_record_cmp(const struct virtual_search_record *r1,
				     const struct virtual_search_record *r2)
{
	if (r1->mailbox_id < r2->mailbox_id)
		return -1;
	if (r1->mailbox_id > r2->mailbox_id)
		return 1;

	if (r1->real_uid < r2->real_uid)
		return -1;
	if (r1->real_uid > r2->real_uid)
		return 1;
	return 0;
}

static int mail_search_get_result(struct mail_search_context *ctx)
{
	const struct mail_search_arg *arg;
	int ret = 1;

	for (arg = ctx->args->args; arg != NULL; arg = arg->next) {
		if (arg->result < 0)
			return -1;
		if (arg->result == 0)
			ret = 0;
	}
	return ret;
}

static void virtual_search_get_records(struct mail_search_context *ctx,
				       struct virtual_search_context *vctx)
{
	struct virtual_mailbox *mbox =
		(struct virtual_mailbox *)ctx->transaction->box;
	const struct virtual_mail_index_record *vrec;
	struct virtual_search_record srec;
	const void *data;
	int result;

	i_zero(&srec);
	while (index_storage_search_next_update_seq(ctx)) {
		result = mail_search_get_result(ctx);
		i_assert(result != 0);
		if (result > 0) {
			/* full match, no need to check this any further */
			seq_range_array_add(&vctx->result, ctx->seq);
		} else {
			/* possible match, save and check later */
			mail_index_lookup_ext(mbox->box.view, ctx->seq,
					      mbox->virtual_ext_id,
					      &data, NULL);
			vrec = data;

			srec.mailbox_id = vrec->mailbox_id;
			srec.real_uid = vrec->real_uid;
			srec.virtual_seq = ctx->seq;
			array_push_back(&vctx->records, &srec);
		}
		mail_search_args_reset(ctx->args->args, FALSE);
	}
	array_sort(&vctx->records, virtual_search_record_cmp);

	ctx->progress_max = array_count(&vctx->records);
}

struct mail_search_context *
virtual_search_init(struct mailbox_transaction_context *t,
		    struct mail_search_args *args,
		    const enum mail_sort_type *sort_program,
		    enum mail_fetch_field wanted_fields,
		    struct mailbox_header_lookup_ctx *wanted_headers)
{
	struct mail_search_context *ctx;
	struct virtual_search_context *vctx;

	ctx = index_storage_search_init(t, args, sort_program,
					wanted_fields, wanted_headers);

	vctx = i_new(struct virtual_search_context, 1);
	vctx->search_state = VIRTUAL_SEARCH_STATE_BUILD;
	i_array_init(&vctx->result, 64);
	i_array_init(&vctx->records, 64);
	MODULE_CONTEXT_SET(ctx, virtual_storage_module, vctx);

	virtual_search_get_records(ctx, vctx);
	seq_range_array_iter_init(&vctx->result_iter, &vctx->result);
	return ctx;
}

int virtual_search_deinit(struct mail_search_context *ctx)
{
	struct virtual_search_context *vctx = VIRTUAL_CONTEXT_REQUIRE(ctx);

	array_free(&vctx->result);
	array_free(&vctx->records);
	i_free(vctx);
	return index_storage_search_deinit(ctx);
}

bool virtual_search_next_nonblock(struct mail_search_context *ctx,
				  struct mail **mail_r, bool *tryagain_r)
{
	struct virtual_search_context *vctx = VIRTUAL_CONTEXT_REQUIRE(ctx);
	struct index_search_context *ictx = (struct index_search_context *)ctx;
	uint32_t seq;

	switch (vctx->search_state) {
	case VIRTUAL_SEARCH_STATE_BUILD:
		if (ctx->sort_program == NULL)
			vctx->search_state = VIRTUAL_SEARCH_STATE_SORT;
		else
			vctx->search_state = VIRTUAL_SEARCH_STATE_RETURN;
		return virtual_search_next_nonblock(ctx, mail_r, tryagain_r);
	case VIRTUAL_SEARCH_STATE_RETURN:
		return index_storage_search_next_nonblock(ctx, mail_r, tryagain_r);
	case VIRTUAL_SEARCH_STATE_SORT:
		/* the messages won't be returned sorted, so we'll have to
		   do it ourself */
		while (index_storage_search_next_nonblock(ctx, mail_r, tryagain_r))
			seq_range_array_add(&vctx->result, (*mail_r)->seq);
		if (*tryagain_r)
			return FALSE;

		vctx->next_result_n = 0;
		vctx->search_state = VIRTUAL_SEARCH_STATE_SORT_DONE;
		/* fall through */
	case VIRTUAL_SEARCH_STATE_SORT_DONE:
		*tryagain_r = FALSE;
		if (!seq_range_array_iter_nth(&vctx->result_iter,
					      vctx->next_result_n, &seq))
			return FALSE;
		vctx->next_result_n++;
		*mail_r = index_search_get_mail(ictx);
		i_assert(*mail_r != NULL);
		mail_set_seq(*mail_r, seq);
		return TRUE;
	}
	i_unreached();
}

static void search_args_set_full_match(struct mail_search_arg *args)
{
	for (; args != NULL; args = args->next)
		args->result = 1;
}

bool virtual_search_next_update_seq(struct mail_search_context *ctx)
{
	struct virtual_search_context *vctx = VIRTUAL_CONTEXT_REQUIRE(ctx);
	const struct virtual_search_record *recs;
	unsigned int count;

	recs = array_get(&vctx->records, &count);
	if (vctx->next_record_idx < count) {
		/* go through potential results first */
		ctx->seq = recs[vctx->next_record_idx++].virtual_seq - 1;
		if (!index_storage_search_next_update_seq(ctx))
			i_unreached();
		ctx->progress_cur = vctx->next_record_idx;
		return TRUE;
	}

	if (ctx->sort_program != NULL &&
	    seq_range_array_iter_nth(&vctx->result_iter,
				     vctx->next_result_n, &ctx->seq)) {
		/* this is known to match fully */
		search_args_set_full_match(ctx->args->args);
		vctx->next_result_n++;
		return TRUE;
	}
	return FALSE;
}