--- /dev/null
+Return-Path: <amdragon@mit.edu>\r
+X-Original-To: notmuch@notmuchmail.org\r
+Delivered-To: notmuch@notmuchmail.org\r
+Received: from localhost (localhost [127.0.0.1])\r
+ by olra.theworths.org (Postfix) with ESMTP id 1EFD0431FBC\r
+ for <notmuch@notmuchmail.org>; Sun, 27 Jul 2014 09:58:46 -0700 (PDT)\r
+X-Virus-Scanned: Debian amavisd-new at olra.theworths.org\r
+X-Spam-Flag: NO\r
+X-Spam-Score: -2.3\r
+X-Spam-Level: \r
+X-Spam-Status: No, score=-2.3 tagged_above=-999 required=5\r
+ tests=[RCVD_IN_DNSWL_MED=-2.3] autolearn=disabled\r
+Received: from olra.theworths.org ([127.0.0.1])\r
+ by localhost (olra.theworths.org [127.0.0.1]) (amavisd-new, port 10024)\r
+ with ESMTP id GHQtWonDpKLt for <notmuch@notmuchmail.org>;\r
+ Sun, 27 Jul 2014 09:58:38 -0700 (PDT)\r
+Received: from dmz-mailsec-scanner-7.mit.edu (dmz-mailsec-scanner-7.mit.edu\r
+ [18.7.68.36])\r
+ (using TLSv1 with cipher DHE-RSA-AES256-SHA (256/256 bits))\r
+ (No client certificate requested)\r
+ by olra.theworths.org (Postfix) with ESMTPS id E19C1431FAE\r
+ for <notmuch@notmuchmail.org>; Sun, 27 Jul 2014 09:58:37 -0700 (PDT)\r
+X-AuditID: 12074424-f79146d00000067c-f1-53d52fbd0990\r
+Received: from mailhub-auth-3.mit.edu ( [18.9.21.43])\r
+ (using TLS with cipher AES256-SHA (256/256 bits))\r
+ (Client did not present a certificate)\r
+ by dmz-mailsec-scanner-7.mit.edu (Symantec Messaging Gateway) with SMTP\r
+ id 06.24.01660.DBF25D35; Sun, 27 Jul 2014 12:58:37 -0400 (EDT)\r
+Received: from outgoing.mit.edu (outgoing-auth-1.mit.edu [18.9.28.11])\r
+ by mailhub-auth-3.mit.edu (8.13.8/8.9.2) with ESMTP id s6RGwawD013761; \r
+ Sun, 27 Jul 2014 12:58:36 -0400\r
+Received: from awakening.csail.mit.edu (awakening.csail.mit.edu [18.26.4.91])\r
+ (authenticated bits=0)\r
+ (User authenticated as amdragon@ATHENA.MIT.EDU)\r
+ by outgoing.mit.edu (8.13.8/8.12.4) with ESMTP id s6RGwYJw022906\r
+ (version=TLSv1/SSLv3 cipher=DHE-RSA-AES128-SHA bits=128 verify=NOT);\r
+ Sun, 27 Jul 2014 12:58:36 -0400\r
+Received: from amthrax by awakening.csail.mit.edu with local (Exim 4.80)\r
+ (envelope-from <amdragon@mit.edu>)\r
+ id 1XBRmM-0007Fa-3C; Sun, 27 Jul 2014 12:58:34 -0400\r
+Date: Sun, 27 Jul 2014 12:58:33 -0400\r
+From: Austin Clements <amdragon@MIT.EDU>\r
+To: Mark Walters <markwalters1009@gmail.com>\r
+Subject: Re: [PATCH 05/14] lib: Database version 3: Introduce fine-grained\r
+ "features"\r
+Message-ID: <20140727165833.GH13893@mit.edu>\r
+References: <1406433173-19169-1-git-send-email-amdragon@mit.edu>\r
+ <1406433173-19169-6-git-send-email-amdragon@mit.edu>\r
+ <87silnyvoq.fsf@qmul.ac.uk>\r
+MIME-Version: 1.0\r
+Content-Type: text/plain; charset=us-ascii\r
+Content-Disposition: inline\r
+In-Reply-To: <87silnyvoq.fsf@qmul.ac.uk>\r
+User-Agent: Mutt/1.5.21 (2010-09-15)\r
+X-Brightmail-Tracker:\r
+ H4sIAAAAAAAAA+NgFmplleLIzCtJLcpLzFFi42IR4hTV1t2rfzXYoOket8XquTwW12/OZHZg\r
+ 8tg56y67x7NVt5gDmKK4bFJSczLLUov07RK4MpbN/cVacHkqY8W+PytYGxgbCrsYOTkkBEwk\r
+ Ns5YxQhhi0lcuLeerYuRi0NIYDaTxN/GRUwQzkZGieaOucwQzmkmiYsL1jBCOEsYJfa9WAPW\r
+ zyKgKtHbdIEdxGYT0JDYtn85WFxEQEfi9qEFYHFmAWmJb7+bmUBsYYFwiRdTLoPV8ALVtLav\r
+ hho6lVFibW8LC0RCUOLkzCcsEM1aEjf+vQRq5gAbtPwfB0iYE2jXoiXLmEFsUQEViSknt7FN\r
+ YBSahaR7FpLuWQjdCxiZVzHKpuRW6eYmZuYUpybrFicn5uWlFuma6+VmluilppRuYgSFNruL\r
+ yg7G5kNKhxgFOBiVeHgz2K4EC7EmlhVX5h5ilORgUhLl1da+GizEl5SfUpmRWJwRX1Sak1p8\r
+ iFGCg1lJhLfwLVA5b0piZVVqUT5MSpqDRUmc9621VbCQQHpiSWp2ampBahFMVoaDQ0mCV0EP\r
+ aKhgUWp6akVaZk4JQpqJgxNkOA/Q8ABdoBre4oLE3OLMdIj8KUZFKXHedpCEAEgiozQPrheW\r
+ el4xigO9IswrCrKCB5i24LpfAQ1mAhrM4n8ZZHBJIkJKqoHRN27/qzel3RtqSrmmmgt8vyM7\r
+ jZvp3eSt/rJdhq755w1y86R6rz8InFv0KjPyGjf7eZNldjubTxpvfNR9xNJA/oRWT0TAyp4l\r
+ O4KX8okdXOEUeTUpx51vxqodT833i1t/2Ret9iyU43l8e82R/v/+NomiKcbJbNnX2N59VjE0\r
+ SlyXtK1870YlluKMREMt5qLiRABpAKaHGAMAAA==\r
+Cc: notmuch@notmuchmail.org\r
+X-BeenThere: notmuch@notmuchmail.org\r
+X-Mailman-Version: 2.1.13\r
+Precedence: list\r
+List-Id: "Use and development of the notmuch mail system."\r
+ <notmuch.notmuchmail.org>\r
+List-Unsubscribe: <http://notmuchmail.org/mailman/options/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=unsubscribe>\r
+List-Archive: <http://notmuchmail.org/pipermail/notmuch>\r
+List-Post: <mailto:notmuch@notmuchmail.org>\r
+List-Help: <mailto:notmuch-request@notmuchmail.org?subject=help>\r
+List-Subscribe: <http://notmuchmail.org/mailman/listinfo/notmuch>,\r
+ <mailto:notmuch-request@notmuchmail.org?subject=subscribe>\r
+X-List-Received-Date: Sun, 27 Jul 2014 16:58:46 -0000\r
+\r
+Quoth Mark Walters on Jul 27 at 10:53 am:\r
+> \r
+> On Sun, 27 Jul 2014, Austin Clements <amdragon@MIT.EDU> wrote:\r
+> > Previously, our database schema was versioned by a single number.\r
+> > Each database schema change had to occur "atomically" in Notmuch's\r
+> > development history: before some commit, Notmuch used version N, after\r
+> > that commit, it used version N+1. Hence, each new schema version\r
+> > could introduce only one change, the task of developing a schema\r
+> > change fell on a single person, and it all had to happen and be\r
+> > perfect in a single commit series. This made introducing a new schema\r
+> > version hard. We've seen only two schema changes in the history of\r
+> > Notmuch.\r
+> \r
+> I like this series as a whole and I am happy with all the patches that I\r
+> haven't sent comments on. (Note, however, that I am not very familiar\r
+> with the database code.)\r
+> \r
+> There is one thing which confuses me in this patch:\r
+> \r
+> >\r
+> > This commit introduces database schema version 3; hopefully the last\r
+> > schema version we'll need for a while. With this version, we switch\r
+> > from a single version number to "features": a set of named,\r
+> > independent aspects of the database schema.\r
+> >\r
+> > Features should make backwards compatibility easier. For many things,\r
+> > it should be easy to support databases both with and without a\r
+> > feature, which will allow us to make upgrades optional and will enable\r
+> > "unstable" features that can be developed and tested over time.\r
+> >\r
+> > Features also make forwards compatibility easier. The features\r
+> > recorded in a database include "compatibility flags," which can\r
+> > indicate to an older version of Notmuch when it must support a given\r
+> > feature to open the database for read or for write. This lets us\r
+> > replace the old vague "I don't recognize this version, so something\r
+> > might go wrong, but I promise to try my best" warnings upon opening a\r
+> > database with an unknown version with precise errors. If a database\r
+> > is safe to open for read/write despite unknown features, an older\r
+> > version will know that and issue no message at all. If the database\r
+> > is not safe to open for read/write because of unknown features, an\r
+> > older version will know that, too, and can tell the user exactly which\r
+> > required features it lacks support for.\r
+> > ---\r
+> > lib/database-private.h | 56 +++++++++++++++\r
+> > lib/database.cc | 189 +++++++++++++++++++++++++++++++++++++++++--------\r
+> > 2 files changed, 214 insertions(+), 31 deletions(-)\r
+> >\r
+> > diff --git a/lib/database-private.h b/lib/database-private.h\r
+> > index d3e65fd..323b9fe 100644\r
+> > --- a/lib/database-private.h\r
+> > +++ b/lib/database-private.h\r
+> > @@ -46,6 +46,11 @@ struct _notmuch_database {\r
+> > int atomic_nesting;\r
+> > Xapian::Database *xapian_db;\r
+> > \r
+> > + /* Bit mask of features used by this database. Features are\r
+> > + * named, independent aspects of the database schema. This is a\r
+> > + * bitwise-OR of NOTMUCH_FEATURE_* values (below). */\r
+> > + unsigned int features;\r
+> > +\r
+> > unsigned int last_doc_id;\r
+> > uint64_t last_thread_id;\r
+> > \r
+> > @@ -55,6 +60,57 @@ struct _notmuch_database {\r
+> > Xapian::ValueRangeProcessor *date_range_processor;\r
+> > };\r
+> > \r
+> > +/* Bit masks for _notmuch_database::features. */\r
+> > +enum {\r
+> > + /* If set, file names are stored in "file-direntry" terms. If\r
+> > + * unset, file names are stored in document data.\r
+> > + *\r
+> > + * Introduced: version 1. Implementation support: both for read;\r
+> > + * required for write. */\r
+> > + NOTMUCH_FEATURE_FILE_TERMS = 1 << 0,\r
+> > +\r
+> > + /* If set, directory timestamps are stored in documents with\r
+> > + * XDIRECTORY terms and relative paths. If unset, directory\r
+> > + * timestamps are stored in documents with XTIMESTAMP terms and\r
+> > + * absolute paths.\r
+> > + *\r
+> > + * Introduced: version 1. Implementation support: required. */\r
+> > + NOTMUCH_FEATURE_DIRECTORY_DOCS = 1 << 1,\r
+> > +\r
+> > + /* If set, the from, subject, and message-id headers are stored in\r
+> > + * message document values. If unset, message documents *may*\r
+> > + * have these values, but if the value is empty, it must be\r
+> > + * retrieved from the message file.\r
+> > + *\r
+> > + * Introduced: optional in version 1, required as of version 3.\r
+> > + * Implementation support: both.\r
+> > + */\r
+> > + NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES = 1 << 2,\r
+> > +\r
+> > + /* If set, folder terms are boolean and path terms exist. If\r
+> > + * unset, folder terms are probabilistic and stemmed and path\r
+> > + * terms do not exist.\r
+> > + *\r
+> > + * Introduced: version 2. Implementation support: required. */\r
+> > + NOTMUCH_FEATURE_BOOL_FOLDER = 1 << 3,\r
+> > +};\r
+> > +\r
+> > +/* Prior to database version 3, features were implied by the database\r
+> > + * version number, so hard-code them for earlier versions. */\r
+> > +#define NOTMUCH_FEATURES_V0 (0)\r
+> > +#define NOTMUCH_FEATURES_V1 (NOTMUCH_FEATURES_V0 | NOTMUCH_FEATURE_FILE_TERMS | \\r
+> > + NOTMUCH_FEATURE_DIRECTORY_DOCS)\r
+> > +#define NOTMUCH_FEATURES_V2 (NOTMUCH_FEATURES_V1 | NOTMUCH_FEATURE_BOOL_FOLDER)\r
+> > +\r
+> > +/* Current database features. If any of these are missing from a\r
+> > + * database, request an upgrade.\r
+> > + * NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES is not included because\r
+> > + * upgrade doesn't currently introduce the feature (though brand new\r
+> > + * databases will have it). */\r
+> > +#define NOTMUCH_FEATURES_CURRENT \\r
+> > + (NOTMUCH_FEATURE_FILE_TERMS | NOTMUCH_FEATURE_DIRECTORY_DOCS | \\r
+> > + NOTMUCH_FEATURE_BOOL_FOLDER)\r
+> > +\r
+> > /* Return the list of terms from the given iterator matching a prefix.\r
+> > * The prefix will be stripped from the strings in the returned list.\r
+> > * The list will be allocated using ctx as the talloc context.\r
+> > diff --git a/lib/database.cc b/lib/database.cc\r
+> > index 45c4260..03eef3e 100644\r
+> > --- a/lib/database.cc\r
+> > +++ b/lib/database.cc\r
+> > @@ -20,6 +20,7 @@\r
+> > \r
+> > #include "database-private.h"\r
+> > #include "parse-time-vrp.h"\r
+> > +#include "string-util.h"\r
+> > \r
+> > #include <iostream>\r
+> > \r
+> > @@ -42,7 +43,7 @@ typedef struct {\r
+> > const char *prefix;\r
+> > } prefix_t;\r
+> > \r
+> > -#define NOTMUCH_DATABASE_VERSION 2\r
+> > +#define NOTMUCH_DATABASE_VERSION 3\r
+> > \r
+> > #define STRINGIFY(s) _SUB_STRINGIFY(s)\r
+> > #define _SUB_STRINGIFY(s) #s\r
+> > @@ -151,6 +152,17 @@ typedef struct {\r
+> > * changes are made to the database (such as by\r
+> > * indexing new fields).\r
+> > *\r
+> > + * features The set of features supported by this\r
+> > + * database. This consists of a set of\r
+> > + * '\n'-separated lines, where each is a feature\r
+> > + * name, a '\t', and compatibility flags. If the\r
+> > + * compatibility flags contain 'w', then the\r
+> > + * opener must support this feature to safely\r
+> > + * write this database. If the compatibility\r
+> > + * flags contain 'r', then the opener must\r
+> > + * support this feature to read this database.\r
+> > + * Introduced in database version 3.\r
+> > + *\r
+> > * last_thread_id The last thread ID generated. This is stored\r
+> > * as a 16-byte hexadecimal ASCII representation\r
+> > * of a 64-bit unsigned integer. The first ID\r
+> > @@ -226,6 +238,7 @@ static prefix_t PROBABILISTIC_PREFIX[]= {\r
+> > { "subject", "XSUBJECT"},\r
+> > };\r
+> > \r
+> > +\r
+> > const char *\r
+> > _find_prefix (const char *name)\r
+> > {\r
+> > @@ -251,6 +264,25 @@ _find_prefix (const char *name)\r
+> > return "";\r
+> > }\r
+> > \r
+> > +static const struct\r
+> > +{\r
+> > + /* NOTMUCH_FEATURE_* value. */\r
+> > + unsigned int value;\r
+> > + /* Feature name as it appears in the database. This name should\r
+> > + * be appropriate for displaying to the user if an older version\r
+> > + * of notmuch doesn't support this feature. */\r
+> > + const char *name;\r
+> > + /* Compatibility flags when this feature is declared. */\r
+> > + const char *flags;\r
+> > +} feature_names[] = {\r
+> > + {NOTMUCH_FEATURE_FILE_TERMS, "file terms", "rw"},\r
+> > + {NOTMUCH_FEATURE_DIRECTORY_DOCS, "directory documents", "rw"},\r
+> > + /* Header values are not required for reading a database because a\r
+> > + * reader can just refer to the message file. */\r
+> > + {NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES, "from/subject/ID values", "w"},\r
+> > + {NOTMUCH_FEATURE_BOOL_FOLDER, "boolean folder terms", "rw"},\r
+> > +};\r
+> > +\r
+> > const char *\r
+> > notmuch_status_to_string (notmuch_status_t status)\r
+> > {\r
+> > @@ -591,6 +623,11 @@ notmuch_database_create (const char *path, notmuch_database_t **database)\r
+> > ¬much);\r
+> > if (status)\r
+> > goto DONE;\r
+> > +\r
+> > + /* Upgrade doesn't add this feature to existing databases, but new\r
+> > + * databases have it. */\r
+> > + notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;\r
+> > +\r
+> \r
+> Does this mean that if the user wants this feature he has to dump,\r
+> delete the existing database, and restore? Is that just because no-one\r
+> has implemented upgrade for this feature or is it "hard" in some sense?\r
+\r
+Yep. But note that while this feature bit *guarantees* that these\r
+values will be available, if this feature bit isn't set, the code will\r
+still check for these values and use them if they're present.\r
+\r
+Implementing an upgrade for this feature is "hard" in the sense that\r
+it requires parsing messages to fill in missing values. Currently,\r
+all upgrades operate on the database exclusively, without going back\r
+to the original messages. I doubt we'll be able to keep that up\r
+forever, but that's what's going on now. (It'll be tricky to deal\r
+with when we do need it, since the database may not be up-to-date with\r
+the mail store, so we may not be able to open all messages!)\r
+\r
+> A possibly related question: is there likely to be a case where the user\r
+> does not want to add/upgrade some feature? Would it be worth having an\r
+> explicit upgrade command which let the user choose which features to\r
+> upgrade? This is orthogonal to this series: I am just trying to get a\r
+> feel for how it will, or could, be used.\r
+\r
+Good question. My gut feeling is that we should try to avoid this in\r
+the general case because this sort of "feature fragmentation" would\r
+complicate the code, as well as testing and debugging.\r
+\r
+There are a few potential uses I can think of, though. One relates to\r
+what I was saying above: if some upgrade requires reindexing messages,\r
+we may want to let the user postpone that (maybe notmuch new --reindex\r
+or something). I also think we should have "unstable" features for\r
+things that are under development and may change in\r
+non-backwards-compatible ways. This would be specifically targeted at\r
+developers, and developers would have to opt in to these unstable\r
+features. But the goal would always be to stabilize them and make\r
+them standard features that are automatically upgraded to.\r
+\r
+> Best wishes\r
+> \r
+> Mark\r
+> \r
+> > status = notmuch_database_upgrade (notmuch, NULL, NULL);\r
+> > if (status) {\r
+> > notmuch_database_close(notmuch);\r
+> > @@ -619,6 +656,80 @@ _notmuch_database_ensure_writable (notmuch_database_t *notmuch)\r
+> > return NOTMUCH_STATUS_SUCCESS;\r
+> > }\r
+> > \r
+> > +/* Parse a database features string from the given database version.\r
+> > + *\r
+> > + * For version < 3, this ignores the features string and returns a\r
+> > + * hard-coded set of features.\r
+> > + *\r
+> > + * If there are unrecognized features that are required to open the\r
+> > + * database in mode (which should be 'r' or 'w'), return a\r
+> > + * comma-separated list of unrecognized but required features in\r
+> > + * *incompat_out, which will be allocated from ctx.\r
+> > + */\r
+> > +static unsigned int\r
+> > +_parse_features (const void *ctx, const char *features, unsigned int version,\r
+> > + char mode, char **incompat_out)\r
+> > +{\r
+> > + unsigned int res = 0, namelen, i;\r
+> > + size_t llen = 0;\r
+> > + const char *flags;\r
+> > +\r
+> > + /* Prior to database version 3, features were implied by the\r
+> > + * version number. */\r
+> > + if (version == 0)\r
+> > + return NOTMUCH_FEATURES_V0;\r
+> > + else if (version == 1)\r
+> > + return NOTMUCH_FEATURES_V1;\r
+> > + else if (version == 2)\r
+> > + return NOTMUCH_FEATURES_V2;\r
+> > +\r
+> > + /* Parse the features string */\r
+> > + while ((features = strtok_len_c (features + llen, "\n", &llen)) != NULL) {\r
+> > + flags = strchr (features, '\t');\r
+> > + if (! flags || flags > features + llen)\r
+> > + continue;\r
+> > + namelen = flags - features;\r
+> > +\r
+> > + for (i = 0; i < ARRAY_SIZE (feature_names); ++i) {\r
+> > + if (strlen (feature_names[i].name) == namelen &&\r
+> > + strncmp (feature_names[i].name, features, namelen) == 0) {\r
+> > + res |= feature_names[i].value;\r
+> > + break;\r
+> > + }\r
+> > + }\r
+> > +\r
+> > + if (i == ARRAY_SIZE (feature_names)) {\r
+> > + /* Unrecognized feature */\r
+> > + const char *have = strchr (flags, mode);\r
+> > + if (have && have < features + llen) {\r
+> > + /* This feature is required to access this database in\r
+> > + * 'mode', but we don't understand it. */\r
+> > + if (! *incompat_out)\r
+> > + *incompat_out = talloc_strdup (ctx, "");\r
+> > + *incompat_out = talloc_asprintf_append_buffer (\r
+> > + *incompat_out, "%s%.*s", **incompat_out ? ", " : "",\r
+> > + namelen, features);\r
+> > + }\r
+> > + }\r
+> > + }\r
+> > +\r
+> > + return res;\r
+> > +}\r
+> > +\r
+> > +static char *\r
+> > +_print_features (const void *ctx, unsigned int features)\r
+> > +{\r
+> > + unsigned int i;\r
+> > + char *res = talloc_strdup (ctx, "");\r
+> > +\r
+> > + for (i = 0; i < ARRAY_SIZE (feature_names); ++i)\r
+> > + if (features & feature_names[i].value)\r
+> > + res = talloc_asprintf_append_buffer (\r
+> > + res, "%s\t%s\n", feature_names[i].name, feature_names[i].flags);\r
+> > +\r
+> > + return res;\r
+> > +}\r
+> > +\r
+> > notmuch_status_t\r
+> > notmuch_database_open (const char *path,\r
+> > notmuch_database_mode_t mode,\r
+> > @@ -627,7 +738,7 @@ notmuch_database_open (const char *path,\r
+> > notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;\r
+> > void *local = talloc_new (NULL);\r
+> > notmuch_database_t *notmuch = NULL;\r
+> > - char *notmuch_path, *xapian_path;\r
+> > + char *notmuch_path, *xapian_path, *incompat_features;\r
+> > struct stat st;\r
+> > int err;\r
+> > unsigned int i, version;\r
+> > @@ -686,39 +797,51 @@ notmuch_database_open (const char *path,\r
+> > if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {\r
+> > notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,\r
+> > Xapian::DB_CREATE_OR_OPEN);\r
+> > - version = notmuch_database_get_version (notmuch);\r
+> > -\r
+> > - if (version > NOTMUCH_DATABASE_VERSION) {\r
+> > - fprintf (stderr,\r
+> > - "Error: Notmuch database at %s\n"\r
+> > - " has a newer database format version (%u) than supported by this\n"\r
+> > - " version of notmuch (%u). Refusing to open this database in\n"\r
+> > - " read-write mode.\n",\r
+> > - notmuch_path, version, NOTMUCH_DATABASE_VERSION);\r
+> > - notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;\r
+> > - notmuch_database_destroy (notmuch);\r
+> > - notmuch = NULL;\r
+> > - status = NOTMUCH_STATUS_FILE_ERROR;\r
+> > - goto DONE;\r
+> > - }\r
+> > -\r
+> > - if (version < NOTMUCH_DATABASE_VERSION)\r
+> > - notmuch->needs_upgrade = TRUE;\r
+> > } else {\r
+> > notmuch->xapian_db = new Xapian::Database (xapian_path);\r
+> > - version = notmuch_database_get_version (notmuch);\r
+> > - if (version > NOTMUCH_DATABASE_VERSION)\r
+> > - {\r
+> > - fprintf (stderr,\r
+> > - "Warning: Notmuch database at %s\n"\r
+> > - " has a newer database format version (%u) than supported by this\n"\r
+> > - " version of notmuch (%u). Some operations may behave incorrectly,\n"\r
+> > - " (but the database will not be harmed since it is being opened\n"\r
+> > - " in read-only mode).\n",\r
+> > - notmuch_path, version, NOTMUCH_DATABASE_VERSION);\r
+> > - }\r
+> > }\r
+> > \r
+> > + /* Check version. As of database version 3, we represent\r
+> > + * changes in terms of features, so assume a version bump\r
+> > + * means a dramatically incompatible change. */\r
+> > + version = notmuch_database_get_version (notmuch);\r
+> > + if (version > NOTMUCH_DATABASE_VERSION) {\r
+> > + fprintf (stderr,\r
+> > + "Error: Notmuch database at %s\n"\r
+> > + " has a newer database format version (%u) than supported by this\n"\r
+> > + " version of notmuch (%u).\n",\r
+> > + notmuch_path, version, NOTMUCH_DATABASE_VERSION);\r
+> > + notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;\r
+> > + notmuch_database_destroy (notmuch);\r
+> > + notmuch = NULL;\r
+> > + status = NOTMUCH_STATUS_FILE_ERROR;\r
+> > + goto DONE;\r
+> > + }\r
+> > +\r
+> > + /* Check features. */\r
+> > + incompat_features = NULL;\r
+> > + notmuch->features = _parse_features (\r
+> > + local, notmuch->xapian_db->get_metadata ("features").c_str (),\r
+> > + version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',\r
+> > + &incompat_features);\r
+> > + if (incompat_features) {\r
+> > + fprintf (stderr,\r
+> > + "Error: Notmuch database at %s\n"\r
+> > + " requires features (%s)\n"\r
+> > + " not supported by this version of notmuch.\n",\r
+> > + notmuch_path, incompat_features);\r
+> > + notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;\r
+> > + notmuch_database_destroy (notmuch);\r
+> > + notmuch = NULL;\r
+> > + status = NOTMUCH_STATUS_FILE_ERROR;\r
+> > + goto DONE;\r
+> > + }\r
+> > +\r
+> > + /* Do we want an upgrade? */\r
+> > + if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE &&\r
+> > + NOTMUCH_FEATURES_CURRENT & ~notmuch->features)\r
+> > + notmuch->needs_upgrade = TRUE;\r
+> > +\r
+> > notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();\r
+> > last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");\r
+> > if (last_thread_id.empty ()) {\r
+> > @@ -1077,6 +1200,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,\r
+> > double progress),\r
+> > void *closure)\r
+> > {\r
+> > + void *local = talloc_new (NULL);\r
+> > Xapian::WritableDatabase *db;\r
+> > struct sigaction action;\r
+> > struct itimerval timerval;\r
+> > @@ -1226,6 +1350,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,\r
+> > notmuch_query_destroy (query);\r
+> > }\r
+> > \r
+> > + notmuch->features |= NOTMUCH_FEATURES_CURRENT;\r
+> > + db->set_metadata ("features", _print_features (local, notmuch->features));\r
+> > db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION));\r
+> > db->flush ();\r
+> > \r
+> > @@ -1302,6 +1428,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,\r
+> > sigaction (SIGALRM, &action, NULL);\r
+> > }\r
+> > \r
+> > + talloc_free (local);\r
+> > return NOTMUCH_STATUS_SUCCESS;\r
+> > }\r
+> > \r