Re: [PATCH v3 04/13] lib: Database version 3: Introduce fine-grained "features"
authorAustin Clements <amdragon@MIT.EDU>
Sun, 24 Aug 2014 00:57:40 +0000 (20:57 +2000)
committerW. Trevor King <wking@tremily.us>
Fri, 7 Nov 2014 18:04:16 +0000 (10:04 -0800)
33/990d90fd6a7bab90e75d7ed1f1af3b10a859e4 [new file with mode: 0644]

diff --git a/33/990d90fd6a7bab90e75d7ed1f1af3b10a859e4 b/33/990d90fd6a7bab90e75d7ed1f1af3b10a859e4
new file mode 100644 (file)
index 0000000..3ee4acd
--- /dev/null
@@ -0,0 +1,586 @@
+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 786AB431FBC\r
+       for <notmuch@notmuchmail.org>; Sat, 23 Aug 2014 17:57:49 -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 Hjxg7ETVrgXr for <notmuch@notmuchmail.org>;\r
+       Sat, 23 Aug 2014 17:57:44 -0700 (PDT)\r
+Received: from dmz-mailsec-scanner-1.mit.edu (dmz-mailsec-scanner-1.mit.edu\r
+       [18.9.25.12])\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 2D056431FAF\r
+       for <notmuch@notmuchmail.org>; Sat, 23 Aug 2014 17:57:44 -0700 (PDT)\r
+X-AuditID: 1209190c-f795e6d000006c66-72-53f93887247e\r
+Received: from mailhub-auth-4.mit.edu ( [18.7.62.39])\r
+       (using TLS with cipher AES256-SHA (256/256 bits))\r
+       (Client did not present a certificate)\r
+       by dmz-mailsec-scanner-1.mit.edu (Symantec Messaging Gateway) with SMTP\r
+       id BC.74.27750.78839F35; Sat, 23 Aug 2014 20:57:43 -0400 (EDT)\r
+Received: from outgoing.mit.edu (outgoing-auth-1.mit.edu [18.9.28.11])\r
+       by mailhub-auth-4.mit.edu (8.13.8/8.9.2) with ESMTP id s7O0vgP7025574; \r
+       Sat, 23 Aug 2014 20:57:43 -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 s7O0veiN027349\r
+       (version=TLSv1/SSLv3 cipher=DHE-RSA-AES128-SHA bits=128 verify=NOT);\r
+       Sat, 23 Aug 2014 20:57:41 -0400\r
+Received: from amthrax by awakening.csail.mit.edu with local (Exim 4.80)\r
+       (envelope-from <amdragon@MIT.EDU>)\r
+       id 1XLM7o-0001sz-Gj; Sat, 23 Aug 2014 20:57:40 -0400\r
+Date: Sat, 23 Aug 2014 20:57:40 -0400\r
+From: Austin Clements <amdragon@MIT.EDU>\r
+To: Jani Nikula <jani@nikula.org>\r
+Subject: Re: [PATCH v3 04/13] lib: Database version 3: Introduce fine-grained\r
+       "features"\r
+Message-ID: <20140824005740.GM3015@mit.edu>\r
+References: <1406859003-11561-1-git-send-email-amdragon@mit.edu>\r
+       <1406859003-11561-5-git-send-email-amdragon@mit.edu>\r
+       <87r407jisg.fsf@nikula.org>\r
+MIME-Version: 1.0\r
+Content-Type: text/plain; charset=us-ascii\r
+Content-Disposition: inline\r
+In-Reply-To: <87r407jisg.fsf@nikula.org>\r
+User-Agent: Mutt/1.5.21 (2010-09-15)\r
+X-Brightmail-Tracker:\r
+ H4sIAAAAAAAAA+NgFmpileLIzCtJLcpLzFFi42IRYrdT1223+BlscKhPyqJpurPF9ZszmR2Y\r
+       PG7df83u8WzVLeYApigum5TUnMyy1CJ9uwSujN+r57MXXFrGWLF9dkED4+q6LkZODgkBE4k7\r
+       vYdYIWwxiQv31rN1MXJxCAnMZpI4c+QMG0hCSGAjo8SaCXUQidNMEqtnTGeFSCxhlDgzUQfE\r
+       ZhFQlbj/9h0jiM0moCGxbf9yMFtEQFFi88n9YDazgLTEt9/NTCC2sECUxLP188HivALaEg+/\r
+       z2CCWDCVUWLK9ntQCUGJkzOfsEA0a0nc+PcSqIgDbNDyfxwgYU6gXZdXPwebKSqgIjHl5Da2\r
+       CYxCs5B0z0LSPQuhewEj8ypG2ZTcKt3cxMyc4tRk3eLkxLy81CJdQ73czBK91JTSTYygoOaU\r
+       5NnB+Oag0iFGAQ5GJR7eDxd/BAuxJpYVV+YeYpTkYFIS5X2l8TNYiC8pP6UyI7E4I76oNCe1\r
+       +BCjBAezkghvsRlQjjclsbIqtSgfJiXNwaIkzvvW2ipYSCA9sSQ1OzW1ILUIJivDwaEkwRtq\r
+       DtQoWJSanlqRlplTgpBm4uAEGc4DNFwfpIa3uCAxtzgzHSJ/ilFRSpx3I0hCACSRUZoH1wtL\r
+       Oq8YxYFeEYZo5wEmLLjuV0CDmYAGT5/xFWRwSSJCSqqBMTVZ/4vR70DFKx/mh87afCW9R0Je\r
+       do31O6UNHbafzuyzbXHZtffMwjVerXmO+z6zTC/2P3zWc8HJE54cYmIPeV4azLy9jDFRtklq\r
+       oqSzS3/iGmvrPSl7rCfeXBzWtKrS52NbbVVWzJeA2z69m5mmLp7EcmJq0yeHTY9+fJDZvnX3\r
+       j4DP/B+ci5RYijMSDbWYi4oTAc88TAAVAwAA\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, 24 Aug 2014 00:57:49 -0000\r
+\r
+Quoth Jani Nikula on Aug 23 at  7:02 pm:\r
+> On Fri, 01 Aug 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
+> > 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
+> I agree with the change overall; it might be useful to have another set\r
+> of eyes on the patch though.\r
+> \r
+> > ---\r
+> >  lib/database-private.h |  57 ++++++++++++++-\r
+> >  lib/database.cc        | 190 ++++++++++++++++++++++++++++++++++++++++---------\r
+> >  2 files changed, 213 insertions(+), 34 deletions(-)\r
+> >\r
+> > diff --git a/lib/database-private.h b/lib/database-private.h\r
+> > index d3e65fd..2ffab33 100644\r
+> > --- a/lib/database-private.h\r
+> > +++ b/lib/database-private.h\r
+> > @@ -41,11 +41,15 @@ struct _notmuch_database {\r
+> >  \r
+> >      char *path;\r
+> >  \r
+> > -    notmuch_bool_t needs_upgrade;\r
+> >      notmuch_database_mode_t mode;\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 +59,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
+> \r
+> Maybe I'm dense, but the implementation support comments could be\r
+> clearer.\r
+\r
+No, this was too terse.  The intent was to state what the current\r
+library implementation supports: e.g., the current implementation\r
+supports reading from databases both with and without\r
+NOTMUCH_FEATURE_FILE_TERMS, but if you attempt to write a database\r
+without this feature, some operations may return\r
+NOTMUCH_STATUS_UPGRADE_REQUIRED (introduced in a later patch).\r
+\r
+I wonder, though, if the "implementation support" bits should just be\r
+omitted.  These comments are inevitably going to get out of sync with\r
+the real implementation, and in this case an incorrect comment may be\r
+worse than no comment.  I could put a comment over the whole enum\r
+giving a more general description:\r
+\r
+/* Bit masks for _notmuch_database::features.  Features are named,\r
+ * independent aspects of the database schema.\r
+ *\r
+ * A database stores the set of features that it "uses" (implicitly\r
+ * before database version 3 and explicitly as of version 3).\r
+ *\r
+ * A given library version will "recognize" a particular set of\r
+ * features; if a database uses a feature that the library does not\r
+ * recognize, the library will refuse to open it.  It is assumed the\r
+ * set of recognized features grows monotonically over time.  A\r
+ * library version will "implement" some subset of the recognized\r
+ * features: some operations may require that the database use (or not\r
+ * use) some feature, while other operations may support both\r
+ * databases that use and that don't use some feature.\r
+ *\r
+ * On disk, the database stores string names for these features (see\r
+ * the feature_names array).  These enum bit values are never\r
+ * persisted to disk and may change freely.\r
+ */\r
+\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..29a56db 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
+> > @@ -251,6 +263,25 @@ _find_prefix (const char *name)\r
+> >      return "";\r
+> >  }\r
+> >  \r
+> > +static const struct\r
+> > +{\r
+> \r
+> Brace should be at the end of the previous line.\r
+\r
+Fixed.\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,             "multiple paths per message", "rw"},\r
+> > +    {NOTMUCH_FEATURE_DIRECTORY_DOCS,         "relative directory paths", "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/message-ID in database", "w"},\r
+> > +    {NOTMUCH_FEATURE_BOOL_FOLDER,            "exact folder:/path: search", "rw"},\r
+> \r
+> Spaces missing after the opening braces and before the closing braces.\r
+\r
+Fixed.  I also wrapped all of the entries between the enum name and\r
+the string name since one of the lines was already too long and this\r
+pushed two more over.\r
+\r
+> > +};\r
+> > +\r
+> >  const char *\r
+> >  notmuch_status_to_string (notmuch_status_t status)\r
+> >  {\r
+> > @@ -591,6 +622,11 @@ notmuch_database_create (const char *path, notmuch_database_t **database)\r
+> >                                &notmuch);\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
+> >      status = notmuch_database_upgrade (notmuch, NULL, NULL);\r
+> >      if (status) {\r
+> >    notmuch_database_close(notmuch);\r
+> > @@ -619,6 +655,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
+> Please describe the actual return value.\r
+\r
+Fixed.  I also gave the enum type a name (in response to David's\r
+review) and used it here, so this is more self-documenting.\r
+\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
+> Do we intend the lib user to be able to parse the features? Perhaps we\r
+> should use '\n' as separator here too? (Although looks like *currently*\r
+> this is only for printing the message from within the lib.)\r
+\r
+I'd intended this only for user presentation.  I updated the comment\r
+to mention this.  If it turns out we need this information in a\r
+computer-friendly form in the future, it should be easy to add.\r
+\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 +737,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
+> > @@ -677,7 +787,6 @@ notmuch_database_open (const char *path,\r
+> >      if (notmuch->path[strlen (notmuch->path) - 1] == '/')\r
+> >    notmuch->path[strlen (notmuch->path) - 1] = '\0';\r
+> >  \r
+> > -    notmuch->needs_upgrade = FALSE;\r
+> >      notmuch->mode = mode;\r
+> >      notmuch->atomic_nesting = 0;\r
+> >      try {\r
+> > @@ -686,37 +795,44 @@ 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
+> \r
+> Makes me think _parse_features could use notmuch_database_mode_t mode\r
+> instead of char mode. *shrug*.\r
+\r
+In principle there could be other compatibility modes, though I can't\r
+imagine what they would be.\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
+> >    notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();\r
+> > @@ -1048,7 +1164,8 @@ notmuch_database_get_version (notmuch_database_t *notmuch)\r
+> >  notmuch_bool_t\r
+> >  notmuch_database_needs_upgrade (notmuch_database_t *notmuch)\r
+> >  {\r
+> > -    return notmuch->needs_upgrade;\r
+> > +    return notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE &&\r
+> > +  (NOTMUCH_FEATURES_CURRENT & ~notmuch->features);\r
+> >  }\r
+> \r
+> Am I correct that this does not lead to a database upgrade from v2 to v3\r
+> until we actually change the features?\r
+\r
+Yep, that's true (though new databases will start with v3).\r
+Conveniently, I have a bunch of patches that introduce some new\r
+features pending right behind this series.  :)\r
+\r
+> Aside, why don't we return a suitable status code from\r
+> notmuch_database_open when an upgrade is required?\r
+\r
+That may make sense at this point in the series, but later patches\r
+make upgrades no longer "required" and instead enforce database\r
+feature requirements on a per-operation basis.  At that point, this\r
+function becomes effectively optional; more "wants_upgrade" than\r
+"needs_upgrade".\r
+\r
+> >  static volatile sig_atomic_t do_progress_notify = 0;\r
+> > @@ -1077,6 +1194,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
+> > @@ -1114,6 +1232,10 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,\r
+> >    timer_is_active = TRUE;\r
+> >      }\r
+> >  \r
+> > +    /* Set the target features so we write out changes in the desired\r
+> > +     * format. */\r
+> > +    notmuch->features |= NOTMUCH_FEATURES_CURRENT;\r
+> > +\r
+> >      /* Before version 1, each message document had its filename in the\r
+> >       * data field. Copy that into the new format by calling\r
+> >       * notmuch_message_add_filename.\r
+> > @@ -1226,6 +1348,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,\r
+> >    notmuch_query_destroy (query);\r
+> >      }\r
+> >  \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 +1425,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