Mercurial > dovecot > core-2.2
changeset 9622:cae78e734cdb HEAD
Moved around mail-index-transaction code and added initial unit tests.
author | Timo Sirainen <tss@iki.fi> |
---|---|
date | Mon, 13 Jul 2009 19:54:28 -0400 |
parents | 8c17eb6c28d6 |
children | 3da42dafa798 |
files | src/lib-index/Makefile.am src/lib-index/mail-index-transaction-finish.c src/lib-index/mail-index-transaction-private.h src/lib-index/mail-index-transaction-update.c src/lib-index/mail-index-transaction.c src/lib-index/test-mail-index-transaction-update.c |
diffstat | 6 files changed, 1476 insertions(+), 1029 deletions(-) [+] |
line wrap: on
line diff
--- a/src/lib-index/Makefile.am Mon Jul 13 19:42:01 2009 -0400 +++ b/src/lib-index/Makefile.am Mon Jul 13 19:54:28 2009 -0400 @@ -25,6 +25,7 @@ mail-index-transaction-export.c \ mail-index-transaction-finish.c \ mail-index-transaction-sort-appends.c \ + mail-index-transaction-update.c \ mail-index-transaction-view.c \ mail-index-strmap.c \ mail-index-sync.c \ @@ -60,6 +61,7 @@ mailbox-list-index-private.h test_programs = \ + test-mail-index-transaction-update \ test-mail-transaction-log-append \ test-mail-transaction-log-view @@ -70,6 +72,10 @@ ../lib-test/libtest.la \ ../lib/liblib.la +test_mail_index_transaction_update_SOURCES = test-mail-index-transaction-update.c +test_mail_index_transaction_update_LDADD = mail-index-transaction-update.lo $(test_libs) +test_mail_index_transaction_update_DEPENDENCIES = mail-index-transaction-update.lo $(test_libs) + test_mail_transaction_log_append_SOURCES = test-mail-transaction-log-append.c test_mail_transaction_log_append_LDADD = mail-transaction-log-append.lo $(test_libs) test_mail_transaction_log_append_DEPENDENCIES = mail-transaction-log-append.lo $(test_libs)
--- a/src/lib-index/mail-index-transaction-finish.c Mon Jul 13 19:42:01 2009 -0400 +++ b/src/lib-index/mail-index-transaction-finish.c Mon Jul 13 19:54:28 2009 -0400 @@ -338,7 +338,10 @@ int mail_index_transaction_finish(struct mail_index_transaction *t) { - mail_index_transaction_sort_appends(t); + if (array_is_created(&t->appends)) { + mail_index_update_day_headers(t); + mail_index_transaction_sort_appends(t); + } mail_index_transaction_finish_flag_updates(t); if (array_is_created(&t->ext_reset_atomic) || t->max_modseq != 0) {
--- a/src/lib-index/mail-index-transaction-private.h Mon Jul 13 19:42:01 2009 -0400 +++ b/src/lib-index/mail-index-transaction-private.h Mon Jul 13 19:54:28 2009 -0400 @@ -101,10 +101,12 @@ void mail_index_transaction_ref(struct mail_index_transaction *t); void mail_index_transaction_unref(struct mail_index_transaction **t); +void mail_index_transaction_reset_v(struct mail_index_transaction *t); void mail_index_transaction_sort_appends(struct mail_index_transaction *t); uint32_t mail_index_transaction_get_next_uid(struct mail_index_transaction *t); void mail_index_transaction_set_log_updates(struct mail_index_transaction *t); +void mail_index_update_day_headers(struct mail_index_transaction *t); unsigned int mail_index_transaction_get_flag_update_pos(struct mail_index_transaction *t, @@ -115,5 +117,10 @@ int mail_index_transaction_finish(struct mail_index_transaction *t); void mail_index_transaction_export(struct mail_index_transaction *t, struct mail_transaction_log_append_ctx *append_ctx); +unsigned int +mail_index_transaction_get_flag_update_pos(struct mail_index_transaction *t, + unsigned int left_idx, + unsigned int right_idx, + uint32_t seq); #endif
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/mail-index-transaction-update.c Mon Jul 13 19:54:28 2009 -0400 @@ -0,0 +1,1013 @@ +/* Copyright (c) 2003-2009 Dovecot authors, see the included COPYING file */ + +/* Inside transaction we keep messages stored in sequences in uid fields. + Before they're written to transaction log the sequences are changed to + UIDs. */ + +#include "lib.h" +#include "ioloop.h" +#include "array.h" +#include "mail-index-private.h" +#include "mail-index-transaction-private.h" + +static bool +mail_index_transaction_has_ext_changes(struct mail_index_transaction *t); + +struct mail_index_record * +mail_index_transaction_lookup(struct mail_index_transaction *t, uint32_t seq) +{ + i_assert(seq >= t->first_new_seq && seq <= t->last_new_seq); + + return array_idx_modifiable(&t->appends, seq - t->first_new_seq); +} + +void mail_index_transaction_reset_v(struct mail_index_transaction *t) +{ + ARRAY_TYPE(seq_array) *recs; + struct mail_index_transaction_ext_hdr_update *ext_hdrs; + unsigned i, count; + + if (array_is_created(&t->ext_rec_updates)) { + recs = array_get_modifiable(&t->ext_rec_updates, &count); + for (i = 0; i < count; i++) { + if (array_is_created(&recs[i])) + array_free(&recs[i]); + } + array_free(&t->ext_rec_updates); + } + if (array_is_created(&t->ext_rec_atomics)) { + recs = array_get_modifiable(&t->ext_rec_atomics, &count); + for (i = 0; i < count; i++) { + if (array_is_created(&recs[i])) + array_free(&recs[i]); + } + array_free(&t->ext_rec_atomics); + } + if (array_is_created(&t->ext_hdr_updates)) { + ext_hdrs = array_get_modifiable(&t->ext_hdr_updates, &count); + for (i = 0; i < count; i++) { + i_free(ext_hdrs[i].data); + i_free(ext_hdrs[i].mask); + } + array_free(&t->ext_hdr_updates); + } + + if (array_is_created(&t->keyword_updates)) { + struct mail_index_transaction_keyword_update *u; + + u = array_get_modifiable(&t->keyword_updates, &count); + for (i = 0; i < count; i++) { + if (array_is_created(&u[i].add_seq)) + array_free(&u[i].add_seq); + if (array_is_created(&u[i].remove_seq)) + array_free(&u[i].remove_seq); + } + array_free(&t->keyword_updates); + } + if (array_is_created(&t->keyword_resets)) + array_free(&t->keyword_resets); + + if (array_is_created(&t->appends)) + array_free(&t->appends); + if (array_is_created(&t->expunges)) + array_free(&t->expunges); + if (array_is_created(&t->updates)) + array_free(&t->updates); + if (array_is_created(&t->ext_resizes)) + array_free(&t->ext_resizes); + if (array_is_created(&t->ext_resets)) + array_free(&t->ext_resets); + if (array_is_created(&t->ext_reset_ids)) + array_free(&t->ext_reset_ids); + if (array_is_created(&t->ext_reset_atomic)) + array_free(&t->ext_reset_atomic); + + t->first_new_seq = mail_index_view_get_messages_count(t->view)+1; + t->last_new_seq = 0; + t->last_update_idx = 0; + t->min_flagupdate_seq = 0; + t->max_flagupdate_seq = 0; + + memset(t->pre_hdr_mask, 0, sizeof(t->pre_hdr_mask)); + memset(t->post_hdr_mask, 0, sizeof(t->post_hdr_mask)); + + t->appends_nonsorted = FALSE; + t->drop_unnecessary_flag_updates = FALSE; + t->pre_hdr_changed = FALSE; + t->post_hdr_changed = FALSE; + t->reset = FALSE; + t->log_updates = FALSE; + t->log_ext_updates = FALSE; +} + +void mail_index_transaction_set_log_updates(struct mail_index_transaction *t) +{ + /* flag updates aren't included in log_updates */ + t->log_updates = array_is_created(&t->appends) || + array_is_created(&t->expunges) || + array_is_created(&t->keyword_resets) || + array_is_created(&t->keyword_updates) || + t->pre_hdr_changed || t->post_hdr_changed; +} + +void mail_index_update_day_headers(struct mail_index_transaction *t) +{ + struct mail_index_header hdr; + const struct mail_index_record *rec; + const int max_days = N_ELEMENTS(hdr.day_first_uid); + struct tm tm; + time_t stamp; + int i, days; + + hdr = *mail_index_get_header(t->view); + rec = array_idx(&t->appends, 0); + + /* get beginning of today */ + tm = *localtime(&ioloop_time); + tm.tm_hour = 0; + tm.tm_min = 0; + tm.tm_sec = 0; + stamp = mktime(&tm); + i_assert(stamp != (time_t)-1); + + if ((time_t)hdr.day_stamp >= stamp) + return; + + /* get number of days since last message */ + days = (stamp - hdr.day_stamp) / (3600*24); + if (days > max_days) + days = max_days; + + /* @UNSAFE: move days forward and fill the missing days with old + day_first_uid[0]. */ + memmove(hdr.day_first_uid + days, hdr.day_first_uid, max_days - days); + for (i = 1; i < days; i++) + hdr.day_first_uid[i] = hdr.day_first_uid[0]; + + hdr.day_stamp = stamp; + hdr.day_first_uid[0] = rec->uid; + + mail_index_update_header(t, + offsetof(struct mail_index_header, day_stamp), + &hdr.day_stamp, sizeof(hdr.day_stamp), FALSE); + mail_index_update_header(t, + offsetof(struct mail_index_header, day_first_uid), + hdr.day_first_uid, sizeof(hdr.day_first_uid), FALSE); +} + +void mail_index_append(struct mail_index_transaction *t, uint32_t uid, + uint32_t *seq_r) +{ + struct mail_index_record *rec; + + i_assert(!t->no_appends); + + t->log_updates = TRUE; + + if (!array_is_created(&t->appends)) + i_array_init(&t->appends, 32); + + /* sequence number is visible only inside given view, + so let it generate it */ + if (t->last_new_seq != 0) + *seq_r = ++t->last_new_seq; + else + *seq_r = t->last_new_seq = t->first_new_seq; + + rec = array_append_space(&t->appends); + if (uid != 0) { + rec->uid = uid; + if (!t->appends_nonsorted && + t->last_new_seq != t->first_new_seq) { + /* if previous record's UID is larger than this one, + we'll have to sort the appends later */ + rec = mail_index_transaction_lookup(t, *seq_r - 1); + if (rec->uid > uid) + t->appends_nonsorted = TRUE; + else if (rec->uid == uid) + i_panic("Duplicate UIDs added in transaction"); + } + if (t->highest_append_uid < uid) + t->highest_append_uid = uid; + } +} + +void mail_index_append_assign_uids(struct mail_index_transaction *t, + uint32_t first_uid, uint32_t *next_uid_r) +{ + struct mail_index_record *recs; + unsigned int i, count; + + if (!array_is_created(&t->appends)) + return; + + i_assert(first_uid > t->highest_append_uid); + + recs = array_get_modifiable(&t->appends, &count); + for (i = 0; i < count; i++) { + if (recs[i].uid == 0) + recs[i].uid = first_uid++; + } + + *next_uid_r = first_uid; +} + +static void +mail_index_expunge_last_append_ext(ARRAY_TYPE(seq_array_array) *ext_updates, + uint32_t seq) +{ + ARRAY_TYPE(seq_array) *seqs; + unsigned int i, count, idx; + + if (!array_is_created(ext_updates)) + return; + + seqs = array_get_modifiable(ext_updates, &count); + for (i = 0; i < count; i++) { + if (array_is_created(&seqs[i]) && + mail_index_seq_array_lookup(&seqs[i], seq, &idx)) + array_delete(&seqs[i], idx, 1); + } +} + +static void +mail_index_expunge_last_append(struct mail_index_transaction *t, uint32_t seq) +{ + struct mail_index_transaction_keyword_update *kw_updates; + unsigned int i, count; + + i_assert(seq == t->last_new_seq); + + /* remove extension updates */ + mail_index_expunge_last_append_ext(&t->ext_rec_updates, seq); + mail_index_expunge_last_append_ext(&t->ext_rec_atomics, seq); + t->log_ext_updates = mail_index_transaction_has_ext_changes(t); + + /* remove keywords */ + if (array_is_created(&t->keyword_resets)) + seq_range_array_remove(&t->keyword_resets, seq); + if (array_is_created(&t->keyword_updates)) { + kw_updates = array_get_modifiable(&t->keyword_updates, &count); + for (i = 0; i < count; i++) { + if (array_is_created(&kw_updates[i].add_seq)) { + seq_range_array_remove(&kw_updates[i].add_seq, + seq); + } + if (array_is_created(&kw_updates[i].remove_seq)) { + seq_range_array_remove( + &kw_updates[i].remove_seq, seq); + } + } + } + + /* and finally remove the append itself */ + array_delete(&t->appends, seq - t->first_new_seq, 1); + t->last_new_seq--; + if (t->first_new_seq > t->last_new_seq) { + t->last_new_seq = 0; + t->appends_nonsorted = FALSE; + array_free(&t->appends); + } + mail_index_transaction_set_log_updates(t); +} + +void mail_index_expunge(struct mail_index_transaction *t, uint32_t seq) +{ + i_assert(seq > 0); + if (seq >= t->first_new_seq) { + /* we can handle only the last append. otherwise we'd have to + renumber sequences and that gets tricky. for now this is + enough, since we typically want to expunge all the + appends. */ + mail_index_expunge_last_append(t, seq); + } else { + t->log_updates = TRUE; + + /* expunges is a sorted array of {seq1, seq2, ..}, .. */ + seq_range_array_add(&t->expunges, 128, seq); + } +} + +static void update_minmax_flagupdate_seq(struct mail_index_transaction *t, + uint32_t seq1, uint32_t seq2) +{ + if (t->min_flagupdate_seq == 0) { + t->min_flagupdate_seq = seq1; + t->max_flagupdate_seq = seq2; + } else { + if (t->min_flagupdate_seq > seq1) + t->min_flagupdate_seq = seq1; + if (t->max_flagupdate_seq < seq2) + t->max_flagupdate_seq = seq2; + } +} + +unsigned int +mail_index_transaction_get_flag_update_pos(struct mail_index_transaction *t, + unsigned int left_idx, + unsigned int right_idx, + uint32_t seq) +{ + const struct mail_transaction_flag_update *updates; + unsigned int idx, count; + + updates = array_get(&t->updates, &count); + i_assert(left_idx <= right_idx && right_idx <= count); + + /* find the first update with either overlapping range, + or the update which will come after our insert */ + idx = left_idx; + while (left_idx < right_idx) { + idx = (left_idx + right_idx) / 2; + + if (updates[idx].uid2 < seq) + left_idx = idx+1; + else if (updates[idx].uid1 > seq) + right_idx = idx; + else + break; + } + if (left_idx > idx) + idx++; + return idx; +} + +static void +mail_index_insert_flag_update(struct mail_index_transaction *t, + struct mail_transaction_flag_update u, + unsigned int idx) +{ + struct mail_transaction_flag_update *updates, tmp_update; + unsigned int count, first_idx, max; + + updates = array_get_modifiable(&t->updates, &count); + + /* overlapping ranges, split/merge them */ + i_assert(idx == 0 || updates[idx-1].uid2 < u.uid1); + i_assert(idx == count || updates[idx].uid2 >= u.uid1); + + /* first we'll just add the changes without trying to merge anything */ + first_idx = idx; + for (; idx < count && u.uid2 >= updates[idx].uid1; idx++) { + i_assert(u.uid1 <= updates[idx].uid2); + if (u.uid1 != updates[idx].uid1 && + (updates[idx].add_flags != u.add_flags || + updates[idx].remove_flags != u.remove_flags)) { + if (u.uid1 < updates[idx].uid1) { + /* insert new update */ + tmp_update = u; + tmp_update.uid2 = updates[idx].uid1 - 1; + } else { + /* split existing update from beginning */ + tmp_update = updates[idx]; + tmp_update.uid2 = u.uid1 - 1; + updates[idx].uid1 = u.uid1; + } + + i_assert(tmp_update.uid1 <= tmp_update.uid2); + i_assert(updates[idx].uid1 <= updates[idx].uid2); + + array_insert(&t->updates, idx, &tmp_update, 1); + updates = array_get_modifiable(&t->updates, &count); + idx++; + } else if (u.uid1 < updates[idx].uid1) { + updates[idx].uid1 = u.uid1; + } + + if (u.uid2 < updates[idx].uid2 && + (updates[idx].add_flags != u.add_flags || + updates[idx].remove_flags != u.remove_flags)) { + /* split existing update from end */ + tmp_update = updates[idx]; + tmp_update.uid2 = u.uid2; + updates[idx].uid1 = u.uid2 + 1; + + i_assert(tmp_update.uid1 <= tmp_update.uid2); + i_assert(updates[idx].uid1 <= updates[idx].uid2); + + array_insert(&t->updates, idx, &tmp_update, 1); + updates = array_get_modifiable(&t->updates, &count); + } + + updates[idx].add_flags = + (updates[idx].add_flags | u.add_flags) & + ~u.remove_flags; + updates[idx].remove_flags = + (updates[idx].remove_flags | u.remove_flags) & + ~u.add_flags; + u.uid1 = updates[idx].uid2 + 1; + + if (updates[idx].add_flags == 0 && + updates[idx].remove_flags == 0) { + /* we can remove this update completely */ + array_delete(&t->updates, idx, 1); + updates = array_get_modifiable(&t->updates, &count); + } + + if (u.uid1 > u.uid2) { + /* break here before idx++ so last_update_idx is set + correctly */ + break; + } + } + i_assert(idx <= count); + + if (u.uid1 <= u.uid2) { + i_assert(idx == 0 || updates[idx-1].uid2 < u.uid1); + i_assert(idx == count || updates[idx].uid1 > u.uid2); + array_insert(&t->updates, idx, &u, 1); + } + updates = array_get_modifiable(&t->updates, &count); + t->last_update_idx = idx == count ? count-1 : idx; + + /* merge everything */ + idx = first_idx == 0 ? 0 : first_idx - 1; + max = I_MIN(t->last_update_idx + 1, count); + for (; idx < max; ) { + if (updates[idx].uid2 + 1 == updates[idx+1].uid1 && + updates[idx].add_flags == updates[idx+1].add_flags && + updates[idx].remove_flags == updates[idx+1].remove_flags) { + /* merge */ + updates[idx].uid2 = updates[idx+1].uid2; + array_delete(&t->updates, idx + 1, 1); + max--; + if (t->last_update_idx > idx) + t->last_update_idx--; + updates = array_get_modifiable(&t->updates, &count); + } else { + idx++; + } + } +} + +static void mail_index_record_modify_flags(struct mail_index_record *rec, + enum modify_type modify_type, + enum mail_flags flags) +{ + switch (modify_type) { + case MODIFY_REPLACE: + rec->flags = flags; + break; + case MODIFY_ADD: + rec->flags |= flags; + break; + case MODIFY_REMOVE: + rec->flags &= ~flags; + break; + } +} + +void mail_index_update_flags_range(struct mail_index_transaction *t, + uint32_t seq1, uint32_t seq2, + enum modify_type modify_type, + enum mail_flags flags) +{ + struct mail_index_record *rec; + struct mail_transaction_flag_update u, *last_update; + unsigned int idx, first_idx, count; + + update_minmax_flagupdate_seq(t, seq1, seq2); + if (seq2 >= t->first_new_seq) { + /* updates for appended messages, modify them directly */ + uint32_t seq; + + for (seq = I_MAX(t->first_new_seq, seq1); seq <= seq2; seq++) { + rec = mail_index_transaction_lookup(t, seq); + mail_index_record_modify_flags(rec, modify_type, flags); + } + if (seq1 >= t->first_new_seq) + return; + + /* range contains also existing messages. update them next. */ + seq2 = t->first_new_seq - 1; + } + + i_assert(seq1 <= seq2 && seq1 > 0); + i_assert(seq2 <= mail_index_view_get_messages_count(t->view)); + + if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_AVOID_FLAG_UPDATES) != 0) + t->drop_unnecessary_flag_updates = TRUE; + + memset(&u, 0, sizeof(u)); + u.uid1 = seq1; + u.uid2 = seq2; + + switch (modify_type) { + case MODIFY_REPLACE: + u.add_flags = flags; + u.remove_flags = ~flags & MAIL_INDEX_FLAGS_MASK; + break; + case MODIFY_ADD: + if (flags == 0) + return; + u.add_flags = flags; + break; + case MODIFY_REMOVE: + if (flags == 0) + return; + u.remove_flags = flags; + break; + } + + if (!array_is_created(&t->updates)) { + i_array_init(&t->updates, 256); + array_append(&t->updates, &u, 1); + return; + } + + last_update = array_get_modifiable(&t->updates, &count); + if (t->last_update_idx < count) { + /* fast path - hopefully we're updating the next message, + or a message that is to be appended as last update */ + last_update += t->last_update_idx; + if (seq1 - 1 == last_update->uid2) { + if (u.add_flags == last_update->add_flags && + u.remove_flags == last_update->remove_flags && + (t->last_update_idx + 1 == count || + last_update[1].uid1 > seq2)) { + /* we can just update the UID range */ + last_update->uid2 = seq2; + return; + } + } else if (seq1 > last_update->uid2) { + /* hopefully we can just append it */ + t->last_update_idx++; + last_update++; + } + } + + if (t->last_update_idx == count) + array_append(&t->updates, &u, 1); + else { + i_assert(t->last_update_idx < count); + + /* slow path */ + if (seq1 > last_update->uid2) { + /* added after this */ + first_idx = t->last_update_idx + 1; + } else { + /* added before this or on top of this */ + first_idx = 0; + count = t->last_update_idx + 1; + } + idx = mail_index_transaction_get_flag_update_pos(t, first_idx, + count, u.uid1); + mail_index_insert_flag_update(t, u, idx); + } +} + +void mail_index_update_flags(struct mail_index_transaction *t, uint32_t seq, + enum modify_type modify_type, + enum mail_flags flags) +{ + mail_index_update_flags_range(t, seq, seq, modify_type, flags); +} + +void mail_index_update_header(struct mail_index_transaction *t, + size_t offset, const void *data, size_t size, + bool prepend) +{ + i_assert(offset < sizeof(t->pre_hdr_change)); + i_assert(size <= sizeof(t->pre_hdr_change) - offset); + + t->log_updates = TRUE; + + if (prepend) { + t->pre_hdr_changed = TRUE; + memcpy(t->pre_hdr_change + offset, data, size); + for (; size > 0; size--) + t->pre_hdr_mask[offset++] = 1; + } else { + t->post_hdr_changed = TRUE; + memcpy(t->post_hdr_change + offset, data, size); + for (; size > 0; size--) + t->post_hdr_mask[offset++] = 1; + } +} + +void mail_index_ext_resize(struct mail_index_transaction *t, uint32_t ext_id, + uint32_t hdr_size, uint16_t record_size, + uint16_t record_align) +{ + struct mail_transaction_ext_intro intro; + uint32_t old_record_size, old_record_align; + + memset(&intro, 0, sizeof(intro)); + + /* get ext_id from transaction's map if it's there */ + if (!mail_index_map_get_ext_idx(t->view->map, ext_id, &intro.ext_id)) { + /* have to create it */ + const struct mail_index_registered_ext *rext; + + intro.ext_id = (uint32_t)-1; + rext = array_idx(&t->view->index->extensions, ext_id); + old_record_size = rext->record_size; + old_record_align = rext->record_align; + } else { + const struct mail_index_ext *ext; + + ext = array_idx(&t->view->map->extensions, intro.ext_id); + old_record_size = ext->record_size; + old_record_align = ext->record_align; + } + + /* allow only header size changes if extension records have already + been changed in transaction */ + i_assert(!array_is_created(&t->ext_rec_updates) || + (old_record_size == record_size && + old_record_align == record_align)); + + t->log_ext_updates = TRUE; + + if (!array_is_created(&t->ext_resizes)) + i_array_init(&t->ext_resizes, ext_id + 2); + + intro.hdr_size = hdr_size; + intro.record_size = record_size; + intro.record_align = record_align; + intro.name_size = 1; + array_idx_set(&t->ext_resizes, ext_id, &intro); +} + +void mail_index_ext_reset(struct mail_index_transaction *t, uint32_t ext_id, + uint32_t reset_id, bool clear_data) +{ + struct mail_transaction_ext_reset reset; + + i_assert(reset_id != 0); + + memset(&reset, 0, sizeof(reset)); + reset.new_reset_id = reset_id; + reset.preserve_data = !clear_data; + + mail_index_ext_set_reset_id(t, ext_id, reset_id); + + if (!array_is_created(&t->ext_resets)) + i_array_init(&t->ext_resets, ext_id + 2); + array_idx_set(&t->ext_resets, ext_id, &reset); + t->log_ext_updates = TRUE; +} + +void mail_index_ext_reset_inc(struct mail_index_transaction *t, uint32_t ext_id, + uint32_t prev_reset_id, bool clear_data) +{ + uint32_t expected_reset_id = prev_reset_id + 1; + + mail_index_ext_reset(t, ext_id, (uint32_t)-1, clear_data); + + if (!array_is_created(&t->ext_reset_atomic)) + i_array_init(&t->ext_reset_atomic, ext_id + 2); + array_idx_set(&t->ext_reset_atomic, ext_id, &expected_reset_id); +} + +static bool +mail_index_transaction_has_ext_updates(const ARRAY_TYPE(seq_array_array) *arr) +{ + const ARRAY_TYPE(seq_array) *array; + unsigned int i, count; + + if (array_is_created(arr)) { + array = array_get(arr, &count); + for (i = 0; i < count; i++) { + if (array_is_created(&array[i])) + return TRUE; + } + } + return FALSE; +} + +static bool +mail_index_transaction_has_ext_changes(struct mail_index_transaction *t) +{ + unsigned int i, count; + + if (mail_index_transaction_has_ext_updates(&t->ext_rec_updates)) + return TRUE; + if (mail_index_transaction_has_ext_updates(&t->ext_rec_atomics)) + return TRUE; + + if (array_is_created(&t->ext_hdr_updates)) { + const struct mail_index_transaction_ext_hdr_update *hdr; + + hdr = array_get(&t->ext_hdr_updates, &count); + for (i = 0; i < count; i++) { + if (hdr[i].alloc_size > 0) + return TRUE; + } + } + if (array_is_created(&t->ext_resets)) { + const struct mail_transaction_ext_reset *resets; + + resets = array_get(&t->ext_resets, &count); + for (i = 0; i < count; i++) { + if (resets[i].new_reset_id != 0) + return TRUE; + } + } + if (array_is_created(&t->ext_resizes)) { + const struct mail_transaction_ext_intro *resizes; + + resizes = array_get(&t->ext_resizes, &count); + for (i = 0; i < count; i++) { + if (resizes[i].name_size > 0) + return TRUE; + } + } + return FALSE; +} + +static void +mail_index_ext_update_reset(ARRAY_TYPE(seq_array_array) *arr, uint32_t ext_id) +{ + if (array_is_created(arr) && ext_id < array_count(arr)) { + /* if extension records have been updated, clear them */ + ARRAY_TYPE(seq_array) *array; + + array = array_idx_modifiable(arr, ext_id); + if (array_is_created(array)) + array_clear(array); + } +} + +void mail_index_ext_set_reset_id(struct mail_index_transaction *t, + uint32_t ext_id, uint32_t reset_id) +{ + mail_index_ext_update_reset(&t->ext_rec_updates, ext_id); + mail_index_ext_update_reset(&t->ext_rec_atomics, ext_id); + if (array_is_created(&t->ext_hdr_updates) && + ext_id < array_count(&t->ext_hdr_updates)) { + /* if extension headers have been updated, clear them */ + struct mail_index_transaction_ext_hdr_update *hdr; + + hdr = array_idx_modifiable(&t->ext_hdr_updates, ext_id); + if (hdr->alloc_size > 0) { + i_free_and_null(hdr->mask); + i_free_and_null(hdr->data); + } + hdr->alloc_size = 0; + } + if (array_is_created(&t->ext_resets) && + ext_id < array_count(&t->ext_resets)) { + /* clear resets */ + array_idx_clear(&t->ext_resets, ext_id); + } + if (array_is_created(&t->ext_resizes) && + ext_id < array_count(&t->ext_resizes)) { + /* clear resizes */ + array_idx_clear(&t->ext_resizes, ext_id); + } + + if (!array_is_created(&t->ext_reset_ids)) + i_array_init(&t->ext_reset_ids, ext_id + 2); + array_idx_set(&t->ext_reset_ids, ext_id, &reset_id); + + t->log_ext_updates = mail_index_transaction_has_ext_changes(t); +} + +void mail_index_update_header_ext(struct mail_index_transaction *t, + uint32_t ext_id, size_t offset, + const void *data, size_t size) +{ + struct mail_index_transaction_ext_hdr_update *hdr; + size_t new_size; + + i_assert(offset <= (uint16_t)-1 && size <= (uint16_t)-1 && + offset + size <= (uint16_t)-1); + + if (!array_is_created(&t->ext_hdr_updates)) + i_array_init(&t->ext_hdr_updates, ext_id + 2); + + hdr = array_idx_modifiable(&t->ext_hdr_updates, ext_id); + if (hdr->alloc_size < offset || hdr->alloc_size - offset < size) { + i_assert(size < (size_t)-1 - offset); + new_size = nearest_power(offset + size); + hdr->mask = i_realloc(hdr->mask, hdr->alloc_size, new_size); + hdr->data = i_realloc(hdr->data, hdr->alloc_size, new_size); + hdr->alloc_size = new_size; + } + memset(hdr->mask + offset, 1, size); + memcpy(hdr->data + offset, data, size); + + t->log_ext_updates = TRUE; +} + +void mail_index_update_ext(struct mail_index_transaction *t, uint32_t seq, + uint32_t ext_id, const void *data, void *old_data_r) +{ + struct mail_index *index = t->view->index; + const struct mail_index_registered_ext *rext; + const struct mail_transaction_ext_intro *intro; + uint16_t record_size; + ARRAY_TYPE(seq_array) *array; + unsigned int count; + + i_assert(seq > 0 && + (seq <= mail_index_view_get_messages_count(t->view) || + seq <= t->last_new_seq)); + i_assert(ext_id < array_count(&index->extensions)); + + t->log_ext_updates = TRUE; + + if (!array_is_created(&t->ext_resizes)) { + intro = NULL; + count = 0; + } else { + intro = array_get(&t->ext_resizes, &count); + } + if (ext_id < count && intro[ext_id].name_size != 0) { + /* resized record */ + record_size = intro[ext_id].record_size; + } else { + rext = array_idx(&index->extensions, ext_id); + record_size = rext->record_size; + } + + if (!array_is_created(&t->ext_rec_updates)) + i_array_init(&t->ext_rec_updates, ext_id + 2); + array = array_idx_modifiable(&t->ext_rec_updates, ext_id); + + /* @UNSAFE */ + if (!mail_index_seq_array_add(array, seq, data, record_size, + old_data_r)) { + /* not found, clear old_data if it was given */ + if (old_data_r != NULL) + memset(old_data_r, 0, record_size); + } +} + +int mail_index_atomic_inc_ext(struct mail_index_transaction *t, + uint32_t seq, uint32_t ext_id, int diff) +{ + ARRAY_TYPE(seq_array) *array; + int32_t old_diff32, diff32 = diff; + + i_assert(seq > 0 && + (seq <= mail_index_view_get_messages_count(t->view) || + seq <= t->last_new_seq)); + i_assert(ext_id < array_count(&t->view->index->extensions)); + /* currently non-external transactions can be applied multiple times, + causing multiple increments. */ + //FIXME:i_assert((t->flags & MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL) != 0); + + t->log_ext_updates = TRUE; + if (!array_is_created(&t->ext_rec_atomics)) + i_array_init(&t->ext_rec_atomics, ext_id + 2); + array = array_idx_modifiable(&t->ext_rec_atomics, ext_id); + if (mail_index_seq_array_add(array, seq, &diff32, sizeof(diff32), + &old_diff32)) { + /* already incremented this sequence in this transaction */ + diff32 += old_diff32; + mail_index_seq_array_add(array, seq, &diff32, sizeof(diff32), + NULL); + } + return diff32; +} + +static bool +keyword_update_has_changes(struct mail_index_transaction *t, uint32_t seq, + enum modify_type modify_type, + struct mail_keywords *keywords) +{ + struct mail_index_transaction_keyword_update *u; + ARRAY_TYPE(keyword_indexes) existing; + const unsigned int *existing_idx; + unsigned int i, j, existing_count; + bool found; + + t_array_init(&existing, 32); + if (seq < t->first_new_seq) + mail_index_lookup_keywords(t->view, seq, &existing); + existing_idx = array_get(&existing, &existing_count); + + if (modify_type == MODIFY_REPLACE && existing_count != keywords->count) + return TRUE; + + for (i = 0; i < keywords->count; i++) { + u = array_idx_modifiable(&t->keyword_updates, + keywords->idx[i]); + if (array_is_created(&u->add_seq) || + array_is_created(&u->remove_seq)) + return TRUE; + + found = FALSE; + for (j = 0; j < existing_count; j++) { + if (existing_idx[j] == keywords->idx[i]) { + found = TRUE; + break; + } + } + switch (modify_type) { + case MODIFY_ADD: + case MODIFY_REPLACE: + if (!found) + return TRUE; + break; + case MODIFY_REMOVE: + if (found) + return TRUE; + break; + } + } + return FALSE; +} + +void mail_index_update_keywords(struct mail_index_transaction *t, uint32_t seq, + enum modify_type modify_type, + struct mail_keywords *keywords) +{ + struct mail_index_transaction_keyword_update *u; + unsigned int i, ku_count; + bool changed; + + i_assert(seq > 0 && + (seq <= mail_index_view_get_messages_count(t->view) || + seq <= t->last_new_seq)); + i_assert(keywords->count > 0 || modify_type == MODIFY_REPLACE); + i_assert(keywords->index == t->view->index); + + update_minmax_flagupdate_seq(t, seq, seq); + + if (!array_is_created(&t->keyword_updates) && keywords->count > 0) { + uint32_t max_idx = keywords->idx[keywords->count-1]; + + i_array_init(&t->keyword_updates, max_idx + 1); + } + + if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_AVOID_FLAG_UPDATES) != 0) { + T_BEGIN { + changed = keyword_update_has_changes(t, seq, + modify_type, + keywords); + } T_END; + if (!changed) + return; + } + + /* Update add_seq and remove_seq arrays which describe the keyword + changes. Don't bother updating remove_seq or keyword resets for + newly added messages since they default to not having any + keywords anyway. */ + switch (modify_type) { + case MODIFY_ADD: + for (i = 0; i < keywords->count; i++) { + u = array_idx_modifiable(&t->keyword_updates, + keywords->idx[i]); + seq_range_array_add(&u->add_seq, 16, seq); + seq_range_array_remove(&u->remove_seq, seq); + } + break; + case MODIFY_REMOVE: + for (i = 0; i < keywords->count; i++) { + u = array_idx_modifiable(&t->keyword_updates, + keywords->idx[i]); + seq_range_array_remove(&u->add_seq, seq); + if (seq < t->first_new_seq) + seq_range_array_add(&u->remove_seq, 16, seq); + } + break; + case MODIFY_REPLACE: + /* Remove sequence from all add/remove arrays */ + if (array_is_created(&t->keyword_updates)) { + u = array_get_modifiable(&t->keyword_updates, + &ku_count); + for (i = 0; i < ku_count; i++) { + seq_range_array_remove(&u[i].add_seq, seq); + seq_range_array_remove(&u[i].remove_seq, seq); + } + } + /* Add the wanted keyword back */ + for (i = 0; i < keywords->count; i++) { + u = array_idx_modifiable(&t->keyword_updates, + keywords->idx[i]); + seq_range_array_add(&u->add_seq, 16, seq); + } + + if (seq < t->first_new_seq) + seq_range_array_add(&t->keyword_resets, 16, seq); + break; + } + + t->log_updates = TRUE; +} + +void mail_index_transaction_reset(struct mail_index_transaction *t) +{ + t->v.reset(t); +} + +void mail_index_reset(struct mail_index_transaction *t) +{ + mail_index_transaction_reset(t); + + t->reset = TRUE; +} + +void mail_index_transaction_set_max_modseq(struct mail_index_transaction *t, + uint64_t max_modseq, + ARRAY_TYPE(seq_range) *seqs) +{ + i_assert(array_is_created(seqs)); + + t->max_modseq = max_modseq; + t->conflict_seqs = seqs; +}
--- a/src/lib-index/mail-index-transaction.c Mon Jul 13 19:42:01 2009 -0400 +++ b/src/lib-index/mail-index-transaction.c Mon Jul 13 19:54:28 2009 -0400 @@ -1,9 +1,5 @@ /* Copyright (c) 2003-2009 Dovecot authors, see the included COPYING file */ -/* Inside transaction we keep messages stored in sequences in uid fields. - Before they're written to transaction log the sequences are changed to - UIDs. This is because we're able to compress sequence ranges better. */ - #include "lib.h" #include "ioloop.h" #include "array.h" @@ -12,110 +8,11 @@ #include "mail-transaction-log-private.h" #include "mail-index-transaction-private.h" +#include <stdlib.h> + void (*hook_mail_index_transaction_created) (struct mail_index_transaction *t) = NULL; -static bool -mail_index_transaction_has_ext_changes(struct mail_index_transaction *t); - -static void mail_index_transaction_reset_v(struct mail_index_transaction *t) -{ - ARRAY_TYPE(seq_array) *recs; - struct mail_index_transaction_ext_hdr_update *ext_hdrs; - unsigned i, count; - - if (array_is_created(&t->ext_rec_updates)) { - recs = array_get_modifiable(&t->ext_rec_updates, &count); - for (i = 0; i < count; i++) { - if (array_is_created(&recs[i])) - array_free(&recs[i]); - } - array_free(&t->ext_rec_updates); - } - if (array_is_created(&t->ext_rec_atomics)) { - recs = array_get_modifiable(&t->ext_rec_atomics, &count); - for (i = 0; i < count; i++) { - if (array_is_created(&recs[i])) - array_free(&recs[i]); - } - array_free(&t->ext_rec_atomics); - } - if (array_is_created(&t->ext_hdr_updates)) { - ext_hdrs = array_get_modifiable(&t->ext_hdr_updates, &count); - for (i = 0; i < count; i++) { - i_free(ext_hdrs[i].data); - i_free(ext_hdrs[i].mask); - } - array_free(&t->ext_hdr_updates); - } - - if (array_is_created(&t->keyword_updates)) { - struct mail_index_transaction_keyword_update *u; - - u = array_get_modifiable(&t->keyword_updates, &count); - for (i = 0; i < count; i++) { - if (array_is_created(&u[i].add_seq)) - array_free(&u[i].add_seq); - if (array_is_created(&u[i].remove_seq)) - array_free(&u[i].remove_seq); - } - array_free(&t->keyword_updates); - } - if (array_is_created(&t->keyword_resets)) - array_free(&t->keyword_resets); - - if (array_is_created(&t->appends)) - array_free(&t->appends); - if (array_is_created(&t->expunges)) - array_free(&t->expunges); - if (array_is_created(&t->updates)) - array_free(&t->updates); - if (array_is_created(&t->ext_resizes)) - array_free(&t->ext_resizes); - if (array_is_created(&t->ext_resets)) - array_free(&t->ext_resets); - if (array_is_created(&t->ext_reset_ids)) - array_free(&t->ext_reset_ids); - if (array_is_created(&t->ext_reset_atomic)) - array_free(&t->ext_reset_atomic); - - t->first_new_seq = mail_index_view_get_messages_count(t->view)+1; - t->last_new_seq = 0; - t->last_update_idx = 0; - t->min_flagupdate_seq = 0; - t->max_flagupdate_seq = 0; - - memset(t->pre_hdr_mask, 0, sizeof(t->pre_hdr_mask)); - memset(t->post_hdr_mask, 0, sizeof(t->post_hdr_mask)); - - t->appends_nonsorted = FALSE; - t->pre_hdr_changed = FALSE; - t->post_hdr_changed = FALSE; - t->reset = FALSE; - t->log_updates = FALSE; - t->log_ext_updates = FALSE; -} - -void mail_index_transaction_set_log_updates(struct mail_index_transaction *t) -{ - /* flag updates aren't included in log_updates */ - t->log_updates = array_is_created(&t->appends) || - array_is_created(&t->expunges) || - array_is_created(&t->keyword_resets) || - array_is_created(&t->keyword_updates) || - t->pre_hdr_changed || t->post_hdr_changed; -} - -static void mail_index_transaction_free(struct mail_index_transaction *t) -{ - mail_index_transaction_reset(t); - - array_free(&t->module_contexts); - mail_index_view_transaction_unref(t->view); - mail_index_view_close(&t->view); - i_free(t); -} - struct mail_index_view * mail_index_transaction_get_view(struct mail_index_transaction *t) { @@ -139,8 +36,15 @@ struct mail_index_transaction *t = *_t; *_t = NULL; - if (--t->refcount == 0) - mail_index_transaction_free(t); + if (--t->refcount > 0) + return; + + mail_index_transaction_reset(t); + + array_free(&t->module_contexts); + mail_index_view_transaction_unref(t->view); + mail_index_view_close(&t->view); + i_free(t); } uint32_t mail_index_transaction_get_next_uid(struct mail_index_transaction *t) @@ -174,11 +78,6 @@ return next_uid; } -void mail_index_transaction_reset(struct mail_index_transaction *t) -{ - t->v.reset(t); -} - static int mail_transaction_log_file_refresh(struct mail_index_transaction *t, struct mail_transaction_log_append_ctx *ctx) @@ -243,52 +142,6 @@ return 0; } -static void -mail_index_update_day_headers(struct mail_index_transaction *t) -{ - struct mail_index_header hdr; - const struct mail_index_record *rec; - const int max_days = N_ELEMENTS(hdr.day_first_uid); - struct tm tm; - time_t stamp; - int i, days; - - hdr = *mail_index_get_header(t->view); - rec = array_idx(&t->appends, 0); - - /* get beginning of today */ - tm = *localtime(&ioloop_time); - tm.tm_hour = 0; - tm.tm_min = 0; - tm.tm_sec = 0; - stamp = mktime(&tm); - i_assert(stamp != (time_t)-1); - - if ((time_t)hdr.day_stamp >= stamp) - return; - - /* get number of days since last message */ - days = (stamp - hdr.day_stamp) / (3600*24); - if (days > max_days) - days = max_days; - - /* @UNSAFE: move days forward and fill the missing days with old - day_first_uid[0]. */ - memmove(hdr.day_first_uid + days, hdr.day_first_uid, max_days - days); - for (i = 1; i < days; i++) - hdr.day_first_uid[i] = hdr.day_first_uid[0]; - - hdr.day_stamp = stamp; - hdr.day_first_uid[0] = rec->uid; - - mail_index_update_header(t, - offsetof(struct mail_index_header, day_stamp), - &hdr.day_stamp, sizeof(hdr.day_stamp), FALSE); - mail_index_update_header(t, - offsetof(struct mail_index_header, day_first_uid), - hdr.day_first_uid, sizeof(hdr.day_first_uid), FALSE); -} - static int mail_index_transaction_commit_v(struct mail_index_transaction *t, uint32_t *log_file_seq_r, uoff_t *log_file_offset_r) @@ -299,9 +152,6 @@ i_assert(t->first_new_seq > mail_index_view_get_messages_count(t->view)); - if (array_is_created(&t->appends)) - mail_index_update_day_headers(t); - if (!MAIL_INDEX_TRANSACTION_HAS_CHANGES(t) && !t->reset) { /* nothing to append */ ret = 0; @@ -364,873 +214,6 @@ t->v.rollback(t); } -struct mail_index_record * -mail_index_transaction_lookup(struct mail_index_transaction *t, uint32_t seq) -{ - i_assert(seq >= t->first_new_seq && seq <= t->last_new_seq); - - return array_idx_modifiable(&t->appends, seq - t->first_new_seq); -} - -void mail_index_append(struct mail_index_transaction *t, uint32_t uid, - uint32_t *seq_r) -{ - struct mail_index_record *rec; - - i_assert(!t->no_appends); - - t->log_updates = TRUE; - - if (!array_is_created(&t->appends)) - i_array_init(&t->appends, 32); - - /* sequence number is visible only inside given view, - so let it generate it */ - if (t->last_new_seq != 0) - *seq_r = ++t->last_new_seq; - else - *seq_r = t->last_new_seq = t->first_new_seq; - - rec = array_append_space(&t->appends); - if (uid != 0) { - rec->uid = uid; - if (!t->appends_nonsorted && - t->last_new_seq != t->first_new_seq) { - /* if previous record's UID is larger than this one, - we'll have to sort the appends later */ - rec = mail_index_transaction_lookup(t, *seq_r - 1); - if (rec->uid > uid) - t->appends_nonsorted = TRUE; - else if (rec->uid == uid) - i_panic("Duplicate UIDs added in transaction"); - } - if (t->highest_append_uid < uid) - t->highest_append_uid = uid; - } -} - -void mail_index_append_assign_uids(struct mail_index_transaction *t, - uint32_t first_uid, uint32_t *next_uid_r) -{ - struct mail_index_record *recs; - unsigned int i, count; - - i_assert(first_uid != 0); - - if (!array_is_created(&t->appends)) - return; - - recs = array_get_modifiable(&t->appends, &count); - - /* find the first mail with uid = 0 */ - for (i = 0; i < count; i++) { - if (recs[i].uid == 0) - break; - } - - for (; i < count; i++) { - i_assert(recs[i].uid == 0); - recs[i].uid = first_uid++; - } - - *next_uid_r = first_uid; -} - -static void -mail_index_expunge_last_append_ext(ARRAY_TYPE(seq_array_array) *ext_updates, - uint32_t seq) -{ - ARRAY_TYPE(seq_array) *seqs; - unsigned int i, count, idx; - - if (!array_is_created(ext_updates)) - return; - - seqs = array_get_modifiable(ext_updates, &count); - for (i = 0; i < count; i++) { - if (array_is_created(&seqs[i]) && - mail_index_seq_array_lookup(&seqs[i], seq, &idx)) - array_delete(&seqs[i], idx, 1); - } -} - -static void -mail_index_expunge_last_append(struct mail_index_transaction *t, uint32_t seq) -{ - struct mail_index_transaction_keyword_update *kw_updates; - unsigned int i, count; - - i_assert(seq == t->last_new_seq); - - /* remove extension updates */ - mail_index_expunge_last_append_ext(&t->ext_rec_updates, seq); - mail_index_expunge_last_append_ext(&t->ext_rec_atomics, seq); - t->log_ext_updates = mail_index_transaction_has_ext_changes(t); - - /* remove keywords */ - if (array_is_created(&t->keyword_resets)) - seq_range_array_remove(&t->keyword_resets, seq); - if (array_is_created(&t->keyword_updates)) { - kw_updates = array_get_modifiable(&t->keyword_updates, &count); - for (i = 0; i < count; i++) { - if (array_is_created(&kw_updates[i].add_seq)) { - seq_range_array_remove(&kw_updates[i].add_seq, - seq); - } - if (array_is_created(&kw_updates[i].remove_seq)) { - seq_range_array_remove( - &kw_updates[i].remove_seq, seq); - } - } - } - - /* and finally remove the append itself */ - array_delete(&t->appends, seq - t->first_new_seq, 1); - t->last_new_seq--; - if (t->first_new_seq > t->last_new_seq) { - t->last_new_seq = 0; - t->appends_nonsorted = FALSE; - array_free(&t->appends); - } - mail_index_transaction_set_log_updates(t); -} - -void mail_index_expunge(struct mail_index_transaction *t, uint32_t seq) -{ - i_assert(seq > 0); - if (seq >= t->first_new_seq) { - /* we can handle only the last append. otherwise we'd have to - renumber sequences and that gets tricky. for now this is - enough, since we typically want to expunge all the - appends. */ - mail_index_expunge_last_append(t, seq); - } else { - t->log_updates = TRUE; - - /* expunges is a sorted array of {seq1, seq2, ..}, .. */ - seq_range_array_add(&t->expunges, 128, seq); - } -} - -static void update_minmax_flagupdate_seq(struct mail_index_transaction *t, - uint32_t seq1, uint32_t seq2) -{ - if (t->min_flagupdate_seq == 0) { - t->min_flagupdate_seq = seq1; - t->max_flagupdate_seq = seq2; - } else { - if (t->min_flagupdate_seq > seq1) - t->min_flagupdate_seq = seq1; - if (t->max_flagupdate_seq < seq2) - t->max_flagupdate_seq = seq2; - } -} - -unsigned int -mail_index_transaction_get_flag_update_pos(struct mail_index_transaction *t, - unsigned int left_idx, - unsigned int right_idx, - uint32_t seq) -{ - const struct mail_transaction_flag_update *updates; - unsigned int idx, count; - - updates = array_get(&t->updates, &count); - i_assert(left_idx <= right_idx && right_idx <= count); - - /* find the first update with either overlapping range, - or the update which will come after our insert */ - idx = left_idx; - while (left_idx < right_idx) { - idx = (left_idx + right_idx) / 2; - - if (updates[idx].uid2 < seq) - left_idx = idx+1; - else if (updates[idx].uid1 > seq) - right_idx = idx; - else - break; - } - if (left_idx > idx) - idx++; - return idx; -} - -static void -mail_index_insert_flag_update(struct mail_index_transaction *t, - struct mail_transaction_flag_update u, - unsigned int idx) -{ - struct mail_transaction_flag_update *updates, tmp_update; - unsigned int count, first_idx, max; - - updates = array_get_modifiable(&t->updates, &count); - - /* overlapping ranges, split/merge them */ - i_assert(idx == 0 || updates[idx-1].uid2 < u.uid1); - i_assert(idx == count || updates[idx].uid2 >= u.uid1); - - /* first we'll just add the changes without trying to merge anything */ - first_idx = idx; - for (; idx < count && u.uid2 >= updates[idx].uid1; idx++) { - i_assert(u.uid1 <= updates[idx].uid2); - if (u.uid1 != updates[idx].uid1 && - (updates[idx].add_flags != u.add_flags || - updates[idx].remove_flags != u.remove_flags)) { - if (u.uid1 < updates[idx].uid1) { - /* insert new update */ - tmp_update = u; - tmp_update.uid2 = updates[idx].uid1 - 1; - } else { - /* split existing update from beginning */ - tmp_update = updates[idx]; - tmp_update.uid2 = u.uid1 - 1; - updates[idx].uid1 = u.uid1; - } - - i_assert(tmp_update.uid1 <= tmp_update.uid2); - i_assert(updates[idx].uid1 <= updates[idx].uid2); - - array_insert(&t->updates, idx, &tmp_update, 1); - updates = array_get_modifiable(&t->updates, &count); - idx++; - } else if (u.uid1 < updates[idx].uid1) { - updates[idx].uid1 = u.uid1; - } - - if (u.uid2 < updates[idx].uid2 && - (updates[idx].add_flags != u.add_flags || - updates[idx].remove_flags != u.remove_flags)) { - /* split existing update from end */ - tmp_update = updates[idx]; - tmp_update.uid2 = u.uid2; - updates[idx].uid1 = u.uid2 + 1; - - i_assert(tmp_update.uid1 <= tmp_update.uid2); - i_assert(updates[idx].uid1 <= updates[idx].uid2); - - array_insert(&t->updates, idx, &tmp_update, 1); - updates = array_get_modifiable(&t->updates, &count); - } - - updates[idx].add_flags = - (updates[idx].add_flags | u.add_flags) & - ~u.remove_flags; - updates[idx].remove_flags = - (updates[idx].remove_flags | u.remove_flags) & - ~u.add_flags; - u.uid1 = updates[idx].uid2 + 1; - - if (updates[idx].add_flags == 0 && - updates[idx].remove_flags == 0) { - /* we can remove this update completely */ - array_delete(&t->updates, idx, 1); - updates = array_get_modifiable(&t->updates, &count); - } - - if (u.uid1 > u.uid2) { - /* break here before idx++ so last_update_idx is set - correctly */ - break; - } - } - i_assert(idx <= count); - - if (u.uid1 <= u.uid2) { - i_assert(idx == 0 || updates[idx-1].uid2 < u.uid1); - i_assert(idx == count || updates[idx].uid1 > u.uid2); - array_insert(&t->updates, idx, &u, 1); - } - updates = array_get_modifiable(&t->updates, &count); - t->last_update_idx = idx == count ? count-1 : idx; - - /* merge everything */ - idx = first_idx == 0 ? 0 : first_idx - 1; - max = I_MIN(t->last_update_idx + 1, count); - for (; idx < max; ) { - if (updates[idx].uid2 + 1 == updates[idx+1].uid1 && - updates[idx].add_flags == updates[idx+1].add_flags && - updates[idx].remove_flags == updates[idx+1].remove_flags) { - /* merge */ - updates[idx].uid2 = updates[idx+1].uid2; - array_delete(&t->updates, idx + 1, 1); - max--; - if (t->last_update_idx > idx) - t->last_update_idx--; - updates = array_get_modifiable(&t->updates, &count); - } else { - idx++; - } - } -} - -static void mail_index_record_modify_flags(struct mail_index_record *rec, - enum modify_type modify_type, - enum mail_flags flags) -{ - switch (modify_type) { - case MODIFY_REPLACE: - rec->flags = flags; - break; - case MODIFY_ADD: - rec->flags |= flags; - break; - case MODIFY_REMOVE: - rec->flags &= ~flags; - break; - } -} - -void mail_index_update_flags_range(struct mail_index_transaction *t, - uint32_t seq1, uint32_t seq2, - enum modify_type modify_type, - enum mail_flags flags) -{ - struct mail_index_record *rec; - struct mail_transaction_flag_update u, *last_update; - unsigned int idx, first_idx, count; - - update_minmax_flagupdate_seq(t, seq1, seq2); - if (seq2 >= t->first_new_seq) { - /* updates for appended messages, modify them directly */ - uint32_t seq; - - for (seq = I_MAX(t->first_new_seq, seq1); seq <= seq2; seq++) { - rec = mail_index_transaction_lookup(t, seq); - mail_index_record_modify_flags(rec, modify_type, flags); - } - if (seq1 >= t->first_new_seq) - return; - - /* range contains also existing messages. update them next. */ - seq2 = t->first_new_seq - 1; - } - - i_assert(seq1 <= seq2 && seq1 > 0); - i_assert(seq2 <= mail_index_view_get_messages_count(t->view)); - - if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_AVOID_FLAG_UPDATES) != 0) - t->drop_unnecessary_flag_updates = TRUE; - - memset(&u, 0, sizeof(u)); - u.uid1 = seq1; - u.uid2 = seq2; - - switch (modify_type) { - case MODIFY_REPLACE: - u.add_flags = flags; - u.remove_flags = ~flags & MAIL_INDEX_FLAGS_MASK; - break; - case MODIFY_ADD: - if (flags == 0) - return; - u.add_flags = flags; - break; - case MODIFY_REMOVE: - if (flags == 0) - return; - u.remove_flags = flags; - break; - } - - if (!array_is_created(&t->updates)) { - i_array_init(&t->updates, 256); - array_append(&t->updates, &u, 1); - return; - } - - last_update = array_get_modifiable(&t->updates, &count); - if (t->last_update_idx < count) { - /* fast path - hopefully we're updating the next message, - or a message that is to be appended as last update */ - last_update += t->last_update_idx; - if (seq1 - 1 == last_update->uid2) { - if (u.add_flags == last_update->add_flags && - u.remove_flags == last_update->remove_flags && - (t->last_update_idx + 1 == count || - last_update[1].uid1 > seq2)) { - /* we can just update the UID range */ - last_update->uid2 = seq2; - return; - } - } else if (seq1 > last_update->uid2) { - /* hopefully we can just append it */ - t->last_update_idx++; - last_update++; - } - } - - if (t->last_update_idx == count) - array_append(&t->updates, &u, 1); - else { - i_assert(t->last_update_idx < count); - - /* slow path */ - if (seq1 > last_update->uid2) { - /* added after this */ - first_idx = t->last_update_idx + 1; - } else { - /* added before this or on top of this */ - first_idx = 0; - count = t->last_update_idx + 1; - } - idx = mail_index_transaction_get_flag_update_pos(t, first_idx, - count, u.uid1); - mail_index_insert_flag_update(t, u, idx); - } -} - -void mail_index_update_flags(struct mail_index_transaction *t, uint32_t seq, - enum modify_type modify_type, - enum mail_flags flags) -{ - mail_index_update_flags_range(t, seq, seq, modify_type, flags); -} - -void mail_index_update_header(struct mail_index_transaction *t, - size_t offset, const void *data, size_t size, - bool prepend) -{ - i_assert(offset < sizeof(t->pre_hdr_change)); - i_assert(size <= sizeof(t->pre_hdr_change) - offset); - - t->log_updates = TRUE; - - if (prepend) { - t->pre_hdr_changed = TRUE; - memcpy(t->pre_hdr_change + offset, data, size); - for (; size > 0; size--) - t->pre_hdr_mask[offset++] = 1; - } else { - t->post_hdr_changed = TRUE; - memcpy(t->post_hdr_change + offset, data, size); - for (; size > 0; size--) - t->post_hdr_mask[offset++] = 1; - } -} - -void mail_index_ext_resize(struct mail_index_transaction *t, uint32_t ext_id, - uint32_t hdr_size, uint16_t record_size, - uint16_t record_align) -{ - struct mail_transaction_ext_intro intro; - uint32_t old_record_size, old_record_align; - - memset(&intro, 0, sizeof(intro)); - - /* get ext_id from transaction's map if it's there */ - if (!mail_index_map_get_ext_idx(t->view->map, ext_id, &intro.ext_id)) { - /* have to create it */ - const struct mail_index_registered_ext *rext; - - intro.ext_id = (uint32_t)-1; - rext = array_idx(&t->view->index->extensions, ext_id); - old_record_size = rext->record_size; - old_record_align = rext->record_align; - } else { - const struct mail_index_ext *ext; - - ext = array_idx(&t->view->map->extensions, intro.ext_id); - old_record_size = ext->record_size; - old_record_align = ext->record_align; - } - - /* allow only header size changes if extension records have already - been changed in transaction */ - i_assert(!array_is_created(&t->ext_rec_updates) || - (old_record_size == record_size && - old_record_align == record_align)); - - t->log_ext_updates = TRUE; - - if (!array_is_created(&t->ext_resizes)) - i_array_init(&t->ext_resizes, ext_id + 2); - - intro.hdr_size = hdr_size; - intro.record_size = record_size; - intro.record_align = record_align; - intro.name_size = 1; - array_idx_set(&t->ext_resizes, ext_id, &intro); -} - -void mail_index_ext_reset(struct mail_index_transaction *t, uint32_t ext_id, - uint32_t reset_id, bool clear_data) -{ - struct mail_transaction_ext_reset reset; - - i_assert(reset_id != 0); - - memset(&reset, 0, sizeof(reset)); - reset.new_reset_id = reset_id; - reset.preserve_data = !clear_data; - - mail_index_ext_set_reset_id(t, ext_id, reset_id); - - if (!array_is_created(&t->ext_resets)) - i_array_init(&t->ext_resets, ext_id + 2); - array_idx_set(&t->ext_resets, ext_id, &reset); - t->log_ext_updates = TRUE; -} - -void mail_index_ext_reset_inc(struct mail_index_transaction *t, uint32_t ext_id, - uint32_t prev_reset_id, bool clear_data) -{ - uint32_t expected_reset_id = prev_reset_id + 1; - - mail_index_ext_reset(t, ext_id, (uint32_t)-1, clear_data); - - if (!array_is_created(&t->ext_reset_atomic)) - i_array_init(&t->ext_reset_atomic, ext_id + 2); - array_idx_set(&t->ext_reset_atomic, ext_id, &expected_reset_id); -} - -static bool -mail_index_transaction_has_ext_updates(const ARRAY_TYPE(seq_array_array) *arr) -{ - const ARRAY_TYPE(seq_array) *array; - unsigned int i, count; - - if (array_is_created(arr)) { - array = array_get(arr, &count); - for (i = 0; i < count; i++) { - if (array_is_created(&array[i])) - return TRUE; - } - } - return FALSE; -} - -static bool -mail_index_transaction_has_ext_changes(struct mail_index_transaction *t) -{ - unsigned int i, count; - - if (mail_index_transaction_has_ext_updates(&t->ext_rec_updates)) - return TRUE; - if (mail_index_transaction_has_ext_updates(&t->ext_rec_atomics)) - return TRUE; - - if (array_is_created(&t->ext_hdr_updates)) { - const struct mail_index_transaction_ext_hdr_update *hdr; - - hdr = array_get(&t->ext_hdr_updates, &count); - for (i = 0; i < count; i++) { - if (hdr[i].alloc_size > 0) - return TRUE; - } - } - if (array_is_created(&t->ext_resets)) { - const struct mail_transaction_ext_reset *resets; - - resets = array_get(&t->ext_resets, &count); - for (i = 0; i < count; i++) { - if (resets[i].new_reset_id != 0) - return TRUE; - } - } - if (array_is_created(&t->ext_resizes)) { - const struct mail_transaction_ext_intro *resizes; - - resizes = array_get(&t->ext_resizes, &count); - for (i = 0; i < count; i++) { - if (resizes[i].name_size > 0) - return TRUE; - } - } - return FALSE; -} - -static void -mail_index_ext_update_reset(ARRAY_TYPE(seq_array_array) *arr, uint32_t ext_id) -{ - if (array_is_created(arr) && ext_id < array_count(arr)) { - /* if extension records have been updated, clear them */ - ARRAY_TYPE(seq_array) *array; - - array = array_idx_modifiable(arr, ext_id); - if (array_is_created(array)) - array_clear(array); - } -} - -void mail_index_ext_set_reset_id(struct mail_index_transaction *t, - uint32_t ext_id, uint32_t reset_id) -{ - mail_index_ext_update_reset(&t->ext_rec_updates, ext_id); - mail_index_ext_update_reset(&t->ext_rec_atomics, ext_id); - if (array_is_created(&t->ext_hdr_updates) && - ext_id < array_count(&t->ext_hdr_updates)) { - /* if extension headers have been updated, clear them */ - struct mail_index_transaction_ext_hdr_update *hdr; - - hdr = array_idx_modifiable(&t->ext_hdr_updates, ext_id); - if (hdr->alloc_size > 0) { - i_free_and_null(hdr->mask); - i_free_and_null(hdr->data); - } - hdr->alloc_size = 0; - } - if (array_is_created(&t->ext_resets) && - ext_id < array_count(&t->ext_resets)) { - /* clear resets */ - array_idx_clear(&t->ext_resets, ext_id); - } - if (array_is_created(&t->ext_resizes) && - ext_id < array_count(&t->ext_resizes)) { - /* clear resizes */ - array_idx_clear(&t->ext_resizes, ext_id); - } - - if (!array_is_created(&t->ext_reset_ids)) - i_array_init(&t->ext_reset_ids, ext_id + 2); - array_idx_set(&t->ext_reset_ids, ext_id, &reset_id); - - t->log_ext_updates = mail_index_transaction_has_ext_changes(t); -} - -void mail_index_update_header_ext(struct mail_index_transaction *t, - uint32_t ext_id, size_t offset, - const void *data, size_t size) -{ - struct mail_index_transaction_ext_hdr_update *hdr; - size_t new_size; - - i_assert(offset <= (uint16_t)-1 && size <= (uint16_t)-1 && - offset + size <= (uint16_t)-1); - - if (!array_is_created(&t->ext_hdr_updates)) - i_array_init(&t->ext_hdr_updates, ext_id + 2); - - hdr = array_idx_modifiable(&t->ext_hdr_updates, ext_id); - if (hdr->alloc_size < offset || hdr->alloc_size - offset < size) { - i_assert(size < (size_t)-1 - offset); - new_size = nearest_power(offset + size); - hdr->mask = i_realloc(hdr->mask, hdr->alloc_size, new_size); - hdr->data = i_realloc(hdr->data, hdr->alloc_size, new_size); - hdr->alloc_size = new_size; - } - memset(hdr->mask + offset, 1, size); - memcpy(hdr->data + offset, data, size); - - t->log_ext_updates = TRUE; -} - -void mail_index_update_ext(struct mail_index_transaction *t, uint32_t seq, - uint32_t ext_id, const void *data, void *old_data_r) -{ - struct mail_index *index = t->view->index; - const struct mail_index_registered_ext *rext; - const struct mail_transaction_ext_intro *intro; - uint16_t record_size; - ARRAY_TYPE(seq_array) *array; - unsigned int count; - - i_assert(seq > 0 && - (seq <= mail_index_view_get_messages_count(t->view) || - seq <= t->last_new_seq)); - i_assert(ext_id < array_count(&index->extensions)); - - t->log_ext_updates = TRUE; - - if (!array_is_created(&t->ext_resizes)) { - intro = NULL; - count = 0; - } else { - intro = array_get(&t->ext_resizes, &count); - } - if (ext_id < count && intro[ext_id].name_size != 0) { - /* resized record */ - record_size = intro[ext_id].record_size; - } else { - rext = array_idx(&index->extensions, ext_id); - record_size = rext->record_size; - } - - if (!array_is_created(&t->ext_rec_updates)) - i_array_init(&t->ext_rec_updates, ext_id + 2); - array = array_idx_modifiable(&t->ext_rec_updates, ext_id); - - /* @UNSAFE */ - if (!mail_index_seq_array_add(array, seq, data, record_size, - old_data_r)) { - /* not found, clear old_data if it was given */ - if (old_data_r != NULL) - memset(old_data_r, 0, record_size); - } -} - -int mail_index_atomic_inc_ext(struct mail_index_transaction *t, - uint32_t seq, uint32_t ext_id, int diff) -{ - ARRAY_TYPE(seq_array) *array; - int32_t old_diff32, diff32 = diff; - - i_assert(seq > 0 && - (seq <= mail_index_view_get_messages_count(t->view) || - seq <= t->last_new_seq)); - i_assert(ext_id < array_count(&t->view->index->extensions)); - /* currently non-external transactions can be applied multiple times, - causing multiple increments. */ - //FIXME:i_assert((t->flags & MAIL_INDEX_TRANSACTION_FLAG_EXTERNAL) != 0); - - t->log_ext_updates = TRUE; - if (!array_is_created(&t->ext_rec_atomics)) - i_array_init(&t->ext_rec_atomics, ext_id + 2); - array = array_idx_modifiable(&t->ext_rec_atomics, ext_id); - if (mail_index_seq_array_add(array, seq, &diff32, sizeof(diff32), - &old_diff32)) { - /* already incremented this sequence in this transaction */ - diff32 += old_diff32; - mail_index_seq_array_add(array, seq, &diff32, sizeof(diff32), - NULL); - } - return diff32; -} - -static bool -keyword_update_has_changes(struct mail_index_transaction *t, uint32_t seq, - enum modify_type modify_type, - struct mail_keywords *keywords) -{ - struct mail_index_transaction_keyword_update *u; - ARRAY_TYPE(keyword_indexes) existing; - const unsigned int *existing_idx; - unsigned int i, j, existing_count; - bool found; - - t_array_init(&existing, 32); - if (seq < t->first_new_seq) - mail_index_lookup_keywords(t->view, seq, &existing); - existing_idx = array_get(&existing, &existing_count); - - if (modify_type == MODIFY_REPLACE && existing_count != keywords->count) - return TRUE; - - for (i = 0; i < keywords->count; i++) { - u = array_idx_modifiable(&t->keyword_updates, - keywords->idx[i]); - if (array_is_created(&u->add_seq) || - array_is_created(&u->remove_seq)) - return TRUE; - - found = FALSE; - for (j = 0; j < existing_count; j++) { - if (existing_idx[j] == keywords->idx[i]) { - found = TRUE; - break; - } - } - switch (modify_type) { - case MODIFY_ADD: - case MODIFY_REPLACE: - if (!found) - return TRUE; - break; - case MODIFY_REMOVE: - if (found) - return TRUE; - break; - } - } - return FALSE; -} - -void mail_index_update_keywords(struct mail_index_transaction *t, uint32_t seq, - enum modify_type modify_type, - struct mail_keywords *keywords) -{ - struct mail_index_transaction_keyword_update *u; - unsigned int i, ku_count; - bool changed; - - i_assert(seq > 0 && - (seq <= mail_index_view_get_messages_count(t->view) || - seq <= t->last_new_seq)); - i_assert(keywords->count > 0 || modify_type == MODIFY_REPLACE); - i_assert(keywords->index == t->view->index); - - update_minmax_flagupdate_seq(t, seq, seq); - - if (!array_is_created(&t->keyword_updates) && keywords->count > 0) { - uint32_t max_idx = keywords->idx[keywords->count-1]; - - i_array_init(&t->keyword_updates, max_idx + 1); - } - - if ((t->flags & MAIL_INDEX_TRANSACTION_FLAG_AVOID_FLAG_UPDATES) != 0) { - T_BEGIN { - changed = keyword_update_has_changes(t, seq, - modify_type, - keywords); - } T_END; - if (!changed) - return; - } - - /* Update add_seq and remove_seq arrays which describe the keyword - changes. Don't bother updating remove_seq or keyword resets for - newly added messages since they default to not having any - keywords anyway. */ - switch (modify_type) { - case MODIFY_ADD: - for (i = 0; i < keywords->count; i++) { - u = array_idx_modifiable(&t->keyword_updates, - keywords->idx[i]); - seq_range_array_add(&u->add_seq, 16, seq); - seq_range_array_remove(&u->remove_seq, seq); - } - break; - case MODIFY_REMOVE: - for (i = 0; i < keywords->count; i++) { - u = array_idx_modifiable(&t->keyword_updates, - keywords->idx[i]); - seq_range_array_remove(&u->add_seq, seq); - if (seq < t->first_new_seq) - seq_range_array_add(&u->remove_seq, 16, seq); - } - break; - case MODIFY_REPLACE: - /* Remove sequence from all add/remove arrays */ - if (array_is_created(&t->keyword_updates)) { - u = array_get_modifiable(&t->keyword_updates, - &ku_count); - for (i = 0; i < ku_count; i++) { - seq_range_array_remove(&u[i].add_seq, seq); - seq_range_array_remove(&u[i].remove_seq, seq); - } - } - /* Add the wanted keyword back */ - for (i = 0; i < keywords->count; i++) { - u = array_idx_modifiable(&t->keyword_updates, - keywords->idx[i]); - seq_range_array_add(&u->add_seq, 16, seq); - } - - if (seq < t->first_new_seq) - seq_range_array_add(&t->keyword_resets, 16, seq); - break; - } - - t->log_updates = TRUE; -} - -void mail_index_reset(struct mail_index_transaction *t) -{ - mail_index_transaction_reset(t); - - t->reset = TRUE; -} - -void mail_index_transaction_set_max_modseq(struct mail_index_transaction *t, - uint64_t max_modseq, - ARRAY_TYPE(seq_range) *seqs) -{ - i_assert(array_is_created(seqs)); - - t->max_modseq = max_modseq; - t->conflict_seqs = seqs; -} - static struct mail_index_transaction_vfuncs trans_vfuncs = { mail_index_transaction_reset_v, mail_index_transaction_commit_v,
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/lib-index/test-mail-index-transaction-update.c Mon Jul 13 19:54:28 2009 -0400 @@ -0,0 +1,435 @@ +/* Copyright (c) 2009 Dovecot authors, see the included COPYING file */ + +#include "lib.h" +#include "array.h" +#include "test-common.h" +#include "mail-index-private.h" +#include "mail-index-transaction-private.h" + +#include <stdlib.h> + +static struct mail_index_header hdr; +static struct mail_index_record rec; + +const struct mail_index_header * +mail_index_get_header(struct mail_index_view *view ATTR_UNUSED) +{ + return &hdr; +} + +const struct mail_index_record * +mail_index_lookup(struct mail_index_view *view ATTR_UNUSED, + uint32_t seq ATTR_UNUSED) +{ + return &rec; +} + +void mail_index_lookup_keywords(struct mail_index_view *view ATTR_UNUSED, + uint32_t seq ATTR_UNUSED, + ARRAY_TYPE(keyword_indexes) *keyword_idx ATTR_UNUSED) +{ + array_clear(keyword_idx); +} + +bool mail_index_map_get_ext_idx(struct mail_index_map *map ATTR_UNUSED, + uint32_t ext_id ATTR_UNUSED, + uint32_t *idx_r ATTR_UNUSED) +{ + return FALSE; +} + +uint32_t mail_index_view_get_messages_count(struct mail_index_view *view ATTR_UNUSED) +{ + return hdr.messages_count; +} + +static struct mail_index_transaction * +mail_index_transaction_new(void) +{ + struct mail_index_transaction *t; + + t = t_new(struct mail_index_transaction, 1); + t->first_new_seq = hdr.messages_count + 1; + return t; +} + +static void test_mail_index_append(void) +{ + struct mail_index_transaction *t; + const struct mail_index_record *appends; + unsigned int count; + uint32_t seq, next_uid; + + hdr.messages_count = 4; + t = mail_index_transaction_new(); + + test_begin("mail index append"); + mail_index_append(t, 0, &seq); + test_assert(t->log_updates); + test_assert(seq == 5); + mail_index_append(t, 0, &seq); + test_assert(seq == 6); + test_assert(!t->appends_nonsorted); + + mail_index_append_assign_uids(t, 123, &next_uid); + test_assert(next_uid == 125); + + appends = array_get(&t->appends, &count); + test_assert(appends[0].uid == 123); + test_assert(appends[0].flags == 0); + test_assert(appends[1].uid == 124); + test_assert(appends[1].flags == 0); + test_end(); + + /* test with some uids */ + t = mail_index_transaction_new(); + + test_begin("mail index append with uids"); + mail_index_append(t, 0, &seq); + test_assert(seq == 5); + mail_index_append(t, 126, &seq); + test_assert(seq == 6); + test_assert(!t->appends_nonsorted); + mail_index_append(t, 124, &seq); + test_assert(seq == 7); + test_assert(t->appends_nonsorted); + mail_index_append(t, 0, &seq); + test_assert(seq == 8); + mail_index_append(t, 128, &seq); + test_assert(seq == 9); + test_assert(t->highest_append_uid == 128); + + mail_index_append_assign_uids(t, 129, &next_uid); + test_assert(next_uid == 131); + + appends = array_get(&t->appends, &count); + test_assert(count == 5); + test_assert(appends[0].uid == 129); + test_assert(appends[1].uid == 126); + test_assert(appends[2].uid == 124); + test_assert(appends[3].uid == 130); + test_assert(appends[4].uid == 128); + test_end(); +} + +static void test_mail_index_flag_update_fastpath(void) +{ + struct mail_index_transaction *t; + const struct mail_transaction_flag_update *updates; + unsigned int count; + + hdr.messages_count = 20; + t = mail_index_transaction_new(); + + test_begin("mail index flag update fast paths"); + + mail_index_update_flags_range(t, 13, 14, MODIFY_REPLACE, + MAIL_DELETED); + test_assert(t->last_update_idx == 0); + test_assert(array_count(&t->updates) == 1); + + mail_index_update_flags_range(t, 15, 15, MODIFY_REPLACE, + MAIL_DELETED); + test_assert(t->last_update_idx == 0); + test_assert(array_count(&t->updates) == 1); + + mail_index_update_flags_range(t, 16, 16, MODIFY_ADD, + MAIL_DELETED); + test_assert(t->last_update_idx == 1); + test_assert(array_count(&t->updates) == 2); + + updates = array_get(&t->updates, &count); + test_assert(updates[0].uid1 == 13); + test_assert(updates[0].uid2 == 15); + test_assert(updates[0].add_flags == MAIL_DELETED); + test_assert(updates[0].remove_flags == + (MAIL_ANSWERED | MAIL_FLAGGED | MAIL_SEEN | MAIL_DRAFT)); + test_assert(updates[0].padding == 0); + test_assert(updates[1].uid1 == 16); + test_assert(updates[1].uid2 == 16); + test_assert(updates[1].add_flags == MAIL_DELETED); + test_assert(updates[1].remove_flags == 0); + test_assert(updates[1].padding == 0); + test_assert(!t->log_updates); + test_end(); +} + +static void test_mail_index_flag_update_simple_merges(void) +{ + struct mail_index_transaction *t; + const struct mail_transaction_flag_update *updates; + unsigned int count; + + hdr.messages_count = 20; + t = mail_index_transaction_new(); + + test_begin("mail index flag update simple merges"); + + mail_index_update_flags_range(t, 6, 8, MODIFY_ADD, + MAIL_FLAGGED); + test_assert(t->last_update_idx == 0); + mail_index_update_flags_range(t, 5, 6, MODIFY_ADD, + MAIL_FLAGGED); + test_assert(t->last_update_idx == 0); + mail_index_update_flags_range(t, 4, 4, MODIFY_ADD, + MAIL_FLAGGED); + test_assert(t->last_update_idx == 0); + mail_index_update_flags_range(t, 7, 9, MODIFY_ADD, + MAIL_FLAGGED); + test_assert(t->last_update_idx == 0); + mail_index_update_flags_range(t, 10, 10, MODIFY_ADD, + MAIL_FLAGGED); + updates = array_get(&t->updates, &count); + test_assert(count == 1); + test_assert(updates[0].uid1 == 4); + test_assert(updates[0].uid2 == 10); + test_assert(updates[0].add_flags == MAIL_FLAGGED); + test_assert(updates[0].remove_flags == 0); + + mail_index_update_flags_range(t, 12, 12, MODIFY_ADD, + MAIL_FLAGGED); + mail_index_update_flags_range(t, 11, 11, MODIFY_ADD, + MAIL_FLAGGED); + test_assert(count == 1); + test_assert(updates[0].uid1 == 4); + test_assert(updates[0].uid2 == 12); + test_end(); +} + +static void test_mail_index_flag_update_complex_merges(void) +{ + struct mail_index_transaction *t; + const struct mail_transaction_flag_update *updates; + unsigned int count; + + hdr.messages_count = 20; + t = mail_index_transaction_new(); + + test_begin("mail index flag update complex merges"); + + mail_index_update_flags_range(t, 6, 8, MODIFY_REPLACE, + MAIL_SEEN); + mail_index_update_flags_range(t, 3, 6, MODIFY_ADD, + MAIL_FLAGGED); + mail_index_update_flags_range(t, 5, 7, MODIFY_ADD, + MAIL_DRAFT); + mail_index_update_flags_range(t, 6, 6, MODIFY_REPLACE, + MAIL_SEEN | MAIL_ANSWERED); + mail_index_update_flags_range(t, 5, 10, MODIFY_REMOVE, + MAIL_ANSWERED); + mail_index_update_flags_range(t, 7, 12, MODIFY_ADD, + MAIL_DELETED); + + updates = array_get(&t->updates, &count); + test_assert(count == 7); + test_assert(updates[0].uid1 == 3); + test_assert(updates[0].uid2 == 4); + test_assert(updates[0].add_flags == MAIL_FLAGGED); + test_assert(updates[0].remove_flags == 0); + test_assert(updates[1].uid1 == 5); + test_assert(updates[1].uid2 == 5); + test_assert(updates[1].add_flags == (MAIL_DRAFT | MAIL_FLAGGED)); + test_assert(updates[1].remove_flags == MAIL_ANSWERED); + test_assert(updates[2].uid1 == 6); + test_assert(updates[2].uid2 == 6); + test_assert(updates[2].add_flags == MAIL_SEEN); + test_assert(updates[2].remove_flags == (MAIL_ANSWERED | MAIL_FLAGGED | MAIL_DELETED | MAIL_DRAFT)); + test_assert(updates[3].uid1 == 7); + test_assert(updates[3].uid2 == 7); + test_assert(updates[3].add_flags == (MAIL_SEEN | MAIL_DRAFT | MAIL_DELETED)); + test_assert(updates[3].remove_flags == (MAIL_ANSWERED | MAIL_FLAGGED)); + test_assert(updates[4].uid1 == 8); + test_assert(updates[4].uid2 == 8); + test_assert(updates[4].add_flags == (MAIL_SEEN | MAIL_DELETED)); + test_assert(updates[4].remove_flags == (MAIL_ANSWERED | MAIL_FLAGGED | MAIL_DRAFT)); + test_assert(updates[5].uid1 == 9); + test_assert(updates[5].uid2 == 10); + test_assert(updates[5].add_flags == MAIL_DELETED); + test_assert(updates[5].remove_flags == MAIL_ANSWERED); + test_assert(updates[6].uid1 == 11); + test_assert(updates[6].uid2 == 12); + test_assert(updates[6].add_flags == MAIL_DELETED); + test_assert(updates[6].remove_flags == 0); + + test_end(); +} + +static bool +flags_array_check(struct mail_index_transaction *t, + const enum mail_flags *flags, unsigned int msg_count) +{ + const struct mail_transaction_flag_update *updates; + unsigned int i, count, seq; + + if (array_is_created(&t->updates)) + updates = array_get(&t->updates, &count); + else { + updates = NULL; + count = 0; + } + for (seq = 1, i = 0; i < count; i++) { + if (i > 0) { + test_assert(updates[i-1].uid2 < updates[i].uid1); + test_assert(updates[i-1].uid2 + 1 != updates[i].uid1 || + updates[i-1].add_flags != updates[i].add_flags || + updates[i-1].remove_flags != updates[i].remove_flags); + } + for (; seq != updates[i].uid1; seq++) + test_assert(flags[seq] == 0); + for (; seq <= updates[i].uid2; seq++) + test_assert(flags[seq] == updates[i].add_flags); + } + for (; seq <= msg_count; seq++) + test_assert(flags[seq] == 0); + return TRUE; +} + +static void test_mail_index_flag_update_random(void) +{ + struct mail_index_transaction *t; + unsigned int r, seq1, seq2, seq; + enum mail_flags *flags, change; + enum modify_type modify_type; + + hdr.messages_count = 20; + t = mail_index_transaction_new(); + + test_begin("mail index flag update random"); + + flags = t_new(enum mail_flags, hdr.messages_count + 1); + for (r = 0; r < 1000; r++) { + change = rand() % (MAIL_FLAGS_NONRECENT+1); + seq1 = (rand() % hdr.messages_count) + 1; + seq2 = seq1 == hdr.messages_count ? seq1 : + (rand() % (hdr.messages_count - seq1)) + seq1; + + switch (rand() % 3) { + case 0: + modify_type = MODIFY_ADD; + for (seq = seq1; seq <= seq2; seq++) + flags[seq] |= change; + break; + case 1: + modify_type = MODIFY_REMOVE; + for (seq = seq1; seq <= seq2; seq++) + flags[seq] &= ~change; + break; + case 2: + modify_type = MODIFY_REPLACE; + for (seq = seq1; seq <= seq2; seq++) + flags[seq] = change; + break; + default: + i_unreached(); + } + mail_index_update_flags_range(t, seq1, seq2, modify_type, + change); + flags_array_check(t, flags, hdr.messages_count); + } + test_end(); +} + +static void test_mail_index_flag_update_appends(void) +{ + struct mail_index_transaction *t; + const struct mail_index_record *appends; + const struct mail_transaction_flag_update *updates; + unsigned int count; + uint32_t seq; + + hdr.messages_count = 4; + t = mail_index_transaction_new(); + + test_begin("mail index flag update appends"); + mail_index_append(t, 0, &seq); + test_assert(seq == 5); + mail_index_append(t, 0, &seq); + test_assert(seq == 6); + mail_index_append(t, 0, &seq); + test_assert(seq == 7); + + mail_index_update_flags_range(t, 5, 6, MODIFY_REPLACE, + MAIL_SEEN | MAIL_FLAGGED); + mail_index_update_flags_range(t, 6, 7, MODIFY_ADD, + MAIL_DRAFT | MAIL_FLAGGED); + mail_index_update_flags_range(t, 5, 7, MODIFY_REMOVE, + MAIL_FLAGGED); + + appends = array_get(&t->appends, &count); + test_assert(count == 3); + test_assert(appends[0].flags == MAIL_SEEN); + test_assert(appends[1].flags == (MAIL_SEEN | MAIL_DRAFT)); + test_assert(appends[2].flags == MAIL_DRAFT); + + /* mixed existing/appends */ + mail_index_update_flags_range(t, 4, 5, MODIFY_ADD, + MAIL_ANSWERED); + test_assert(appends[0].flags == (MAIL_SEEN | MAIL_ANSWERED)); + + updates = array_get(&t->updates, &count); + test_assert(count == 1); + test_assert(updates[0].uid1 == 4); + test_assert(updates[0].uid2 == 4); + test_assert(updates[0].add_flags == MAIL_ANSWERED); + test_end(); +} + +static bool test_flag_update_pos(struct mail_index_transaction *t, + uint32_t seq, unsigned int idx) +{ + unsigned int i, j, count; + + count = array_count(&t->updates); + for (i = 0; i < idx; i++) { + for (j = idx + 1; j <= count; j++) { + if (!mail_index_transaction_get_flag_update_pos(t, i, j, seq) == idx) { + test_assert(FALSE); + return FALSE; + } + } + } + return TRUE; +} + +static void test_mail_index_transaction_get_flag_update_pos(void) +{ + struct mail_index_transaction *t; + + test_begin("mail index transaction get flag update pos"); + + hdr.messages_count = 10; + t = mail_index_transaction_new(); + mail_index_update_flags_range(t, 1, 1, MODIFY_REPLACE, 0); + mail_index_update_flags_range(t, 3, 4, MODIFY_REPLACE, 0); + mail_index_update_flags_range(t, 6, 7, MODIFY_REPLACE, 0); + mail_index_update_flags_range(t, 9, 10, MODIFY_REPLACE, 0); + + test_assert(test_flag_update_pos(t, 1, 0)); + test_assert(test_flag_update_pos(t, 2, 1)); + test_assert(test_flag_update_pos(t, 3, 1)); + test_assert(test_flag_update_pos(t, 4, 1)); + test_assert(test_flag_update_pos(t, 5, 2)); + test_assert(test_flag_update_pos(t, 6, 2)); + test_assert(test_flag_update_pos(t, 7, 2)); + test_assert(test_flag_update_pos(t, 8, 3)); + test_assert(test_flag_update_pos(t, 9, 3)); + test_assert(test_flag_update_pos(t, 10, 3)); + test_assert(test_flag_update_pos(t, 11, 4)); + test_assert(test_flag_update_pos(t, 12, 4)); + test_end(); +} + +int main(void) +{ + static void (*test_functions[])(void) = { + test_mail_index_append, + test_mail_index_flag_update_fastpath, + test_mail_index_flag_update_simple_merges, + test_mail_index_flag_update_complex_merges, + test_mail_index_flag_update_random, + test_mail_index_flag_update_appends, + test_mail_index_transaction_get_flag_update_pos, + NULL + }; + return test_run(test_functions); +}