[PATCH] lib: Invalidate message metadata in _notmuch_message_gen_terms
Previously, we invalidated stored message metadata in _notmuch_message_add_term and _notmuch_message_remove_term, but not in _notmuch_message_gen_terms. This doesn't currently result in any bugs because of our limited uses of _notmuch_message_gen_terms, but it may could cause trouble in the future. --- lib/message.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/message.cc b/lib/message.cc index 1618e81..cde6a1c 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1049,6 +1049,8 @@ _notmuch_message_gen_terms (notmuch_message_t *message, /* Create a gap between this an the next terms so they don't * appear to be a phrase. */ message->termpos = term_gen->get_termpos () + 100; + + _notmuch_message_invalidate_metadata (message, prefix_name); } term_gen->set_termpos (message->termpos); -- 2.0.0
[PATCH] lib: Fix slight misinformation in the database schema doc
The database schema documentation made it sound like each mail document had exactly one on-disk message file, which hasn't been true for a long time. --- lib/database.cc | 9 ++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index d90a924..c70ce63 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -55,9 +55,12 @@ typedef struct { * * Mail document * - - * A mail document is associated with a particular email message file - * on disk. It is indexed with the following prefixed terms which the - * database uses to construct threads, etc.: + * A mail document is associated with a particular email message. It + * is stored in one or more files on disk (though only one has its + * content indexed) and is uniquely identified by its "id" field + * (which is generally the message ID). It is indexed with the + * following prefixed terms which the database uses to construct + * threads, etc.: * *Single terms of given prefix: * -- 2.0.0
[PATCH v3 13/13] lib: Update doc of notmuch_database_{needs_upgrade, upgrade}
Clients are no longer required to call these functions after opening a database in read/write mode (which is good, because almost none of them do!). --- lib/notmuch.h | 21 - 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/notmuch.h b/lib/notmuch.h index cbf2ba5..d771eb2 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -352,22 +352,25 @@ unsigned int notmuch_database_get_version (notmuch_database_t *database); /** - * Does this database need to be upgraded before writing to it? + * Is the database behind the latest supported database version? * - * If this function returns TRUE then no functions that modify the - * database (notmuch_database_add_message, notmuch_message_add_tag, - * notmuch_directory_set_mtime, etc.) will work unless the function - * notmuch_database_upgrade is called successfully first. + * If this function returns TRUE, then the caller may call + * notmuch_database_upgrade to upgrade the database. If the caller + * does not upgrade an out-of-date database, then some functions may + * fail with NOTMUCH_STATUS_UPGRADE_REQUIRED. */ notmuch_bool_t notmuch_database_needs_upgrade (notmuch_database_t *database); /** - * Upgrade the current database. + * Upgrade the current database to the latest supported version. * - * After opening a database in read-write mode, the client should - * check if an upgrade is needed (notmuch_database_needs_upgrade) and - * if so, upgrade with this function before making any modifications. + * This ensures that all current notmuch functionality will be + * available on the database. After opening a database in read-write + * mode, it is recommended that clients check if an upgrade is needed + * (notmuch_database_needs_upgrade) and if so, upgrade with this + * function before making any modifications. If + * notmuch_database_needs_upgrade returns FALSE, this will be a no-op. * * The optional progress_notify callback can be used by the caller to * provide progress indication to the user. If non-NULL it will be -- 2.0.0
[PATCH v3 12/13] lib: Return an error from operations that require an upgrade
Previously, there was no protection against a caller invoking an operation on an old database version that would effectively corrupt the database by treating it like a newer version. According to notmuch.h, any caller that opens the database in read/write mode is supposed to check if the database needs upgrading and perform an upgrade if it does. This would protect against this, but nobody (even the CLI) actually does this. However, with features, it's easy to protect against incompatible operations on a fine-grained basis. This lightweight change allows callers to safely operate on old database versions, while preventing specific operations that would corrupt the database with an informative error message. --- lib/database.cc | 5 + lib/directory.cc | 5 + lib/message.cc | 8 lib/notmuch.h| 16 4 files changed, 34 insertions(+) diff --git a/lib/database.cc b/lib/database.cc index 04b3790..d90a924 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -310,6 +310,8 @@ notmuch_status_to_string (notmuch_status_t status) return "Unbalanced number of calls to notmuch_database_begin_atomic/end_atomic"; case NOTMUCH_STATUS_UNSUPPORTED_OPERATION: return "Unsupported operation"; +case NOTMUCH_STATUS_UPGRADE_REQUIRED: + return "Operation requires a database upgrade"; default: case NOTMUCH_STATUS_LAST_STATUS: return "Unknown error status value"; @@ -2219,6 +2221,9 @@ notmuch_database_find_message_by_filename (notmuch_database_t *notmuch, if (message_ret == NULL) return NOTMUCH_STATUS_NULL_POINTER; +if (! (notmuch->features & NOTMUCH_FEATURE_FILE_TERMS)) + return NOTMUCH_STATUS_UPGRADE_REQUIRED; + /* return NULL on any failure */ *message_ret = NULL; diff --git a/lib/directory.cc b/lib/directory.cc index 6a3ffed..8daaec8 100644 --- a/lib/directory.cc +++ b/lib/directory.cc @@ -105,6 +105,11 @@ _notmuch_directory_create (notmuch_database_t *notmuch, const char *db_path; notmuch_bool_t create = (flags & NOTMUCH_FIND_CREATE); +if (! (notmuch->features & NOTMUCH_FEATURE_DIRECTORY_DOCS)) { + *status_ret = NOTMUCH_STATUS_UPGRADE_REQUIRED; + return NULL; +} + *status_ret = NOTMUCH_STATUS_SUCCESS; path = _notmuch_database_relative_path (notmuch, path); diff --git a/lib/message.cc b/lib/message.cc index 4fc427f..1618e81 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -653,6 +653,10 @@ _notmuch_message_add_filename (notmuch_message_t *message, if (filename == NULL) INTERNAL_ERROR ("Message filename cannot be NULL."); +if (! (message->notmuch->features & NOTMUCH_FEATURE_FILE_TERMS) || + ! (message->notmuch->features & NOTMUCH_FEATURE_BOOL_FOLDER)) + return NOTMUCH_STATUS_UPGRADE_REQUIRED; + relative = _notmuch_database_relative_path (message->notmuch, filename); status = _notmuch_database_split_path (local, relative, &directory, NULL); @@ -697,6 +701,10 @@ _notmuch_message_remove_filename (notmuch_message_t *message, notmuch_private_status_t private_status; notmuch_status_t status; +if (! (message->notmuch->features & NOTMUCH_FEATURE_FILE_TERMS) || + ! (message->notmuch->features & NOTMUCH_FEATURE_BOOL_FOLDER)) + return NOTMUCH_STATUS_UPGRADE_REQUIRED; + status = _notmuch_database_filename_to_direntry ( local, message->notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry); if (status || !direntry) diff --git a/lib/notmuch.h b/lib/notmuch.h index 3c5ec98..cbf2ba5 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -160,6 +160,10 @@ typedef enum _notmuch_status { */ NOTMUCH_STATUS_UNSUPPORTED_OPERATION, /** + * The operation requires a database upgrade. + */ +NOTMUCH_STATUS_UPGRADE_REQUIRED, +/** * Not an actual status value. Just a way to find out how many * valid status values there are. */ @@ -438,6 +442,9 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch); * * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred; * directory not retrieved. + * + * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the + * database to use this function. */ notmuch_status_t notmuch_database_get_directory (notmuch_database_t *database, @@ -490,6 +497,9 @@ notmuch_database_get_directory (notmuch_database_t *database, * * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only * mode so no message can be added. + * + * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the + * database to use this function. */ notmuch_status_t notmuch_database_add_message (notmuch_database_t *database, @@ -520,6 +530,9 @@ notmuch_database_add_message (notmuch_database_t *database, * * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only * mode so no message can be removed. + * + * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the + * databa
[PATCH v3 11/13] lib: Support empty header values in database
Commit 567bcbc2 introduced support for storing various headers in document values. However, doing so in a backwards-compatible way meant that genuinely empty header values could not be distinguished from the old behavior of not storing the headers at all, so these required parsing the original message. Now that we have database features, new databases can declare that all messages have header values, so if we have this feature flag, we can use the stored header value even if it's the empty string. This requires slight cleanup to notmuch_message_get_header, since the code previously couldn't distinguish between empty headers and headers that are never stored in the database (previously this distinction didn't matter). --- lib/message.cc | 45 +++-- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index e6a5a5a..4fc427f 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -412,26 +412,35 @@ _notmuch_message_ensure_message_file (notmuch_message_t *message) const char * notmuch_message_get_header (notmuch_message_t *message, const char *header) { -try { - std::string value; - - /* Fetch header from the appropriate xapian value field if -* available */ - if (strcasecmp (header, "from") == 0) - value = message->doc.get_value (NOTMUCH_VALUE_FROM); - else if (strcasecmp (header, "subject") == 0) - value = message->doc.get_value (NOTMUCH_VALUE_SUBJECT); - else if (strcasecmp (header, "message-id") == 0) - value = message->doc.get_value (NOTMUCH_VALUE_MESSAGE_ID); - - if (!value.empty()) +Xapian::valueno slot = Xapian::BAD_VALUENO; + +/* Fetch header from the appropriate xapian value field if + * available */ +if (strcasecmp (header, "from") == 0) + slot = NOTMUCH_VALUE_FROM; +else if (strcasecmp (header, "subject") == 0) + slot = NOTMUCH_VALUE_SUBJECT; +else if (strcasecmp (header, "message-id") == 0) + slot = NOTMUCH_VALUE_MESSAGE_ID; + +if (slot != Xapian::BAD_VALUENO) { + try { + std::string value = message->doc.get_value (slot); + + /* If we have NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES, then +* empty values indicate empty headers. If we don't, then +* it could just mean we didn't record the header. */ + if ((message->notmuch->features & +NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES) || + ! value.empty()) return talloc_strdup (message, value.c_str ()); -} catch (Xapian::Error &error) { - fprintf (stderr, "A Xapian exception occurred when reading header: %s\n", -error.get_msg().c_str()); - message->notmuch->exception_reported = TRUE; - return NULL; + } catch (Xapian::Error &error) { + fprintf (stderr, "A Xapian exception occurred when reading header: %s\n", +error.get_msg().c_str()); + message->notmuch->exception_reported = TRUE; + return NULL; + } } /* Otherwise fall back to parsing the file */ -- 2.0.0
[PATCH v3 10/13] lib: Report progress for combined upgrade operation
Previously, some parts of upgrade didn't report progress and for others it was possible for the progress meter to restart at 0 part way through the upgrade because each stage was reported separately. Fix this by computing the total amount of work that needs to be done up-front and updating completed work monotonically. --- lib/database.cc | 17 +++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index 31e6a93..04b3790 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1234,6 +1234,19 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, timer_is_active = TRUE; } +/* Figure out how much total work we need to do. */ +if (new_features & + (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER)) { + notmuch_query_t *query = notmuch_query_create (notmuch, ""); + total += notmuch_query_count_messages (query); + notmuch_query_destroy (query); +} +if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) { + t_end = db->allterms_end ("XTIMESTAMP"); + for (t = db->allterms_begin ("XTIMESTAMP"); t != t_end; t++) + ++total; +} + /* Perform the upgrade in a transaction. */ db->begin_transaction (true); @@ -1249,8 +1262,6 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, notmuch_message_t *message; char *filename; - total = notmuch_query_count_messages (query); - for (messages = notmuch_query_search_messages (query); notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) @@ -1333,6 +1344,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, db->delete_document (*p); } + + ++count; } } -- 2.0.0
[PATCH v3 09/13] lib: Reorganize upgrade around document types
Rather than potentially making multiple passes over the same type of data in the database, reorganize upgrade around each type of data that may be upgraded. This eliminates code duplication, will make multi-version upgrades faster, and will let us improve progress reporting. --- lib/database.cc | 72 + 1 file changed, 26 insertions(+), 46 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index 69d775f..31e6a93 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1241,11 +1241,9 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, * format. */ notmuch->features = target_features; -/* Before version 1, each message document had its filename in the - * data field. Copy that into the new format by calling - * notmuch_message_add_filename. - */ -if (new_features & NOTMUCH_FEATURE_FILE_TERMS) { +/* Perform per-message upgrades. */ +if (new_features & + (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER)) { notmuch_query_t *query = notmuch_query_create (notmuch, ""); notmuch_messages_t *messages; notmuch_message_t *message; @@ -1264,13 +1262,27 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, message = notmuch_messages_get (messages); - filename = _notmuch_message_talloc_copy_data (message); - if (filename && *filename != '\0') { - _notmuch_message_add_filename (message, filename); - _notmuch_message_clear_data (message); - _notmuch_message_sync (message); + /* Before version 1, each message document had its +* filename in the data field. Copy that into the new +* format by calling notmuch_message_add_filename. +*/ + if (new_features & NOTMUCH_FEATURE_FILE_TERMS) { + filename = _notmuch_message_talloc_copy_data (message); + if (filename && *filename != '\0') { + _notmuch_message_add_filename (message, filename); + _notmuch_message_clear_data (message); + } + talloc_free (filename); } - talloc_free (filename); + + /* Prior to version 2, the "folder:" prefix was +* probabilistic and stemmed. Change it to the current +* boolean prefix. Add "path:" prefixes while at it. +*/ + if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER) + _notmuch_message_upgrade_folder (message); + + _notmuch_message_sync (message); notmuch_message_destroy (message); @@ -1280,7 +1292,9 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, notmuch_query_destroy (query); } -/* Also, before version 1 we stored directory timestamps in +/* Perform per-directory upgrades. */ + +/* Before version 1 we stored directory timestamps in * XTIMESTAMP documents instead of the current XDIRECTORY * documents. So copy those as well. */ if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) { @@ -1322,40 +1336,6 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, } } -/* - * Prior to version 2, the "folder:" prefix was probabilistic and - * stemmed. Change it to the current boolean prefix. Add "path:" - * prefixes while at it. - */ -if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER) { - notmuch_query_t *query = notmuch_query_create (notmuch, ""); - notmuch_messages_t *messages; - notmuch_message_t *message; - - count = 0; - total = notmuch_query_count_messages (query); - - for (messages = notmuch_query_search_messages (query); -notmuch_messages_valid (messages); -notmuch_messages_move_to_next (messages)) { - if (do_progress_notify) { - progress_notify (closure, (double) count / total); - do_progress_notify = 0; - } - - message = notmuch_messages_get (messages); - - _notmuch_message_upgrade_folder (message); - _notmuch_message_sync (message); - - notmuch_message_destroy (message); - - count++; - } - - notmuch_query_destroy (query); -} - db->set_metadata ("features", _print_features (local, notmuch->features)); db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION)); -- 2.0.0
[PATCH v3 08/13] lib: Use database features to drive upgrade
Previously, we had database version information hard-coded in the upgrade code. Slightly re-organize the upgrade process around the set of new database features to be enabled by the upgrade. --- lib/database.cc | 24 +--- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index faeab51..69d775f 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1195,11 +1195,12 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, void *closure) { void *local = talloc_new (NULL); +Xapian::TermIterator t, t_end; Xapian::WritableDatabase *db; struct sigaction action; struct itimerval timerval; notmuch_bool_t timer_is_active = FALSE; -unsigned int version; +unsigned int target_features, new_features; notmuch_status_t status; unsigned int count = 0, total = 0; @@ -1209,9 +1210,10 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, db = static_cast (notmuch->xapian_db); -version = notmuch_database_get_version (notmuch); +target_features = notmuch->features | NOTMUCH_FEATURES_CURRENT; +new_features = NOTMUCH_FEATURES_CURRENT & ~notmuch->features; -if (version >= NOTMUCH_DATABASE_VERSION) +if (! new_features) return NOTMUCH_STATUS_SUCCESS; if (progress_notify) { @@ -1237,18 +1239,17 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, /* Set the target features so we write out changes in the desired * format. */ -notmuch->features |= NOTMUCH_FEATURES_CURRENT; +notmuch->features = target_features; /* Before version 1, each message document had its filename in the * data field. Copy that into the new format by calling * notmuch_message_add_filename. */ -if (version < 1) { +if (new_features & NOTMUCH_FEATURE_FILE_TERMS) { notmuch_query_t *query = notmuch_query_create (notmuch, ""); notmuch_messages_t *messages; notmuch_message_t *message; char *filename; - Xapian::TermIterator t, t_end; total = notmuch_query_count_messages (query); @@ -1277,11 +1278,12 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, } notmuch_query_destroy (query); +} - /* Also, before version 1 we stored directory timestamps in -* XTIMESTAMP documents instead of the current XDIRECTORY -* documents. So copy those as well. */ - +/* Also, before version 1 we stored directory timestamps in + * XTIMESTAMP documents instead of the current XDIRECTORY + * documents. So copy those as well. */ +if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) { t_end = notmuch->xapian_db->allterms_end ("XTIMESTAMP"); for (t = notmuch->xapian_db->allterms_begin ("XTIMESTAMP"); @@ -1325,7 +1327,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, * stemmed. Change it to the current boolean prefix. Add "path:" * prefixes while at it. */ -if (version < 2) { +if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER) { notmuch_query_t *query = notmuch_query_create (notmuch, ""); notmuch_messages_t *messages; notmuch_message_t *message; -- 2.0.0
[PATCH v3 07/13] lib: Simplify upgrade code using a transaction
Previously, the upgrade was organized as two passes -- an upgrade pass, and a separate cleanup pass -- so the database was always in a valid state. This change substantially simplifies this code by performing the upgrade in a transaction and combining both passes in to one. This 1) eliminates a lot of duplicate code between the passes, 2) speeds up the upgrade process, 3) makes progress reporting more accurate, 4) eliminates the potential for stale data if the upgrade is interrupted during the cleanup pass, and 5) makes it easier to reason about the safety of the upgrade code. --- lib/database.cc | 67 ++--- 1 file changed, 7 insertions(+), 60 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index 29a56db..faeab51 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1232,6 +1232,9 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, timer_is_active = TRUE; } +/* Perform the upgrade in a transaction. */ +db->begin_transaction (true); + /* Set the target features so we write out changes in the desired * format. */ notmuch->features |= NOTMUCH_FEATURES_CURRENT; @@ -1263,6 +1266,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, filename = _notmuch_message_talloc_copy_data (message); if (filename && *filename != '\0') { _notmuch_message_add_filename (message, filename); + _notmuch_message_clear_data (message); _notmuch_message_sync (message); } talloc_free (filename); @@ -1310,6 +1314,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, NOTMUCH_FIND_CREATE, &status); notmuch_directory_set_mtime (directory, mtime); notmuch_directory_destroy (directory); + + db->delete_document (*p); } } } @@ -1350,67 +1356,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, db->set_metadata ("features", _print_features (local, notmuch->features)); db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION)); -db->flush (); - -/* Now that the upgrade is complete we can remove the old data - * and documents that are no longer needed. */ -if (version < 1) { - notmuch_query_t *query = notmuch_query_create (notmuch, ""); - notmuch_messages_t *messages; - notmuch_message_t *message; - char *filename; - - for (messages = notmuch_query_search_messages (query); -notmuch_messages_valid (messages); -notmuch_messages_move_to_next (messages)) - { - if (do_progress_notify) { - progress_notify (closure, (double) count / total); - do_progress_notify = 0; - } - - message = notmuch_messages_get (messages); - - filename = _notmuch_message_talloc_copy_data (message); - if (filename && *filename != '\0') { - _notmuch_message_clear_data (message); - _notmuch_message_sync (message); - } - talloc_free (filename); - - notmuch_message_destroy (message); - } - notmuch_query_destroy (query); -} - -if (version < 1) { - Xapian::TermIterator t, t_end; - - t_end = notmuch->xapian_db->allterms_end ("XTIMESTAMP"); - - for (t = notmuch->xapian_db->allterms_begin ("XTIMESTAMP"); -t != t_end; -t++) - { - Xapian::PostingIterator p, p_end; - std::string term = *t; - - p_end = notmuch->xapian_db->postlist_end (term); - - for (p = notmuch->xapian_db->postlist_begin (term); -p != p_end; -p++) - { - if (do_progress_notify) { - progress_notify (closure, (double) count / total); - do_progress_notify = 0; - } - - db->delete_document (*p); - } - } -} +db->commit_transaction (); if (timer_is_active) { /* Now stop the timer. */ -- 2.0.0
[PATCH v3 06/13] test: Tests for future version and unknown feature handling
--- test/T550-db-features.sh | 48 1 file changed, 48 insertions(+) create mode 100755 test/T550-db-features.sh diff --git a/test/T550-db-features.sh b/test/T550-db-features.sh new file mode 100755 index 000..5569768 --- /dev/null +++ b/test/T550-db-features.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +test_description="database version and feature compatibility" + +. ./test-lib.sh + +test_begin_subtest "future database versions abort open" +${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} "" +output=$(notmuch search x 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/') +rm -rf ${MAIL_DIR}/.notmuch +test_expect_equal "$output" "\ +Error: Notmuch database at FILENAME + has a newer database format version () than supported by this + version of notmuch (3)." + +test_begin_subtest "unknown 'rw' feature aborts read/write open" +${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 3 $'test feature\trw' +output=$(notmuch new 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/') +rm -rf ${MAIL_DIR}/.notmuch +test_expect_equal "$output" "\ +Error: Notmuch database at FILENAME + requires features (test feature) + not supported by this version of notmuch." + +test_begin_subtest "unknown 'rw' feature aborts read-only open" +${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 3 $'test feature\trw' +output=$(notmuch search x 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/') +rm -rf ${MAIL_DIR}/.notmuch +test_expect_equal "$output" "\ +Error: Notmuch database at FILENAME + requires features (test feature) + not supported by this version of notmuch." + +test_begin_subtest "unknown 'w' feature aborts read/write open" +${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 3 $'test feature\tw' +output=$(notmuch new 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/') +rm -rf ${MAIL_DIR}/.notmuch +test_expect_equal "$output" "\ +Error: Notmuch database at FILENAME + requires features (test feature) + not supported by this version of notmuch." + +test_begin_subtest "unknown 'w' feature does not abort read-only open" +${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 3 $'test feature\tw' +output=$(notmuch search x 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/') +rm -rf ${MAIL_DIR}/.notmuch +test_expect_equal "$output" "" + +test_done -- 2.0.0
[PATCH v3 05/13] test: Tool to build DB with specific version and features
This will let us test basic version and feature handling. --- test/.gitignore | 1 + test/Makefile.local | 4 test/make-db-version.cc | 35 +++ 3 files changed, 40 insertions(+) create mode 100644 test/make-db-version.cc diff --git a/test/.gitignore b/test/.gitignore index b3b706d..0f7d5bf 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -5,5 +5,6 @@ parse-time random-corpus smtp-dummy symbol-test +make-db-version test-results tmp.* diff --git a/test/Makefile.local b/test/Makefile.local index 916dd0b..a2d58fc 100644 --- a/test/Makefile.local +++ b/test/Makefile.local @@ -35,6 +35,9 @@ $(dir)/symbol-test: $(dir)/symbol-test.o lib/$(LINKER_NAME) $(dir)/parse-time: $(dir)/parse-time.o parse-time-string/parse-time-string.o $(call quiet,CC) $^ -o $@ +$(dir)/make-db-version: $(dir)/make-db-version.o + $(call quiet,CXX) $^ -o $@ $(XAPIAN_LDFLAGS) + .PHONY: test check test_main_srcs=$(dir)/arg-test.c \ @@ -43,6 +46,7 @@ test_main_srcs=$(dir)/arg-test.c \ $(dir)/parse-time.c \ $(dir)/smtp-dummy.c \ $(dir)/symbol-test.cc \ + $(dir)/make-db-version.cc \ test_srcs=$(test_main_srcs) $(dir)/database-test.c diff --git a/test/make-db-version.cc b/test/make-db-version.cc new file mode 100644 index 000..fa80cac --- /dev/null +++ b/test/make-db-version.cc @@ -0,0 +1,35 @@ +/* Create an empty notmuch database with a specific version and + * features. */ + +#include +#include +#include +#include + +#include + +int main(int argc, char **argv) +{ +if (argc != 4) { + fprintf (stderr, "Usage: %s mailpath version features\n", argv[0]); + exit (2); +} + +std::string nmpath (argv[1]); +nmpath += "/.notmuch"; +if (mkdir (nmpath.c_str (), 0777) < 0) { + perror (("failed to create " + nmpath).c_str ()); + exit (1); +} + +try { + Xapian::WritableDatabase db ( + nmpath + "/xapian", Xapian::DB_CREATE_OR_OPEN); + db.set_metadata ("version", argv[2]); + db.set_metadata ("features", argv[3]); + db.commit (); +} catch (const Xapian::Error &e) { + fprintf (stderr, "%s\n", e.get_description ().c_str ()); + exit (1); +} +} -- 2.0.0
[PATCH v3 04/13] lib: Database version 3: Introduce fine-grained "features"
Previously, our database schema was versioned by a single number. Each database schema change had to occur "atomically" in Notmuch's development history: before some commit, Notmuch used version N, after that commit, it used version N+1. Hence, each new schema version could introduce only one change, the task of developing a schema change fell on a single person, and it all had to happen and be perfect in a single commit series. This made introducing a new schema version hard. We've seen only two schema changes in the history of Notmuch. This commit introduces database schema version 3; hopefully the last schema version we'll need for a while. With this version, we switch from a single version number to "features": a set of named, independent aspects of the database schema. Features should make backwards compatibility easier. For many things, it should be easy to support databases both with and without a feature, which will allow us to make upgrades optional and will enable "unstable" features that can be developed and tested over time. Features also make forwards compatibility easier. The features recorded in a database include "compatibility flags," which can indicate to an older version of Notmuch when it must support a given feature to open the database for read or for write. This lets us replace the old vague "I don't recognize this version, so something might go wrong, but I promise to try my best" warnings upon opening a database with an unknown version with precise errors. If a database is safe to open for read/write despite unknown features, an older version will know that and issue no message at all. If the database is not safe to open for read/write because of unknown features, an older version will know that, too, and can tell the user exactly which required features it lacks support for. --- lib/database-private.h | 57 ++- lib/database.cc| 190 - 2 files changed, 213 insertions(+), 34 deletions(-) diff --git a/lib/database-private.h b/lib/database-private.h index d3e65fd..2ffab33 100644 --- a/lib/database-private.h +++ b/lib/database-private.h @@ -41,11 +41,15 @@ struct _notmuch_database { char *path; -notmuch_bool_t needs_upgrade; notmuch_database_mode_t mode; int atomic_nesting; Xapian::Database *xapian_db; +/* Bit mask of features used by this database. Features are + * named, independent aspects of the database schema. This is a + * bitwise-OR of NOTMUCH_FEATURE_* values (below). */ +unsigned int features; + unsigned int last_doc_id; uint64_t last_thread_id; @@ -55,6 +59,57 @@ struct _notmuch_database { Xapian::ValueRangeProcessor *date_range_processor; }; +/* Bit masks for _notmuch_database::features. */ +enum { +/* If set, file names are stored in "file-direntry" terms. If + * unset, file names are stored in document data. + * + * Introduced: version 1. Implementation support: both for read; + * required for write. */ +NOTMUCH_FEATURE_FILE_TERMS = 1 << 0, + +/* If set, directory timestamps are stored in documents with + * XDIRECTORY terms and relative paths. If unset, directory + * timestamps are stored in documents with XTIMESTAMP terms and + * absolute paths. + * + * Introduced: version 1. Implementation support: required. */ +NOTMUCH_FEATURE_DIRECTORY_DOCS = 1 << 1, + +/* If set, the from, subject, and message-id headers are stored in + * message document values. If unset, message documents *may* + * have these values, but if the value is empty, it must be + * retrieved from the message file. + * + * Introduced: optional in version 1, required as of version 3. + * Implementation support: both. + */ +NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES = 1 << 2, + +/* If set, folder terms are boolean and path terms exist. If + * unset, folder terms are probabilistic and stemmed and path + * terms do not exist. + * + * Introduced: version 2. Implementation support: required. */ +NOTMUCH_FEATURE_BOOL_FOLDER = 1 << 3, +}; + +/* Prior to database version 3, features were implied by the database + * version number, so hard-code them for earlier versions. */ +#define NOTMUCH_FEATURES_V0 (0) +#define NOTMUCH_FEATURES_V1 (NOTMUCH_FEATURES_V0 | NOTMUCH_FEATURE_FILE_TERMS | \ +NOTMUCH_FEATURE_DIRECTORY_DOCS) +#define NOTMUCH_FEATURES_V2 (NOTMUCH_FEATURES_V1 | NOTMUCH_FEATURE_BOOL_FOLDER) + +/* Current database features. If any of these are missing from a + * database, request an upgrade. + * NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES is not included because + * upgrade doesn't currently introduce the feature (though brand new + * databases will have it). */ +#define NOTMUCH_FEATURES_CURRENT \ +(NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_DIRECTORY_DOCS | \ + NOTMUCH_FEATURE_BOOL_FOLDER) + /* Return the list of te
[PATCH v3 03/13] new: Don't report version after upgrade
The version number has always been pretty meaningless to the user and it's about to become even more meaningless with the introduction of "features". Hopefully, the database will remain on version 3 for some time to come; however, the introduction of new features over time in version 3 will necessitate upgrades within version 3. It would be confusing if we always tell the user they've been "upgraded to version 3". If the user wants to know what's new, they should read the news. --- notmuch-new.c| 3 +-- test/T530-upgrade.sh | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/notmuch-new.c b/notmuch-new.c index d269c7c..b7590a8 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -1023,8 +1023,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]) add_files_state.verbosity >= VERBOSITY_NORMAL ? upgrade_print_progress : NULL, &add_files_state); if (add_files_state.verbosity >= VERBOSITY_NORMAL) - printf ("Your notmuch database has now been upgraded to database format version %u.\n", - notmuch_database_get_version (notmuch)); + printf ("Your notmuch database has now been upgraded.\n"); } add_files_state.total_files = 0; diff --git a/test/T530-upgrade.sh b/test/T530-upgrade.sh index 7d5d5aa..c4c4ac8 100755 --- a/test/T530-upgrade.sh +++ b/test/T530-upgrade.sh @@ -33,7 +33,7 @@ test_expect_equal "$output" "\ Welcome to a new version of notmuch! Your database will now be upgraded. This process is safe to interrupt. Backing up tags to FILENAME -Your notmuch database has now been upgraded to database format version 2. +Your notmuch database has now been upgraded. No new mail." test_begin_subtest "tag backup matches pre-upgrade dump" -- 2.0.0
[PATCH v3 02/13] util: Const version of strtok_len
Because of limitations in the C type system, we can't a strtok_len that can work on both const string and non-const strings. The C library solves this by taking a const char* and returning a char* in functions like this (e.g., strchr), but that's not const-safe. Solve it by introducing strtok_len_c, a version of strtok_len for const strings. --- util/string-util.c | 8 util/string-util.h | 3 +++ 2 files changed, 11 insertions(+) diff --git a/util/string-util.c b/util/string-util.c index 3e7066c..a90501e 100644 --- a/util/string-util.c +++ b/util/string-util.c @@ -37,6 +37,14 @@ strtok_len (char *s, const char *delim, size_t *len) return *len ? s : NULL; } +const char * +strtok_len_c (const char *s, const char *delim, size_t *len) +{ +/* strtok_len is already const-safe, but we can't express both + * versions in the C type system. */ +return strtok_len ((char*)s, delim, len); +} + char * sanitize_string (const void *ctx, const char *str) { diff --git a/util/string-util.h b/util/string-util.h index ccad17f..e409cb3 100644 --- a/util/string-util.h +++ b/util/string-util.h @@ -23,6 +23,9 @@ extern "C" { char *strtok_len (char *s, const char *delim, size_t *len); +/* Const version of strtok_len. */ +const char *strtok_len_c (const char *s, const char *delim, size_t *len); + /* Return a talloced string with str sanitized. * * Whitespace characters (tabs and newlines) are replaced with spaces, -- 2.0.0
[PATCH v3 01/13] test: Include generated dependencies for test sources
Previously the build system was generating automatic header dependencies for test sources, but only smtp-dummy was in SRCS, so only its dependencies were being included. Add all of the test sources to SRCS so that the root Makefile.local includes their dependencies. --- test/Makefile.local | 19 --- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/test/Makefile.local b/test/Makefile.local index 1c85b18..916dd0b 100644 --- a/test/Makefile.local +++ b/test/Makefile.local @@ -37,12 +37,17 @@ $(dir)/parse-time: $(dir)/parse-time.o parse-time-string/parse-time-string.o .PHONY: test check -TEST_BINARIES=$(dir)/arg-test \ - $(dir)/hex-xcode \ - $(dir)/random-corpus \ - $(dir)/parse-time \ - $(dir)/smtp-dummy \ - $(dir)/symbol-test +test_main_srcs=$(dir)/arg-test.c \ + $(dir)/hex-xcode.c \ + $(dir)/random-corpus.c \ + $(dir)/parse-time.c \ + $(dir)/smtp-dummy.c \ + $(dir)/symbol-test.cc \ + +test_srcs=$(test_main_srcs) $(dir)/database-test.c + +TEST_BINARIES := $(test_main_srcs:.c=) +TEST_BINARIES := $(TEST_BINARIES:.cc=) test-binaries: $(TEST_BINARIES) @@ -51,7 +56,7 @@ test: all test-binaries check: test -SRCS := $(SRCS) $(smtp_dummy_srcs) +SRCS := $(SRCS) $(test_srcs) CLEAN += $(TEST_BINARIES) $(addsuffix .o,$(TEST_BINARIES)) \ $(dir)/database-test.o \ $(dir)/corpus.mail $(dir)/test-results $(dir)/tmp.* -- 2.0.0
[PATCH v3 00/13] Implement and use database "features"
This is v3 of id:1406652492-27803-1-git-send-email-amdragon at mit.edu. This fixes one issue and tidies up another thing in notmuch_database_upgrade I found while working on change tracking support. Most of the patches are logically identical to v2, but the changes ripple through the patch context, so I'm sending a new series. First, this updates notmuch->features before starting the upgrade, rather than after, so that functions called by upgrade will use the new database features instead of the old (this didn't matter in this series because nothing modified the database differently depending on features). Second, this combines multiple _notmuch_message_sync calls into one, which cleans up the code, should further improve upgrade performance, and makes way for additional per-message upgrades. The diff from v2 is below (excluding patch 2, which David pushed). diff --git a/lib/database.cc b/lib/database.cc index b323691..d90a924 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1252,6 +1252,10 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, /* Perform the upgrade in a transaction. */ db->begin_transaction (true); +/* Set the target features so we write out changes in the desired + * format. */ +notmuch->features = target_features; + /* Perform per-message upgrades. */ if (new_features & (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER)) { @@ -1280,7 +1284,6 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, if (filename && *filename != '\0') { _notmuch_message_add_filename (message, filename); _notmuch_message_clear_data (message); - _notmuch_message_sync (message); } talloc_free (filename); } @@ -1289,10 +1292,10 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, * probabilistic and stemmed. Change it to the current * boolean prefix. Add "path:" prefixes while at it. */ - if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER) { + if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER) _notmuch_message_upgrade_folder (message); - _notmuch_message_sync (message); - } + + _notmuch_message_sync (message); notmuch_message_destroy (message); @@ -1348,7 +1351,6 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, } } -notmuch->features = target_features; db->set_metadata ("features", _print_features (local, notmuch->features)); db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION));
[PATCH RFC] Emacs: Add address completion mechanism implemented in elisp
Michal Sojka writes: > notmuch-company.el hooks itself into message-mode and uses > company-mode to offer the completion to the user. The file is put into > the contrib directory which means that the use has to install it > himself. This is because company-mode is not a part of Emacs and > bytecompiling notmuch-company.el fails due to used --quick option that > causes user installed packages to be ignored. what about for now just putting ;; -*-no-byte-compile: t; -*- at the top of the file? It seems usable uncompiled to me. Then somebody who really wants it compiled can figure out how to do the conditional compilation. > It would probably make sense to implement another completion frontend > based only on Emacs built-in functionality and integrate it with > notmuch-addresses.el. Agreed. > + "`company-mode' completion back-end for `nevermore (nm)'." missed "nevermore" ;) > > +(defun notmuch-flatten-thread-set (thread-set) > + "Convert the result of 'notmuch show' to the plain list of messages." > +(defun notmuch-flatten-thread (thread) > + > +(defun notmuch-flatten-thread-node (thread-node) what about putting those functions in notmuch-query.el? > +(defun notmuch-async-harvest () > + "Collect possible addresses for completion. It queries the logically it seems like this might belong in notmuch-address.el, even if none of the functions in in it currently use d
[PATCH] lib: Invalidate message metadata in _notmuch_message_gen_terms
Previously, we invalidated stored message metadata in _notmuch_message_add_term and _notmuch_message_remove_term, but not in _notmuch_message_gen_terms. This doesn't currently result in any bugs because of our limited uses of _notmuch_message_gen_terms, but it may could cause trouble in the future. --- lib/message.cc | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/message.cc b/lib/message.cc index 1618e81..cde6a1c 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -1049,6 +1049,8 @@ _notmuch_message_gen_terms (notmuch_message_t *message, /* Create a gap between this an the next terms so they don't * appear to be a phrase. */ message->termpos = term_gen->get_termpos () + 100; + + _notmuch_message_invalidate_metadata (message, prefix_name); } term_gen->set_termpos (message->termpos); -- 2.0.0 ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
[PATCH] lib: Fix slight misinformation in the database schema doc
The database schema documentation made it sound like each mail document had exactly one on-disk message file, which hasn't been true for a long time. --- lib/database.cc | 9 ++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index d90a924..c70ce63 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -55,9 +55,12 @@ typedef struct { * * Mail document * - - * A mail document is associated with a particular email message file - * on disk. It is indexed with the following prefixed terms which the - * database uses to construct threads, etc.: + * A mail document is associated with a particular email message. It + * is stored in one or more files on disk (though only one has its + * content indexed) and is uniquely identified by its "id" field + * (which is generally the message ID). It is indexed with the + * following prefixed terms which the database uses to construct + * threads, etc.: * *Single terms of given prefix: * -- 2.0.0 ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
Custom Tag Images
Hello Everyone. If this is not the place to address this issue, could you please direct me to the proper forum. Info: Using Emacs 24.3.1 on ArchLinux I am interested in setting up custom image for some of the tags I use. I have tried to use a svg file by setting it in the following ways: (defun notmuch-tag-list-icon () "Return SVG data representing a list. This can be used with `notmuch-tag-format-image-data'." " http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\";> http://www.w3.org/2000/svg\"; xmlns:xlink=\"http://www.w3.org/1999/xlink\"; width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"> ") and setting it like I do the default images. e.g. (setq notmuch-tag-formats '( ("flagged" (notmuch-tag-format-image-data tag (notmuch-tag-star-icon))) ("lists" (notmuch-tag-format-image-data tag (notmuch-tag-list-icon))) )) I have also tried via the customize image setting and using the path to the file. Both ways produce nothing but a colored rectangle. I have downloaded the file in question from http://IcoMoon.io Am I missing something obvious here, or is this svg perhaps formatted incorrectly for notmuch emacs? Any pointers in the right direction are greatly appreciated. Thanks.
Custom Tag Images
Hello Everyone. If this is not the place to address this issue, could you please direct me to the proper forum. Info: Using Emacs 24.3.1 on ArchLinux I am interested in setting up custom image for some of the tags I use. I have tried to use a svg file by setting it in the following ways: (defun notmuch-tag-list-icon () "Return SVG data representing a list. This can be used with `notmuch-tag-format-image-data'." " http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\";> http://www.w3.org/2000/svg\"; xmlns:xlink=\"http://www.w3.org/1999/xlink\"; width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"> ") and setting it like I do the default images. e.g. (setq notmuch-tag-formats '( ("flagged" (notmuch-tag-format-image-data tag (notmuch-tag-star-icon))) ("lists" (notmuch-tag-format-image-data tag (notmuch-tag-list-icon))) )) I have also tried via the customize image setting and using the path to the file. Both ways produce nothing but a colored rectangle. I have downloaded the file in question from http://IcoMoon.io Am I missing something obvious here, or is this svg perhaps formatted incorrectly for notmuch emacs? Any pointers in the right direction are greatly appreciated. Thanks.
[PATCH v3 13/13] lib: Update doc of notmuch_database_{needs_upgrade, upgrade}
Clients are no longer required to call these functions after opening a database in read/write mode (which is good, because almost none of them do!). --- lib/notmuch.h | 21 - 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/notmuch.h b/lib/notmuch.h index cbf2ba5..d771eb2 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -352,22 +352,25 @@ unsigned int notmuch_database_get_version (notmuch_database_t *database); /** - * Does this database need to be upgraded before writing to it? + * Is the database behind the latest supported database version? * - * If this function returns TRUE then no functions that modify the - * database (notmuch_database_add_message, notmuch_message_add_tag, - * notmuch_directory_set_mtime, etc.) will work unless the function - * notmuch_database_upgrade is called successfully first. + * If this function returns TRUE, then the caller may call + * notmuch_database_upgrade to upgrade the database. If the caller + * does not upgrade an out-of-date database, then some functions may + * fail with NOTMUCH_STATUS_UPGRADE_REQUIRED. */ notmuch_bool_t notmuch_database_needs_upgrade (notmuch_database_t *database); /** - * Upgrade the current database. + * Upgrade the current database to the latest supported version. * - * After opening a database in read-write mode, the client should - * check if an upgrade is needed (notmuch_database_needs_upgrade) and - * if so, upgrade with this function before making any modifications. + * This ensures that all current notmuch functionality will be + * available on the database. After opening a database in read-write + * mode, it is recommended that clients check if an upgrade is needed + * (notmuch_database_needs_upgrade) and if so, upgrade with this + * function before making any modifications. If + * notmuch_database_needs_upgrade returns FALSE, this will be a no-op. * * The optional progress_notify callback can be used by the caller to * provide progress indication to the user. If non-NULL it will be -- 2.0.0 ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
[PATCH v3 04/13] lib: Database version 3: Introduce fine-grained "features"
Previously, our database schema was versioned by a single number. Each database schema change had to occur "atomically" in Notmuch's development history: before some commit, Notmuch used version N, after that commit, it used version N+1. Hence, each new schema version could introduce only one change, the task of developing a schema change fell on a single person, and it all had to happen and be perfect in a single commit series. This made introducing a new schema version hard. We've seen only two schema changes in the history of Notmuch. This commit introduces database schema version 3; hopefully the last schema version we'll need for a while. With this version, we switch from a single version number to "features": a set of named, independent aspects of the database schema. Features should make backwards compatibility easier. For many things, it should be easy to support databases both with and without a feature, which will allow us to make upgrades optional and will enable "unstable" features that can be developed and tested over time. Features also make forwards compatibility easier. The features recorded in a database include "compatibility flags," which can indicate to an older version of Notmuch when it must support a given feature to open the database for read or for write. This lets us replace the old vague "I don't recognize this version, so something might go wrong, but I promise to try my best" warnings upon opening a database with an unknown version with precise errors. If a database is safe to open for read/write despite unknown features, an older version will know that and issue no message at all. If the database is not safe to open for read/write because of unknown features, an older version will know that, too, and can tell the user exactly which required features it lacks support for. --- lib/database-private.h | 57 ++- lib/database.cc| 190 - 2 files changed, 213 insertions(+), 34 deletions(-) diff --git a/lib/database-private.h b/lib/database-private.h index d3e65fd..2ffab33 100644 --- a/lib/database-private.h +++ b/lib/database-private.h @@ -41,11 +41,15 @@ struct _notmuch_database { char *path; -notmuch_bool_t needs_upgrade; notmuch_database_mode_t mode; int atomic_nesting; Xapian::Database *xapian_db; +/* Bit mask of features used by this database. Features are + * named, independent aspects of the database schema. This is a + * bitwise-OR of NOTMUCH_FEATURE_* values (below). */ +unsigned int features; + unsigned int last_doc_id; uint64_t last_thread_id; @@ -55,6 +59,57 @@ struct _notmuch_database { Xapian::ValueRangeProcessor *date_range_processor; }; +/* Bit masks for _notmuch_database::features. */ +enum { +/* If set, file names are stored in "file-direntry" terms. If + * unset, file names are stored in document data. + * + * Introduced: version 1. Implementation support: both for read; + * required for write. */ +NOTMUCH_FEATURE_FILE_TERMS = 1 << 0, + +/* If set, directory timestamps are stored in documents with + * XDIRECTORY terms and relative paths. If unset, directory + * timestamps are stored in documents with XTIMESTAMP terms and + * absolute paths. + * + * Introduced: version 1. Implementation support: required. */ +NOTMUCH_FEATURE_DIRECTORY_DOCS = 1 << 1, + +/* If set, the from, subject, and message-id headers are stored in + * message document values. If unset, message documents *may* + * have these values, but if the value is empty, it must be + * retrieved from the message file. + * + * Introduced: optional in version 1, required as of version 3. + * Implementation support: both. + */ +NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES = 1 << 2, + +/* If set, folder terms are boolean and path terms exist. If + * unset, folder terms are probabilistic and stemmed and path + * terms do not exist. + * + * Introduced: version 2. Implementation support: required. */ +NOTMUCH_FEATURE_BOOL_FOLDER = 1 << 3, +}; + +/* Prior to database version 3, features were implied by the database + * version number, so hard-code them for earlier versions. */ +#define NOTMUCH_FEATURES_V0 (0) +#define NOTMUCH_FEATURES_V1 (NOTMUCH_FEATURES_V0 | NOTMUCH_FEATURE_FILE_TERMS | \ +NOTMUCH_FEATURE_DIRECTORY_DOCS) +#define NOTMUCH_FEATURES_V2 (NOTMUCH_FEATURES_V1 | NOTMUCH_FEATURE_BOOL_FOLDER) + +/* Current database features. If any of these are missing from a + * database, request an upgrade. + * NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES is not included because + * upgrade doesn't currently introduce the feature (though brand new + * databases will have it). */ +#define NOTMUCH_FEATURES_CURRENT \ +(NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_DIRECTORY_DOCS | \ + NOTMUCH_FEATURE_BOOL_FOLDER) + /* Return the list
[PATCH v3 10/13] lib: Report progress for combined upgrade operation
Previously, some parts of upgrade didn't report progress and for others it was possible for the progress meter to restart at 0 part way through the upgrade because each stage was reported separately. Fix this by computing the total amount of work that needs to be done up-front and updating completed work monotonically. --- lib/database.cc | 17 +++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index 31e6a93..04b3790 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1234,6 +1234,19 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, timer_is_active = TRUE; } +/* Figure out how much total work we need to do. */ +if (new_features & + (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER)) { + notmuch_query_t *query = notmuch_query_create (notmuch, ""); + total += notmuch_query_count_messages (query); + notmuch_query_destroy (query); +} +if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) { + t_end = db->allterms_end ("XTIMESTAMP"); + for (t = db->allterms_begin ("XTIMESTAMP"); t != t_end; t++) + ++total; +} + /* Perform the upgrade in a transaction. */ db->begin_transaction (true); @@ -1249,8 +1262,6 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, notmuch_message_t *message; char *filename; - total = notmuch_query_count_messages (query); - for (messages = notmuch_query_search_messages (query); notmuch_messages_valid (messages); notmuch_messages_move_to_next (messages)) @@ -1333,6 +1344,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, db->delete_document (*p); } + + ++count; } } -- 2.0.0 ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
[PATCH v3 08/13] lib: Use database features to drive upgrade
Previously, we had database version information hard-coded in the upgrade code. Slightly re-organize the upgrade process around the set of new database features to be enabled by the upgrade. --- lib/database.cc | 24 +--- 1 file changed, 13 insertions(+), 11 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index faeab51..69d775f 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1195,11 +1195,12 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, void *closure) { void *local = talloc_new (NULL); +Xapian::TermIterator t, t_end; Xapian::WritableDatabase *db; struct sigaction action; struct itimerval timerval; notmuch_bool_t timer_is_active = FALSE; -unsigned int version; +unsigned int target_features, new_features; notmuch_status_t status; unsigned int count = 0, total = 0; @@ -1209,9 +1210,10 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, db = static_cast (notmuch->xapian_db); -version = notmuch_database_get_version (notmuch); +target_features = notmuch->features | NOTMUCH_FEATURES_CURRENT; +new_features = NOTMUCH_FEATURES_CURRENT & ~notmuch->features; -if (version >= NOTMUCH_DATABASE_VERSION) +if (! new_features) return NOTMUCH_STATUS_SUCCESS; if (progress_notify) { @@ -1237,18 +1239,17 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, /* Set the target features so we write out changes in the desired * format. */ -notmuch->features |= NOTMUCH_FEATURES_CURRENT; +notmuch->features = target_features; /* Before version 1, each message document had its filename in the * data field. Copy that into the new format by calling * notmuch_message_add_filename. */ -if (version < 1) { +if (new_features & NOTMUCH_FEATURE_FILE_TERMS) { notmuch_query_t *query = notmuch_query_create (notmuch, ""); notmuch_messages_t *messages; notmuch_message_t *message; char *filename; - Xapian::TermIterator t, t_end; total = notmuch_query_count_messages (query); @@ -1277,11 +1278,12 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, } notmuch_query_destroy (query); +} - /* Also, before version 1 we stored directory timestamps in -* XTIMESTAMP documents instead of the current XDIRECTORY -* documents. So copy those as well. */ - +/* Also, before version 1 we stored directory timestamps in + * XTIMESTAMP documents instead of the current XDIRECTORY + * documents. So copy those as well. */ +if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) { t_end = notmuch->xapian_db->allterms_end ("XTIMESTAMP"); for (t = notmuch->xapian_db->allterms_begin ("XTIMESTAMP"); @@ -1325,7 +1327,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, * stemmed. Change it to the current boolean prefix. Add "path:" * prefixes while at it. */ -if (version < 2) { +if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER) { notmuch_query_t *query = notmuch_query_create (notmuch, ""); notmuch_messages_t *messages; notmuch_message_t *message; -- 2.0.0 ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
[PATCH v3 07/13] lib: Simplify upgrade code using a transaction
Previously, the upgrade was organized as two passes -- an upgrade pass, and a separate cleanup pass -- so the database was always in a valid state. This change substantially simplifies this code by performing the upgrade in a transaction and combining both passes in to one. This 1) eliminates a lot of duplicate code between the passes, 2) speeds up the upgrade process, 3) makes progress reporting more accurate, 4) eliminates the potential for stale data if the upgrade is interrupted during the cleanup pass, and 5) makes it easier to reason about the safety of the upgrade code. --- lib/database.cc | 67 ++--- 1 file changed, 7 insertions(+), 60 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index 29a56db..faeab51 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1232,6 +1232,9 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, timer_is_active = TRUE; } +/* Perform the upgrade in a transaction. */ +db->begin_transaction (true); + /* Set the target features so we write out changes in the desired * format. */ notmuch->features |= NOTMUCH_FEATURES_CURRENT; @@ -1263,6 +1266,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, filename = _notmuch_message_talloc_copy_data (message); if (filename && *filename != '\0') { _notmuch_message_add_filename (message, filename); + _notmuch_message_clear_data (message); _notmuch_message_sync (message); } talloc_free (filename); @@ -1310,6 +1314,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, NOTMUCH_FIND_CREATE, &status); notmuch_directory_set_mtime (directory, mtime); notmuch_directory_destroy (directory); + + db->delete_document (*p); } } } @@ -1350,67 +1356,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, db->set_metadata ("features", _print_features (local, notmuch->features)); db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION)); -db->flush (); - -/* Now that the upgrade is complete we can remove the old data - * and documents that are no longer needed. */ -if (version < 1) { - notmuch_query_t *query = notmuch_query_create (notmuch, ""); - notmuch_messages_t *messages; - notmuch_message_t *message; - char *filename; - - for (messages = notmuch_query_search_messages (query); -notmuch_messages_valid (messages); -notmuch_messages_move_to_next (messages)) - { - if (do_progress_notify) { - progress_notify (closure, (double) count / total); - do_progress_notify = 0; - } - - message = notmuch_messages_get (messages); - - filename = _notmuch_message_talloc_copy_data (message); - if (filename && *filename != '\0') { - _notmuch_message_clear_data (message); - _notmuch_message_sync (message); - } - talloc_free (filename); - - notmuch_message_destroy (message); - } - notmuch_query_destroy (query); -} - -if (version < 1) { - Xapian::TermIterator t, t_end; - - t_end = notmuch->xapian_db->allterms_end ("XTIMESTAMP"); - - for (t = notmuch->xapian_db->allterms_begin ("XTIMESTAMP"); -t != t_end; -t++) - { - Xapian::PostingIterator p, p_end; - std::string term = *t; - - p_end = notmuch->xapian_db->postlist_end (term); - - for (p = notmuch->xapian_db->postlist_begin (term); -p != p_end; -p++) - { - if (do_progress_notify) { - progress_notify (closure, (double) count / total); - do_progress_notify = 0; - } - - db->delete_document (*p); - } - } -} +db->commit_transaction (); if (timer_is_active) { /* Now stop the timer. */ -- 2.0.0 ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
[PATCH v3 06/13] test: Tests for future version and unknown feature handling
--- test/T550-db-features.sh | 48 1 file changed, 48 insertions(+) create mode 100755 test/T550-db-features.sh diff --git a/test/T550-db-features.sh b/test/T550-db-features.sh new file mode 100755 index 000..5569768 --- /dev/null +++ b/test/T550-db-features.sh @@ -0,0 +1,48 @@ +#!/usr/bin/env bash +test_description="database version and feature compatibility" + +. ./test-lib.sh + +test_begin_subtest "future database versions abort open" +${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} "" +output=$(notmuch search x 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/') +rm -rf ${MAIL_DIR}/.notmuch +test_expect_equal "$output" "\ +Error: Notmuch database at FILENAME + has a newer database format version () than supported by this + version of notmuch (3)." + +test_begin_subtest "unknown 'rw' feature aborts read/write open" +${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 3 $'test feature\trw' +output=$(notmuch new 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/') +rm -rf ${MAIL_DIR}/.notmuch +test_expect_equal "$output" "\ +Error: Notmuch database at FILENAME + requires features (test feature) + not supported by this version of notmuch." + +test_begin_subtest "unknown 'rw' feature aborts read-only open" +${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 3 $'test feature\trw' +output=$(notmuch search x 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/') +rm -rf ${MAIL_DIR}/.notmuch +test_expect_equal "$output" "\ +Error: Notmuch database at FILENAME + requires features (test feature) + not supported by this version of notmuch." + +test_begin_subtest "unknown 'w' feature aborts read/write open" +${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 3 $'test feature\tw' +output=$(notmuch new 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/') +rm -rf ${MAIL_DIR}/.notmuch +test_expect_equal "$output" "\ +Error: Notmuch database at FILENAME + requires features (test feature) + not supported by this version of notmuch." + +test_begin_subtest "unknown 'w' feature does not abort read-only open" +${TEST_DIRECTORY}/make-db-version ${MAIL_DIR} 3 $'test feature\tw' +output=$(notmuch search x 2>&1 | sed 's/\(database at\) .*/\1 FILENAME/') +rm -rf ${MAIL_DIR}/.notmuch +test_expect_equal "$output" "" + +test_done -- 2.0.0 ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
[PATCH v3 11/13] lib: Support empty header values in database
Commit 567bcbc2 introduced support for storing various headers in document values. However, doing so in a backwards-compatible way meant that genuinely empty header values could not be distinguished from the old behavior of not storing the headers at all, so these required parsing the original message. Now that we have database features, new databases can declare that all messages have header values, so if we have this feature flag, we can use the stored header value even if it's the empty string. This requires slight cleanup to notmuch_message_get_header, since the code previously couldn't distinguish between empty headers and headers that are never stored in the database (previously this distinction didn't matter). --- lib/message.cc | 45 +++-- 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/lib/message.cc b/lib/message.cc index e6a5a5a..4fc427f 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -412,26 +412,35 @@ _notmuch_message_ensure_message_file (notmuch_message_t *message) const char * notmuch_message_get_header (notmuch_message_t *message, const char *header) { -try { - std::string value; - - /* Fetch header from the appropriate xapian value field if -* available */ - if (strcasecmp (header, "from") == 0) - value = message->doc.get_value (NOTMUCH_VALUE_FROM); - else if (strcasecmp (header, "subject") == 0) - value = message->doc.get_value (NOTMUCH_VALUE_SUBJECT); - else if (strcasecmp (header, "message-id") == 0) - value = message->doc.get_value (NOTMUCH_VALUE_MESSAGE_ID); - - if (!value.empty()) +Xapian::valueno slot = Xapian::BAD_VALUENO; + +/* Fetch header from the appropriate xapian value field if + * available */ +if (strcasecmp (header, "from") == 0) + slot = NOTMUCH_VALUE_FROM; +else if (strcasecmp (header, "subject") == 0) + slot = NOTMUCH_VALUE_SUBJECT; +else if (strcasecmp (header, "message-id") == 0) + slot = NOTMUCH_VALUE_MESSAGE_ID; + +if (slot != Xapian::BAD_VALUENO) { + try { + std::string value = message->doc.get_value (slot); + + /* If we have NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES, then +* empty values indicate empty headers. If we don't, then +* it could just mean we didn't record the header. */ + if ((message->notmuch->features & +NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES) || + ! value.empty()) return talloc_strdup (message, value.c_str ()); -} catch (Xapian::Error &error) { - fprintf (stderr, "A Xapian exception occurred when reading header: %s\n", -error.get_msg().c_str()); - message->notmuch->exception_reported = TRUE; - return NULL; + } catch (Xapian::Error &error) { + fprintf (stderr, "A Xapian exception occurred when reading header: %s\n", +error.get_msg().c_str()); + message->notmuch->exception_reported = TRUE; + return NULL; + } } /* Otherwise fall back to parsing the file */ -- 2.0.0 ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
[PATCH v3 03/13] new: Don't report version after upgrade
The version number has always been pretty meaningless to the user and it's about to become even more meaningless with the introduction of "features". Hopefully, the database will remain on version 3 for some time to come; however, the introduction of new features over time in version 3 will necessitate upgrades within version 3. It would be confusing if we always tell the user they've been "upgraded to version 3". If the user wants to know what's new, they should read the news. --- notmuch-new.c| 3 +-- test/T530-upgrade.sh | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/notmuch-new.c b/notmuch-new.c index d269c7c..b7590a8 100644 --- a/notmuch-new.c +++ b/notmuch-new.c @@ -1023,8 +1023,7 @@ notmuch_new_command (notmuch_config_t *config, int argc, char *argv[]) add_files_state.verbosity >= VERBOSITY_NORMAL ? upgrade_print_progress : NULL, &add_files_state); if (add_files_state.verbosity >= VERBOSITY_NORMAL) - printf ("Your notmuch database has now been upgraded to database format version %u.\n", - notmuch_database_get_version (notmuch)); + printf ("Your notmuch database has now been upgraded.\n"); } add_files_state.total_files = 0; diff --git a/test/T530-upgrade.sh b/test/T530-upgrade.sh index 7d5d5aa..c4c4ac8 100755 --- a/test/T530-upgrade.sh +++ b/test/T530-upgrade.sh @@ -33,7 +33,7 @@ test_expect_equal "$output" "\ Welcome to a new version of notmuch! Your database will now be upgraded. This process is safe to interrupt. Backing up tags to FILENAME -Your notmuch database has now been upgraded to database format version 2. +Your notmuch database has now been upgraded. No new mail." test_begin_subtest "tag backup matches pre-upgrade dump" -- 2.0.0 ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
[PATCH v3 05/13] test: Tool to build DB with specific version and features
This will let us test basic version and feature handling. --- test/.gitignore | 1 + test/Makefile.local | 4 test/make-db-version.cc | 35 +++ 3 files changed, 40 insertions(+) create mode 100644 test/make-db-version.cc diff --git a/test/.gitignore b/test/.gitignore index b3b706d..0f7d5bf 100644 --- a/test/.gitignore +++ b/test/.gitignore @@ -5,5 +5,6 @@ parse-time random-corpus smtp-dummy symbol-test +make-db-version test-results tmp.* diff --git a/test/Makefile.local b/test/Makefile.local index 916dd0b..a2d58fc 100644 --- a/test/Makefile.local +++ b/test/Makefile.local @@ -35,6 +35,9 @@ $(dir)/symbol-test: $(dir)/symbol-test.o lib/$(LINKER_NAME) $(dir)/parse-time: $(dir)/parse-time.o parse-time-string/parse-time-string.o $(call quiet,CC) $^ -o $@ +$(dir)/make-db-version: $(dir)/make-db-version.o + $(call quiet,CXX) $^ -o $@ $(XAPIAN_LDFLAGS) + .PHONY: test check test_main_srcs=$(dir)/arg-test.c \ @@ -43,6 +46,7 @@ test_main_srcs=$(dir)/arg-test.c \ $(dir)/parse-time.c \ $(dir)/smtp-dummy.c \ $(dir)/symbol-test.cc \ + $(dir)/make-db-version.cc \ test_srcs=$(test_main_srcs) $(dir)/database-test.c diff --git a/test/make-db-version.cc b/test/make-db-version.cc new file mode 100644 index 000..fa80cac --- /dev/null +++ b/test/make-db-version.cc @@ -0,0 +1,35 @@ +/* Create an empty notmuch database with a specific version and + * features. */ + +#include +#include +#include +#include + +#include + +int main(int argc, char **argv) +{ +if (argc != 4) { + fprintf (stderr, "Usage: %s mailpath version features\n", argv[0]); + exit (2); +} + +std::string nmpath (argv[1]); +nmpath += "/.notmuch"; +if (mkdir (nmpath.c_str (), 0777) < 0) { + perror (("failed to create " + nmpath).c_str ()); + exit (1); +} + +try { + Xapian::WritableDatabase db ( + nmpath + "/xapian", Xapian::DB_CREATE_OR_OPEN); + db.set_metadata ("version", argv[2]); + db.set_metadata ("features", argv[3]); + db.commit (); +} catch (const Xapian::Error &e) { + fprintf (stderr, "%s\n", e.get_description ().c_str ()); + exit (1); +} +} -- 2.0.0 ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
[PATCH v3 12/13] lib: Return an error from operations that require an upgrade
Previously, there was no protection against a caller invoking an operation on an old database version that would effectively corrupt the database by treating it like a newer version. According to notmuch.h, any caller that opens the database in read/write mode is supposed to check if the database needs upgrading and perform an upgrade if it does. This would protect against this, but nobody (even the CLI) actually does this. However, with features, it's easy to protect against incompatible operations on a fine-grained basis. This lightweight change allows callers to safely operate on old database versions, while preventing specific operations that would corrupt the database with an informative error message. --- lib/database.cc | 5 + lib/directory.cc | 5 + lib/message.cc | 8 lib/notmuch.h| 16 4 files changed, 34 insertions(+) diff --git a/lib/database.cc b/lib/database.cc index 04b3790..d90a924 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -310,6 +310,8 @@ notmuch_status_to_string (notmuch_status_t status) return "Unbalanced number of calls to notmuch_database_begin_atomic/end_atomic"; case NOTMUCH_STATUS_UNSUPPORTED_OPERATION: return "Unsupported operation"; +case NOTMUCH_STATUS_UPGRADE_REQUIRED: + return "Operation requires a database upgrade"; default: case NOTMUCH_STATUS_LAST_STATUS: return "Unknown error status value"; @@ -2219,6 +2221,9 @@ notmuch_database_find_message_by_filename (notmuch_database_t *notmuch, if (message_ret == NULL) return NOTMUCH_STATUS_NULL_POINTER; +if (! (notmuch->features & NOTMUCH_FEATURE_FILE_TERMS)) + return NOTMUCH_STATUS_UPGRADE_REQUIRED; + /* return NULL on any failure */ *message_ret = NULL; diff --git a/lib/directory.cc b/lib/directory.cc index 6a3ffed..8daaec8 100644 --- a/lib/directory.cc +++ b/lib/directory.cc @@ -105,6 +105,11 @@ _notmuch_directory_create (notmuch_database_t *notmuch, const char *db_path; notmuch_bool_t create = (flags & NOTMUCH_FIND_CREATE); +if (! (notmuch->features & NOTMUCH_FEATURE_DIRECTORY_DOCS)) { + *status_ret = NOTMUCH_STATUS_UPGRADE_REQUIRED; + return NULL; +} + *status_ret = NOTMUCH_STATUS_SUCCESS; path = _notmuch_database_relative_path (notmuch, path); diff --git a/lib/message.cc b/lib/message.cc index 4fc427f..1618e81 100644 --- a/lib/message.cc +++ b/lib/message.cc @@ -653,6 +653,10 @@ _notmuch_message_add_filename (notmuch_message_t *message, if (filename == NULL) INTERNAL_ERROR ("Message filename cannot be NULL."); +if (! (message->notmuch->features & NOTMUCH_FEATURE_FILE_TERMS) || + ! (message->notmuch->features & NOTMUCH_FEATURE_BOOL_FOLDER)) + return NOTMUCH_STATUS_UPGRADE_REQUIRED; + relative = _notmuch_database_relative_path (message->notmuch, filename); status = _notmuch_database_split_path (local, relative, &directory, NULL); @@ -697,6 +701,10 @@ _notmuch_message_remove_filename (notmuch_message_t *message, notmuch_private_status_t private_status; notmuch_status_t status; +if (! (message->notmuch->features & NOTMUCH_FEATURE_FILE_TERMS) || + ! (message->notmuch->features & NOTMUCH_FEATURE_BOOL_FOLDER)) + return NOTMUCH_STATUS_UPGRADE_REQUIRED; + status = _notmuch_database_filename_to_direntry ( local, message->notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry); if (status || !direntry) diff --git a/lib/notmuch.h b/lib/notmuch.h index 3c5ec98..cbf2ba5 100644 --- a/lib/notmuch.h +++ b/lib/notmuch.h @@ -160,6 +160,10 @@ typedef enum _notmuch_status { */ NOTMUCH_STATUS_UNSUPPORTED_OPERATION, /** + * The operation requires a database upgrade. + */ +NOTMUCH_STATUS_UPGRADE_REQUIRED, +/** * Not an actual status value. Just a way to find out how many * valid status values there are. */ @@ -438,6 +442,9 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch); * * NOTMUCH_STATUS_XAPIAN_EXCEPTION: A Xapian exception occurred; * directory not retrieved. + * + * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the + * database to use this function. */ notmuch_status_t notmuch_database_get_directory (notmuch_database_t *database, @@ -490,6 +497,9 @@ notmuch_database_get_directory (notmuch_database_t *database, * * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only * mode so no message can be added. + * + * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the + * database to use this function. */ notmuch_status_t notmuch_database_add_message (notmuch_database_t *database, @@ -520,6 +530,9 @@ notmuch_database_add_message (notmuch_database_t *database, * * NOTMUCH_STATUS_READ_ONLY_DATABASE: Database was opened in read-only * mode so no message can be removed. + * + * NOTMUCH_STATUS_UPGRADE_REQUIRED: The caller must upgrade the + *
[PATCH v3 01/13] test: Include generated dependencies for test sources
Previously the build system was generating automatic header dependencies for test sources, but only smtp-dummy was in SRCS, so only its dependencies were being included. Add all of the test sources to SRCS so that the root Makefile.local includes their dependencies. --- test/Makefile.local | 19 --- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/test/Makefile.local b/test/Makefile.local index 1c85b18..916dd0b 100644 --- a/test/Makefile.local +++ b/test/Makefile.local @@ -37,12 +37,17 @@ $(dir)/parse-time: $(dir)/parse-time.o parse-time-string/parse-time-string.o .PHONY: test check -TEST_BINARIES=$(dir)/arg-test \ - $(dir)/hex-xcode \ - $(dir)/random-corpus \ - $(dir)/parse-time \ - $(dir)/smtp-dummy \ - $(dir)/symbol-test +test_main_srcs=$(dir)/arg-test.c \ + $(dir)/hex-xcode.c \ + $(dir)/random-corpus.c \ + $(dir)/parse-time.c \ + $(dir)/smtp-dummy.c \ + $(dir)/symbol-test.cc \ + +test_srcs=$(test_main_srcs) $(dir)/database-test.c + +TEST_BINARIES := $(test_main_srcs:.c=) +TEST_BINARIES := $(TEST_BINARIES:.cc=) test-binaries: $(TEST_BINARIES) @@ -51,7 +56,7 @@ test: all test-binaries check: test -SRCS := $(SRCS) $(smtp_dummy_srcs) +SRCS := $(SRCS) $(test_srcs) CLEAN += $(TEST_BINARIES) $(addsuffix .o,$(TEST_BINARIES)) \ $(dir)/database-test.o \ $(dir)/corpus.mail $(dir)/test-results $(dir)/tmp.* -- 2.0.0 ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
[PATCH v3 09/13] lib: Reorganize upgrade around document types
Rather than potentially making multiple passes over the same type of data in the database, reorganize upgrade around each type of data that may be upgraded. This eliminates code duplication, will make multi-version upgrades faster, and will let us improve progress reporting. --- lib/database.cc | 72 + 1 file changed, 26 insertions(+), 46 deletions(-) diff --git a/lib/database.cc b/lib/database.cc index 69d775f..31e6a93 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1241,11 +1241,9 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, * format. */ notmuch->features = target_features; -/* Before version 1, each message document had its filename in the - * data field. Copy that into the new format by calling - * notmuch_message_add_filename. - */ -if (new_features & NOTMUCH_FEATURE_FILE_TERMS) { +/* Perform per-message upgrades. */ +if (new_features & + (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER)) { notmuch_query_t *query = notmuch_query_create (notmuch, ""); notmuch_messages_t *messages; notmuch_message_t *message; @@ -1264,13 +1262,27 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, message = notmuch_messages_get (messages); - filename = _notmuch_message_talloc_copy_data (message); - if (filename && *filename != '\0') { - _notmuch_message_add_filename (message, filename); - _notmuch_message_clear_data (message); - _notmuch_message_sync (message); + /* Before version 1, each message document had its +* filename in the data field. Copy that into the new +* format by calling notmuch_message_add_filename. +*/ + if (new_features & NOTMUCH_FEATURE_FILE_TERMS) { + filename = _notmuch_message_talloc_copy_data (message); + if (filename && *filename != '\0') { + _notmuch_message_add_filename (message, filename); + _notmuch_message_clear_data (message); + } + talloc_free (filename); } - talloc_free (filename); + + /* Prior to version 2, the "folder:" prefix was +* probabilistic and stemmed. Change it to the current +* boolean prefix. Add "path:" prefixes while at it. +*/ + if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER) + _notmuch_message_upgrade_folder (message); + + _notmuch_message_sync (message); notmuch_message_destroy (message); @@ -1280,7 +1292,9 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, notmuch_query_destroy (query); } -/* Also, before version 1 we stored directory timestamps in +/* Perform per-directory upgrades. */ + +/* Before version 1 we stored directory timestamps in * XTIMESTAMP documents instead of the current XDIRECTORY * documents. So copy those as well. */ if (new_features & NOTMUCH_FEATURE_DIRECTORY_DOCS) { @@ -1322,40 +1336,6 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, } } -/* - * Prior to version 2, the "folder:" prefix was probabilistic and - * stemmed. Change it to the current boolean prefix. Add "path:" - * prefixes while at it. - */ -if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER) { - notmuch_query_t *query = notmuch_query_create (notmuch, ""); - notmuch_messages_t *messages; - notmuch_message_t *message; - - count = 0; - total = notmuch_query_count_messages (query); - - for (messages = notmuch_query_search_messages (query); -notmuch_messages_valid (messages); -notmuch_messages_move_to_next (messages)) { - if (do_progress_notify) { - progress_notify (closure, (double) count / total); - do_progress_notify = 0; - } - - message = notmuch_messages_get (messages); - - _notmuch_message_upgrade_folder (message); - _notmuch_message_sync (message); - - notmuch_message_destroy (message); - - count++; - } - - notmuch_query_destroy (query); -} - db->set_metadata ("features", _print_features (local, notmuch->features)); db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION)); -- 2.0.0 ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
[PATCH v3 02/13] util: Const version of strtok_len
Because of limitations in the C type system, we can't a strtok_len that can work on both const string and non-const strings. The C library solves this by taking a const char* and returning a char* in functions like this (e.g., strchr), but that's not const-safe. Solve it by introducing strtok_len_c, a version of strtok_len for const strings. --- util/string-util.c | 8 util/string-util.h | 3 +++ 2 files changed, 11 insertions(+) diff --git a/util/string-util.c b/util/string-util.c index 3e7066c..a90501e 100644 --- a/util/string-util.c +++ b/util/string-util.c @@ -37,6 +37,14 @@ strtok_len (char *s, const char *delim, size_t *len) return *len ? s : NULL; } +const char * +strtok_len_c (const char *s, const char *delim, size_t *len) +{ +/* strtok_len is already const-safe, but we can't express both + * versions in the C type system. */ +return strtok_len ((char*)s, delim, len); +} + char * sanitize_string (const void *ctx, const char *str) { diff --git a/util/string-util.h b/util/string-util.h index ccad17f..e409cb3 100644 --- a/util/string-util.h +++ b/util/string-util.h @@ -23,6 +23,9 @@ extern "C" { char *strtok_len (char *s, const char *delim, size_t *len); +/* Const version of strtok_len. */ +const char *strtok_len_c (const char *s, const char *delim, size_t *len); + /* Return a talloced string with str sanitized. * * Whitespace characters (tabs and newlines) are replaced with spaces, -- 2.0.0 ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
[PATCH v3 00/13] Implement and use database "features"
This is v3 of id:1406652492-27803-1-git-send-email-amdra...@mit.edu. This fixes one issue and tidies up another thing in notmuch_database_upgrade I found while working on change tracking support. Most of the patches are logically identical to v2, but the changes ripple through the patch context, so I'm sending a new series. First, this updates notmuch->features before starting the upgrade, rather than after, so that functions called by upgrade will use the new database features instead of the old (this didn't matter in this series because nothing modified the database differently depending on features). Second, this combines multiple _notmuch_message_sync calls into one, which cleans up the code, should further improve upgrade performance, and makes way for additional per-message upgrades. The diff from v2 is below (excluding patch 2, which David pushed). diff --git a/lib/database.cc b/lib/database.cc index b323691..d90a924 100644 --- a/lib/database.cc +++ b/lib/database.cc @@ -1252,6 +1252,10 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, /* Perform the upgrade in a transaction. */ db->begin_transaction (true); +/* Set the target features so we write out changes in the desired + * format. */ +notmuch->features = target_features; + /* Perform per-message upgrades. */ if (new_features & (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_BOOL_FOLDER)) { @@ -1280,7 +1284,6 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, if (filename && *filename != '\0') { _notmuch_message_add_filename (message, filename); _notmuch_message_clear_data (message); - _notmuch_message_sync (message); } talloc_free (filename); } @@ -1289,10 +1292,10 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, * probabilistic and stemmed. Change it to the current * boolean prefix. Add "path:" prefixes while at it. */ - if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER) { + if (new_features & NOTMUCH_FEATURE_BOOL_FOLDER) _notmuch_message_upgrade_folder (message); - _notmuch_message_sync (message); - } + + _notmuch_message_sync (message); notmuch_message_destroy (message); @@ -1348,7 +1351,6 @@ notmuch_database_upgrade (notmuch_database_t *notmuch, } } -notmuch->features = target_features; db->set_metadata ("features", _print_features (local, notmuch->features)); db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION)); ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH RFC] Emacs: Add address completion mechanism implemented in elisp
Michal Sojka writes: > notmuch-company.el hooks itself into message-mode and uses > company-mode to offer the completion to the user. The file is put into > the contrib directory which means that the use has to install it > himself. This is because company-mode is not a part of Emacs and > bytecompiling notmuch-company.el fails due to used --quick option that > causes user installed packages to be ignored. what about for now just putting ;; -*-no-byte-compile: t; -*- at the top of the file? It seems usable uncompiled to me. Then somebody who really wants it compiled can figure out how to do the conditional compilation. > It would probably make sense to implement another completion frontend > based only on Emacs built-in functionality and integrate it with > notmuch-addresses.el. Agreed. > + "`company-mode' completion back-end for `nevermore (nm)'." missed "nevermore" ;) > > +(defun notmuch-flatten-thread-set (thread-set) > + "Convert the result of 'notmuch show' to the plain list of messages." > +(defun notmuch-flatten-thread (thread) > + > +(defun notmuch-flatten-thread-node (thread-node) what about putting those functions in notmuch-query.el? > +(defun notmuch-async-harvest () > + "Collect possible addresses for completion. It queries the logically it seems like this might belong in notmuch-address.el, even if none of the functions in in it currently use d ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
Custom Tag Images
Hello Everyone. If this is not the place to address this issue, could you please direct me to the proper forum. Info: Using Emacs 24.3.1 on ArchLinux I am interested in setting up custom image for some of the tags I use. I have tried to use a svg file by setting it in the following ways: (defun notmuch-tag-list-icon () "Return SVG data representing a list. This can be used with `notmuch-tag-format-image-data'." " http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd\";> http://www.w3.org/2000/svg\"; xmlns:xlink=\"http://www.w3.org/1999/xlink\"; width=\"16\" height=\"16\" viewBox=\"0 0 16 16\"> ") and setting it like I do the default images. e.g. (setq notmuch-tag-formats '( ("flagged" (notmuch-tag-format-image-data tag (notmuch-tag-star-icon))) ("lists" (notmuch-tag-format-image-data tag (notmuch-tag-list-icon))) )) I have also tried via the customize image setting and using the path to the file. Both ways produce nothing but a colored rectangle. I have downloaded the file in question from http://IcoMoon.io Am I missing something obvious here, or is this svg perhaps formatted incorrectly for notmuch emacs? Any pointers in the right direction are greatly appreciated. Thanks.
[PATCH RFC] Emacs: Add address completion mechanism implemented in elisp
On Thu, Jul 31 2014, Trevor Jim wrote: > BTW I have been refactoring my async process code. You might consider > it for your patch. I'll look at that. I figured out that I might also reuse notmuch-parser.el. I'll post here my findings later. -Michal
[PATCH] emacs: Clarify that notmuch-poll-script is deprecated
Austin Clements writes: > notmuch-poll-script has long since been deprecated in favor of > post-new hooks, but this wasn't obvious from the documentation. > Update the documentation to make this clear. Since > notmuch-poll-script could, to some extend, be used to control the path > of the notmuch binary and that use is now clearly discouraged, promote > notmuch-command to a real defcustom instead of just a variable. pushed d
[PATCH v2 02/14] util: Make string-util.h C++-compatible
Austin Clements writes: > --- > util/string-util.h | 8 > 1 file changed, 8 insertions(+) pushed this one patch. d
[PATCH] config: read user.name from $NAME if set
Mark Oteiza writes: > Try to read the config parameter user.name from $NAME before taking the > user name from /etc/passwd. pushed d
[PATCH] config: read database.path from $MAILDIR if set
Mark Oteiza writes: > Try to read the config parameter database.path from $MAILDIR before > falling back to $HOME/mail pushed. d
[PATCH] config: read database.path from $MAILDIR if set
David Bremner writes: > Mark Oteiza writes: > >> Try to read the config parameter database.path from $MAILDIR before >> falling back to $HOME/mail >> --- > > This patch doesn't apply to git master. I'm not really sure happened, > maybe you generated it against some old version? Ah, nevermind it needed to have your other patch applied first. I guess it would be better to make a serious when a few patches touch the same code. d
[PATCH] config: read database.path from $MAILDIR if set
Mark Oteiza writes: > Try to read the config parameter database.path from $MAILDIR before > falling back to $HOME/mail > --- This patch doesn't apply to git master. I'm not really sure happened, maybe you generated it against some old version? d
Re: [PATCH] emacs: Clarify that notmuch-poll-script is deprecated
Austin Clements writes: > notmuch-poll-script has long since been deprecated in favor of > post-new hooks, but this wasn't obvious from the documentation. > Update the documentation to make this clear. Since > notmuch-poll-script could, to some extend, be used to control the path > of the notmuch binary and that use is now clearly discouraged, promote > notmuch-command to a real defcustom instead of just a variable. pushed d ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH] config: read database.path from $MAILDIR if set
Mark Oteiza writes: > Try to read the config parameter database.path from $MAILDIR before > falling back to $HOME/mail pushed. d ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH v2 02/14] util: Make string-util.h C++-compatible
Austin Clements writes: > --- > util/string-util.h | 8 > 1 file changed, 8 insertions(+) pushed this one patch. d ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH] config: read user.name from $NAME if set
Mark Oteiza writes: > Try to read the config parameter user.name from $NAME before taking the > user name from /etc/passwd. pushed d ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH] config: read database.path from $MAILDIR if set
David Bremner writes: > Mark Oteiza writes: > >> Try to read the config parameter database.path from $MAILDIR before >> falling back to $HOME/mail >> --- > > This patch doesn't apply to git master. I'm not really sure happened, > maybe you generated it against some old version? Ah, nevermind it needed to have your other patch applied first. I guess it would be better to make a serious when a few patches touch the same code. d ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH] config: read database.path from $MAILDIR if set
Mark Oteiza writes: > Try to read the config parameter database.path from $MAILDIR before > falling back to $HOME/mail > --- This patch doesn't apply to git master. I'm not really sure happened, maybe you generated it against some old version? d ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch
Re: [PATCH RFC] Emacs: Add address completion mechanism implemented in elisp
On Thu, Jul 31 2014, Trevor Jim wrote: > BTW I have been refactoring my async process code. You might consider > it for your patch. I'll look at that. I figured out that I might also reuse notmuch-parser.el. I'll post here my findings later. -Michal ___ notmuch mailing list notmuch@notmuchmail.org http://notmuchmail.org/mailman/listinfo/notmuch