]> git.notmuchmail.org Git - notmuch/blobdiff - lib/database.cc
lib/database: delete stemmer on destroy
[notmuch] / lib / database.cc
index 9cf8062cbe7caa2c5f7ecd714ca934c7f96b1e76..df83e2048673b235e6c1055ccb47d39923f480a7 100644 (file)
  */
 
 #include "database-private.h"
-#include "parse-time-vrp.h"
-#include "query-fp.h"
-#include "thread-fp.h"
-#include "regexp-fields.h"
 #include "string-util.h"
 
 #include <iostream>
 #include <signal.h>
 #include <ftw.h>
 
-#include <glib.h> /* g_free, GPtrArray, GHashTable */
-#include <glib-object.h> /* g_type_init */
+#include <glib.h>               /* g_free, GPtrArray, GHashTable */
+#include <glib-object.h>        /* g_type_init */
 
-#include <gmime/gmime.h> /* g_mime_init */
+#include <gmime/gmime.h>        /* g_mime_init */
 
 using namespace std;
 
-#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
-
 typedef struct {
     const char *name;
     const char *prefix;
@@ -49,28 +43,48 @@ typedef struct {
 
 #define NOTMUCH_DATABASE_VERSION 3
 
-#define STRINGIFY(s) _SUB_STRINGIFY(s)
+#define STRINGIFY(s) _SUB_STRINGIFY (s)
 #define _SUB_STRINGIFY(s) #s
 
-#if HAVE_XAPIAN_DB_RETRY_LOCK
-#define DB_ACTION (Xapian::DB_CREATE_OR_OPEN | Xapian::DB_RETRY_LOCK)
-#else
-#define DB_ACTION Xapian::DB_CREATE_OR_OPEN
-#endif
+#define LOG_XAPIAN_EXCEPTION(message, error) _log_xapian_exception (__location__, message, error)
+
+static void
+_log_xapian_exception (const char *where, notmuch_database_t *notmuch,  const Xapian::Error error)
+{
+    _notmuch_database_log (notmuch,
+                          "A Xapian exception occurred at %s: %s\n",
+                          where,
+                          error.get_msg ().c_str ());
+    notmuch->exception_reported = true;
+}
+
+notmuch_database_mode_t
+_notmuch_database_mode (notmuch_database_t *notmuch)
+{
+    if (notmuch->writable_xapian_db)
+       return NOTMUCH_DATABASE_MODE_READ_WRITE;
+    else
+       return NOTMUCH_DATABASE_MODE_READ_ONLY;
+}
 
 /* Here's the current schema for our database (for NOTMUCH_DATABASE_VERSION):
  *
  * We currently have three different types of documents (mail, ghost,
  * and directory) and also some metadata.
  *
+ * There are two kinds of prefixes used in notmuch. There are the
+ * human friendly 'prefix names' like "thread:", which are also used
+ * in the query parser, and the actual prefix terms in the database
+ * (e.g. "G"). The correspondence is maintained in the file scope data
+ * structure 'prefix_table'.
+ *
  * Mail document
  * -------------
  * 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.:
+ * is stored in one or more files on disk 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:
  *
@@ -117,11 +131,16 @@ typedef struct {
  *     LAST_MOD:       The revision number as of the last tag or
  *                     filename change.
  *
- * In addition, terms from the content of the message are added with
- * "from", "to", "attachment", and "subject" prefixes for use by the
- * user in searching. Similarly, terms from the path of the mail
- * message are added with "folder" and "path" prefixes. But the
- * database doesn't really care itself about any of these.
+ * The prefixed terms described above are also searchable without an
+ * explicit field name, but as of notmuch 0.29 this is due to
+ * query-parser setup, not extra terms in the database.  In addition,
+ * terms from the content of the message are added without a prefix
+ * for use by the user in searching. Note that the prefix name "body"
+ * is used to refer to the empty prefix string in the database.
+ *
+ * The path of the containing folder is added with the "folder" prefix
+ * (see _notmuch_message_add_folder_terms).  Sub-paths of the the path
+ * of the mail message are added with the "path" prefix.
  *
  * The data portion of a mail document is empty.
  *
@@ -235,156 +254,13 @@ typedef struct {
  *                     same thread.
  */
 
-/* With these prefix values we follow the conventions published here:
- *
- * https://xapian.org/docs/omega/termprefixes.html
- *
- * as much as makes sense. Note that I took some liberty in matching
- * the reserved prefix values to notmuch concepts, (for example, 'G'
- * is documented as "newsGroup (or similar entity - e.g. a web forum
- * name)", for which I think the thread is the closest analogue in
- * notmuch. This in spite of the fact that we will eventually be
- * storing mailing-list messages where 'G' for "mailing list name"
- * might be even a closer analogue. I'm treating the single-character
- * prefixes preferentially for core notmuch concepts (which will be
- * nearly universal to all mail messages).
- */
-
-static const
-prefix_t prefix_table[] = {
-    /* name                    term prefix     flags */
-    { "type",                  "T",            NOTMUCH_FIELD_NO_FLAGS },
-    { "reference",             "XREFERENCE",   NOTMUCH_FIELD_NO_FLAGS },
-    { "replyto",               "XREPLYTO",     NOTMUCH_FIELD_NO_FLAGS },
-    { "directory",             "XDIRECTORY",   NOTMUCH_FIELD_NO_FLAGS },
-    { "file-direntry",         "XFDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
-    { "directory-direntry",    "XDDIRENTRY",   NOTMUCH_FIELD_NO_FLAGS },
-    { "thread",                        "G",            NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "tag",                   "K",            NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "is",                    "K",            NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "id",                    "Q",            NOTMUCH_FIELD_EXTERNAL },
-    { "mid",                   "Q",            NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "path",                  "P",            NOTMUCH_FIELD_EXTERNAL|
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "property",              "XPROPERTY",    NOTMUCH_FIELD_EXTERNAL },
-    /*
-     * Unconditionally add ':' to reduce potential ambiguity with
-     * overlapping prefixes and/or terms that start with capital
-     * letters. See Xapian document termprefixes.html for related
-     * discussion.
-     */
-    { "folder",                        "XFOLDER:",     NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-#if HAVE_XAPIAN_FIELD_PROCESSOR
-    { "date",                  NULL,           NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "query",                 NULL,           NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROCESSOR },
-#endif
-    { "from",                  "XFROM",        NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROBABILISTIC |
-                                               NOTMUCH_FIELD_PROCESSOR },
-    { "to",                    "XTO",          NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROBABILISTIC },
-    { "attachment",            "XATTACHMENT",  NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROBABILISTIC },
-    { "mimetype",              "XMIMETYPE",    NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROBABILISTIC },
-    { "subject",               "XSUBJECT",     NOTMUCH_FIELD_EXTERNAL |
-                                               NOTMUCH_FIELD_PROBABILISTIC |
-                                               NOTMUCH_FIELD_PROCESSOR},
-};
-
-static void
-_setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch)
-{
-    if (prefix->flags & NOTMUCH_FIELD_PROBABILISTIC)
-       notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
-    else
-       notmuch->query_parser->add_boolean_prefix (prefix->name, prefix->prefix);
-}
-
-#if HAVE_XAPIAN_FIELD_PROCESSOR
-static void
-_setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
-{
-    if (prefix->flags & NOTMUCH_FIELD_PROCESSOR) {
-       Xapian::FieldProcessor *fp;
-
-       if (STRNCMP_LITERAL (prefix->name, "date") == 0)
-           fp = (new DateFieldProcessor())->release ();
-       else if (STRNCMP_LITERAL(prefix->name, "query") == 0)
-           fp = (new QueryFieldProcessor (*notmuch->query_parser, notmuch))->release ();
-       else if (STRNCMP_LITERAL(prefix->name, "thread") == 0)
-           fp = (new ThreadFieldProcessor (*notmuch->query_parser, notmuch))->release ();
-       else
-           fp = (new RegexpFieldProcessor (prefix->name, prefix->flags,
-                                           *notmuch->query_parser, notmuch))->release ();
-
-       /* we treat all field-processor fields as boolean in order to get the raw input */
-       notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
-    } else {
-       _setup_query_field_default (prefix, notmuch);
-    }
-}
-#else
-static inline void
-_setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
-{
-    _setup_query_field_default (prefix, notmuch);
-}
-#endif
 
-const char *
-_find_prefix (const char *name)
+notmuch_string_map_iterator_t *
+_notmuch_database_user_headers (notmuch_database_t *notmuch)
 {
-    unsigned int i;
-
-    for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
-       if (strcmp (name, prefix_table[i].name) == 0)
-           return prefix_table[i].prefix;
-    }
-
-    INTERNAL_ERROR ("No prefix exists for '%s'\n", name);
-
-    return "";
+    return _notmuch_string_map_iterator_create (notmuch->user_header, "", false);
 }
 
-static const struct {
-    /* NOTMUCH_FEATURE_* value. */
-    _notmuch_features value;
-    /* Feature name as it appears in the database.  This name should
-     * be appropriate for displaying to the user if an older version
-     * of notmuch doesn't support this feature. */
-    const char *name;
-    /* Compatibility flags when this feature is declared. */
-    const char *flags;
-} feature_names[] = {
-    { NOTMUCH_FEATURE_FILE_TERMS,
-      "multiple paths per message", "rw" },
-    { NOTMUCH_FEATURE_DIRECTORY_DOCS,
-      "relative directory paths", "rw" },
-    /* Header values are not required for reading a database because a
-     * reader can just refer to the message file. */
-    { NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES,
-      "from/subject/message-ID in database", "w" },
-    { NOTMUCH_FEATURE_BOOL_FOLDER,
-      "exact folder:/path: search", "rw" },
-    { NOTMUCH_FEATURE_GHOSTS,
-      "mail documents for missing messages", "w"},
-    /* Knowledge of the index mime-types are not required for reading
-     * a database because a reader will just be unable to query
-     * them. */
-    { NOTMUCH_FEATURE_INDEXED_MIMETYPES,
-      "indexed MIME types", "w"},
-    { NOTMUCH_FEATURE_LAST_MOD,
-      "modification tracking", "w"},
-};
-
 const char *
 notmuch_status_to_string (notmuch_status_t status)
 {
@@ -417,12 +293,26 @@ notmuch_status_to_string (notmuch_status_t status)
        return "Operation requires a database upgrade";
     case NOTMUCH_STATUS_PATH_ERROR:
        return "Path supplied is illegal for this function";
+    case NOTMUCH_STATUS_IGNORED:
+       return "Argument was ignored";
+    case NOTMUCH_STATUS_ILLEGAL_ARGUMENT:
+       return "Illegal argument for function";
     case NOTMUCH_STATUS_MALFORMED_CRYPTO_PROTOCOL:
        return "Crypto protocol missing, malformed, or unintelligible";
     case NOTMUCH_STATUS_FAILED_CRYPTO_CONTEXT_CREATION:
        return "Crypto engine initialization failure";
     case NOTMUCH_STATUS_UNKNOWN_CRYPTO_PROTOCOL:
        return "Unknown crypto protocol";
+    case NOTMUCH_STATUS_NO_CONFIG:
+       return "No configuration file found";
+    case NOTMUCH_STATUS_NO_DATABASE:
+       return "No database found";
+    case NOTMUCH_STATUS_DATABASE_EXISTS:
+       return "Database exists, not recreated";
+    case NOTMUCH_STATUS_BAD_QUERY_SYNTAX:
+       return "Syntax error in query";
+    case NOTMUCH_STATUS_NO_MAIL_ROOT:
+       return "No mail root found";
     default:
     case NOTMUCH_STATUS_LAST_STATUS:
        return "Unknown error status value";
@@ -431,8 +321,8 @@ notmuch_status_to_string (notmuch_status_t status)
 
 void
 _notmuch_database_log (notmuch_database_t *notmuch,
-                     const char *format,
-                     ...)
+                      const char *format,
+                      ...)
 {
     va_list va_args;
 
@@ -447,8 +337,8 @@ _notmuch_database_log (notmuch_database_t *notmuch,
 
 void
 _notmuch_database_log_append (notmuch_database_t *notmuch,
-                     const char *format,
-                     ...)
+                             const char *format,
+                             ...)
 {
     va_list va_args;
 
@@ -571,119 +461,17 @@ notmuch_database_find_message (notmuch_database_t *notmuch,
        return NOTMUCH_STATUS_SUCCESS;
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred finding message: %s.\n",
-                error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        *message_ret = NULL;
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 }
 
-notmuch_status_t
-notmuch_database_create (const char *path, notmuch_database_t **database)
-{
-    char *status_string = NULL;
-    notmuch_status_t status;
-
-    status = notmuch_database_create_verbose (path, database,
-                                             &status_string);
-
-    if (status_string) {
-       fputs (status_string, stderr);
-       free (status_string);
-    }
-
-    return status;
-}
-
-notmuch_status_t
-notmuch_database_create_verbose (const char *path,
-                                notmuch_database_t **database,
-                                char **status_string)
-{
-    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
-    notmuch_database_t *notmuch = NULL;
-    char *notmuch_path = NULL;
-    char *message = NULL;
-    struct stat st;
-    int err;
-
-    if (path == NULL) {
-       message = strdup ("Error: Cannot create a database for a NULL path.\n");
-       status = NOTMUCH_STATUS_NULL_POINTER;
-       goto DONE;
-    }
-
-    if (path[0] != '/') {
-       message = strdup ("Error: Database path must be absolute.\n");
-       status = NOTMUCH_STATUS_PATH_ERROR;
-       goto DONE;
-    }
-
-    err = stat (path, &st);
-    if (err) {
-       IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: %s.\n",
-                               path, strerror (errno)));
-       status = NOTMUCH_STATUS_FILE_ERROR;
-       goto DONE;
-    }
-
-    if (! S_ISDIR (st.st_mode)) {
-       IGNORE_RESULT (asprintf (&message, "Error: Cannot create database at %s: "
-                                "Not a directory.\n",
-                                path));
-       status = NOTMUCH_STATUS_FILE_ERROR;
-       goto DONE;
-    }
-
-    notmuch_path = talloc_asprintf (NULL, "%s/%s", path, ".notmuch");
-
-    err = mkdir (notmuch_path, 0755);
-
-    if (err) {
-       IGNORE_RESULT (asprintf (&message, "Error: Cannot create directory %s: %s.\n",
-                                notmuch_path, strerror (errno)));
-       status = NOTMUCH_STATUS_FILE_ERROR;
-       goto DONE;
-    }
-
-    status = notmuch_database_open_verbose (path,
-                                           NOTMUCH_DATABASE_MODE_READ_WRITE,
-                                           &notmuch, &message);
-    if (status)
-       goto DONE;
-
-    /* Upgrade doesn't add these feature to existing databases, but
-     * new databases have them. */
-    notmuch->features |= NOTMUCH_FEATURE_FROM_SUBJECT_ID_VALUES;
-    notmuch->features |= NOTMUCH_FEATURE_INDEXED_MIMETYPES;
-
-    status = notmuch_database_upgrade (notmuch, NULL, NULL);
-    if (status) {
-       notmuch_database_close(notmuch);
-       notmuch = NULL;
-    }
-
-  DONE:
-    if (notmuch_path)
-       talloc_free (notmuch_path);
-
-    if (message) {
-       if (status_string)
-           *status_string = message;
-       else
-           free (message);
-    }
-    if (database)
-       *database = notmuch;
-    else
-       talloc_free (notmuch);
-    return status;
-}
-
 notmuch_status_t
 _notmuch_database_ensure_writable (notmuch_database_t *notmuch)
 {
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY) {
+    if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY) {
        _notmuch_database_log (notmuch, "Cannot write to a read-only database.\n");
        return NOTMUCH_STATUS_READ_ONLY_DATABASE;
     }
@@ -708,288 +496,6 @@ _notmuch_database_new_revision (notmuch_database_t *notmuch)
     return new_revision;
 }
 
-/* Parse a database features string from the given database version.
- * Returns the feature bit set.
- *
- * For version < 3, this ignores the features string and returns a
- * hard-coded set of features.
- *
- * If there are unrecognized features that are required to open the
- * database in mode (which should be 'r' or 'w'), return a
- * comma-separated list of unrecognized but required features in
- * *incompat_out suitable for presenting to the user.  *incompat_out
- * will be allocated from ctx.
- */
-static _notmuch_features
-_parse_features (const void *ctx, const char *features, unsigned int version,
-                char mode, char **incompat_out)
-{
-    _notmuch_features res = static_cast<_notmuch_features>(0);
-    unsigned int namelen, i;
-    size_t llen = 0;
-    const char *flags;
-
-    /* Prior to database version 3, features were implied by the
-     * version number. */
-    if (version == 0)
-       return NOTMUCH_FEATURES_V0;
-    else if (version == 1)
-       return NOTMUCH_FEATURES_V1;
-    else if (version == 2)
-       return NOTMUCH_FEATURES_V2;
-
-    /* Parse the features string */
-    while ((features = strtok_len_c (features + llen, "\n", &llen)) != NULL) {
-       flags = strchr (features, '\t');
-       if (! flags || flags > features + llen)
-           continue;
-       namelen = flags - features;
-
-       for (i = 0; i < ARRAY_SIZE (feature_names); ++i) {
-           if (strlen (feature_names[i].name) == namelen &&
-               strncmp (feature_names[i].name, features, namelen) == 0) {
-               res |= feature_names[i].value;
-               break;
-           }
-       }
-
-       if (i == ARRAY_SIZE (feature_names) && incompat_out) {
-           /* Unrecognized feature */
-           const char *have = strchr (flags, mode);
-           if (have && have < features + llen) {
-               /* This feature is required to access this database in
-                * 'mode', but we don't understand it. */
-               if (! *incompat_out)
-                   *incompat_out = talloc_strdup (ctx, "");
-               *incompat_out = talloc_asprintf_append_buffer (
-                   *incompat_out, "%s%.*s", **incompat_out ? ", " : "",
-                   namelen, features);
-           }
-       }
-    }
-
-    return res;
-}
-
-static char *
-_print_features (const void *ctx, unsigned int features)
-{
-    unsigned int i;
-    char *res = talloc_strdup (ctx, "");
-
-    for (i = 0; i < ARRAY_SIZE (feature_names); ++i)
-       if (features & feature_names[i].value)
-           res = talloc_asprintf_append_buffer (
-               res, "%s\t%s\n", feature_names[i].name, feature_names[i].flags);
-
-    return res;
-}
-
-notmuch_status_t
-notmuch_database_open (const char *path,
-                      notmuch_database_mode_t mode,
-                      notmuch_database_t **database)
-{
-    char *status_string = NULL;
-    notmuch_status_t status;
-
-    status = notmuch_database_open_verbose (path, mode, database,
-                                          &status_string);
-
-    if (status_string) {
-       fputs (status_string, stderr);
-       free (status_string);
-    }
-
-    return status;
-}
-
-notmuch_status_t
-notmuch_database_open_verbose (const char *path,
-                              notmuch_database_mode_t mode,
-                              notmuch_database_t **database,
-                              char **status_string)
-{
-    notmuch_status_t status = NOTMUCH_STATUS_SUCCESS;
-    void *local = talloc_new (NULL);
-    notmuch_database_t *notmuch = NULL;
-    char *notmuch_path, *xapian_path, *incompat_features;
-    char *message = NULL;
-    struct stat st;
-    int err;
-    unsigned int i, version;
-    static int initialized = 0;
-
-    if (path == NULL) {
-       message = strdup ("Error: Cannot open a database for a NULL path.\n");
-       status = NOTMUCH_STATUS_NULL_POINTER;
-       goto DONE;
-    }
-
-    if (path[0] != '/') {
-       message = strdup ("Error: Database path must be absolute.\n");
-       status = NOTMUCH_STATUS_PATH_ERROR;
-       goto DONE;
-    }
-
-    if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
-       message = strdup ("Out of memory\n");
-       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
-       goto DONE;
-    }
-
-    err = stat (notmuch_path, &st);
-    if (err) {
-       IGNORE_RESULT (asprintf (&message, "Error opening database at %s: %s\n",
-                                notmuch_path, strerror (errno)));
-       status = NOTMUCH_STATUS_FILE_ERROR;
-       goto DONE;
-    }
-
-    if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
-       message = strdup ("Out of memory\n");
-       status = NOTMUCH_STATUS_OUT_OF_MEMORY;
-       goto DONE;
-    }
-
-    /* Initialize the GLib type system and threads */
-#if !GLIB_CHECK_VERSION(2, 35, 1)
-    g_type_init ();
-#endif
-
-    /* Initialize gmime */
-    if (! initialized) {
-       g_mime_init (GMIME_ENABLE_RFC2047_WORKAROUNDS);
-       initialized = 1;
-    }
-
-    notmuch = talloc_zero (NULL, notmuch_database_t);
-    notmuch->exception_reported = false;
-    notmuch->status_string = NULL;
-    notmuch->path = talloc_strdup (notmuch, path);
-
-    strip_trailing(notmuch->path, '/');
-
-    notmuch->mode = mode;
-    notmuch->atomic_nesting = 0;
-    notmuch->view = 1;
-    try {
-       string last_thread_id;
-       string last_mod;
-
-       if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
-           notmuch->xapian_db = new Xapian::WritableDatabase (xapian_path,
-                                                              DB_ACTION);
-       } else {
-           notmuch->xapian_db = new Xapian::Database (xapian_path);
-       }
-
-       /* Check version.  As of database version 3, we represent
-        * changes in terms of features, so assume a version bump
-        * means a dramatically incompatible change. */
-       version = notmuch_database_get_version (notmuch);
-       if (version > NOTMUCH_DATABASE_VERSION) {
-           IGNORE_RESULT (asprintf (&message,
-                     "Error: Notmuch database at %s\n"
-                     "       has a newer database format version (%u) than supported by this\n"
-                     "       version of notmuch (%u).\n",
-                                    notmuch_path, version, NOTMUCH_DATABASE_VERSION));
-           notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
-           notmuch_database_destroy (notmuch);
-           notmuch = NULL;
-           status = NOTMUCH_STATUS_FILE_ERROR;
-           goto DONE;
-       }
-
-       /* Check features. */
-       incompat_features = NULL;
-       notmuch->features = _parse_features (
-           local, notmuch->xapian_db->get_metadata ("features").c_str (),
-           version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
-           &incompat_features);
-       if (incompat_features) {
-           IGNORE_RESULT (asprintf (&message,
-               "Error: Notmuch database at %s\n"
-               "       requires features (%s)\n"
-               "       not supported by this version of notmuch.\n",
-                                    notmuch_path, incompat_features));
-           notmuch->mode = NOTMUCH_DATABASE_MODE_READ_ONLY;
-           notmuch_database_destroy (notmuch);
-           notmuch = NULL;
-           status = NOTMUCH_STATUS_FILE_ERROR;
-           goto DONE;
-       }
-
-       notmuch->last_doc_id = notmuch->xapian_db->get_lastdocid ();
-       last_thread_id = notmuch->xapian_db->get_metadata ("last_thread_id");
-       if (last_thread_id.empty ()) {
-           notmuch->last_thread_id = 0;
-       } else {
-           const char *str;
-           char *end;
-
-           str = last_thread_id.c_str ();
-           notmuch->last_thread_id = strtoull (str, &end, 16);
-           if (*end != '\0')
-               INTERNAL_ERROR ("Malformed database last_thread_id: %s", str);
-       }
-
-       /* Get current highest revision number. */
-       last_mod = notmuch->xapian_db->get_value_upper_bound (
-           NOTMUCH_VALUE_LAST_MOD);
-       if (last_mod.empty ())
-           notmuch->revision = 0;
-       else
-           notmuch->revision = Xapian::sortable_unserialise (last_mod);
-       notmuch->uuid = talloc_strdup (
-           notmuch, notmuch->xapian_db->get_uuid ().c_str ());
-
-       notmuch->query_parser = new Xapian::QueryParser;
-       notmuch->term_gen = new Xapian::TermGenerator;
-       notmuch->term_gen->set_stemmer (Xapian::Stem ("english"));
-       notmuch->value_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
-       notmuch->date_range_processor = new ParseTimeValueRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
-       notmuch->last_mod_range_processor = new Xapian::NumberValueRangeProcessor (NOTMUCH_VALUE_LAST_MOD, "lastmod:");
-
-       notmuch->query_parser->set_default_op (Xapian::Query::OP_AND);
-       notmuch->query_parser->set_database (*notmuch->xapian_db);
-       notmuch->query_parser->set_stemmer (Xapian::Stem ("english"));
-       notmuch->query_parser->set_stemming_strategy (Xapian::QueryParser::STEM_SOME);
-       notmuch->query_parser->add_valuerangeprocessor (notmuch->value_range_processor);
-       notmuch->query_parser->add_valuerangeprocessor (notmuch->date_range_processor);
-       notmuch->query_parser->add_valuerangeprocessor (notmuch->last_mod_range_processor);
-
-       for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
-           const prefix_t *prefix = &prefix_table[i];
-           if (prefix->flags & NOTMUCH_FIELD_EXTERNAL) {
-               _setup_query_field (prefix, notmuch);
-           }
-       }
-    } catch (const Xapian::Error &error) {
-       IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
-                                error.get_msg().c_str()));
-       notmuch_database_destroy (notmuch);
-       notmuch = NULL;
-       status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
-    }
-
-  DONE:
-    talloc_free (local);
-
-    if (message) {
-       if (status_string)
-           *status_string = message;
-       else
-           free (message);
-    }
-
-    if (database)
-       *database = notmuch;
-    else
-       talloc_free (notmuch);
-    return status;
-}
-
 notmuch_status_t
 notmuch_database_close (notmuch_database_t *notmuch)
 {
@@ -998,68 +504,25 @@ notmuch_database_close (notmuch_database_t *notmuch)
     /* Many Xapian objects (and thus notmuch objects) hold references to
      * the database, so merely deleting the database may not suffice to
      * close it.  Thus, we explicitly close it here. */
-    if (notmuch->xapian_db != NULL) {
+    if (notmuch->open) {
        try {
-           /* If there's an outstanding transaction, it's unclear if
-            * closing the Xapian database commits everything up to
-            * that transaction, or may discard committed (but
-            * unflushed) transactions.  To be certain, explicitly
-            * cancel any outstanding transaction before closing. */
-           if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE &&
-               notmuch->atomic_nesting)
-               (static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))
-                   ->cancel_transaction ();
-
            /* Close the database.  This implicitly flushes
-            * outstanding changes. */
-           notmuch->xapian_db->close();
+            * outstanding changes. If there is an open (non-flushed)
+            * transaction, ALL pending changes will be discarded */
+           notmuch->xapian_db->close ();
        } catch (const Xapian::Error &error) {
            status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
            if (! notmuch->exception_reported) {
-               _notmuch_database_log (notmuch, "Error: A Xapian exception occurred closing database: %s\n",
-                        error.get_msg().c_str());
+               _notmuch_database_log (notmuch,
+                                      "Error: A Xapian exception occurred closing database: %s\n",
+                                      error.get_msg ().c_str ());
            }
        }
     }
-
-    delete notmuch->term_gen;
-    notmuch->term_gen = NULL;
-    delete notmuch->query_parser;
-    notmuch->query_parser = NULL;
-    delete notmuch->xapian_db;
-    notmuch->xapian_db = NULL;
-    delete notmuch->value_range_processor;
-    notmuch->value_range_processor = NULL;
-    delete notmuch->date_range_processor;
-    notmuch->date_range_processor = NULL;
-    delete notmuch->last_mod_range_processor;
-    notmuch->last_mod_range_processor = NULL;
-
+    notmuch->open = false;
     return status;
 }
 
-notmuch_status_t
-_notmuch_database_reopen (notmuch_database_t *notmuch)
-{
-    if (notmuch->mode != NOTMUCH_DATABASE_MODE_READ_ONLY)
-       return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;
-
-    try {
-       notmuch->xapian_db->reopen ();
-    } catch (const Xapian::Error &error) {
-       if (! notmuch->exception_reported) {
-           _notmuch_database_log (notmuch, "Error: A Xapian exception reopening database: %s\n",
-                                  error.get_msg ().c_str ());
-           notmuch->exception_reported = true;
-       }
-       return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
-    }
-
-    notmuch->view++;
-
-    return NOTMUCH_STATUS_SUCCESS;
-}
-
 static int
 unlink_cb (const char *path,
           unused (const struct stat *sb),
@@ -1082,7 +545,9 @@ class NotmuchCompactor : public Xapian::Compactor
 
 public:
     NotmuchCompactor(notmuch_compact_status_cb_t cb, void *closure) :
-       status_cb (cb), status_closure (closure) { }
+       status_cb (cb), status_closure (closure)
+    {
+    }
 
     virtual void
     set_status (const std::string &table, const std::string &status)
@@ -1093,9 +558,9 @@ public:
            return;
 
        if (status.length () == 0)
-           msg = talloc_asprintf (NULL, "compacting table %s", table.c_str());
+           msg = talloc_asprintf (NULL, "compacting table %s", table.c_str ());
        else
-           msg = talloc_asprintf (NULL, "     %s", status.c_str());
+           msg = talloc_asprintf (NULL, "     %s", status.c_str ());
 
        if (msg == NULL) {
            return;
@@ -1123,36 +588,58 @@ notmuch_database_compact (const char *path,
                          notmuch_compact_status_cb_t status_cb,
                          void *closure)
 {
-    void *local;
-    char *notmuch_path, *xapian_path, *compact_xapian_path;
     notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
     notmuch_database_t *notmuch = NULL;
-    struct stat statbuf;
-    bool keep_backup;
     char *message = NULL;
 
-    local = talloc_new (NULL);
-    if (! local)
-       return NOTMUCH_STATUS_OUT_OF_MEMORY;
-
-    ret = notmuch_database_open_verbose (path,
-                                        NOTMUCH_DATABASE_MODE_READ_WRITE,
-                                        &notmuch,
-                                        &message);
+    ret = notmuch_database_open_with_config (path,
+                                            NOTMUCH_DATABASE_MODE_READ_WRITE,
+                                            "",
+                                            NULL,
+                                            &notmuch,
+                                            &message);
     if (ret) {
        if (status_cb) status_cb (message, closure);
-       goto DONE;
+       return ret;
     }
 
-    if (! (notmuch_path = talloc_asprintf (local, "%s/%s", path, ".notmuch"))) {
-       ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
-       goto DONE;
-    }
+    _notmuch_config_cache (notmuch, NOTMUCH_CONFIG_DATABASE_PATH, path);
 
-    if (! (xapian_path = talloc_asprintf (local, "%s/%s", notmuch_path, "xapian"))) {
-       ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
+    return notmuch_database_compact_db (notmuch,
+                                       backup_path,
+                                       status_cb,
+                                       closure);
+}
+
+notmuch_status_t
+notmuch_database_compact_db (notmuch_database_t *notmuch,
+                            const char *backup_path,
+                            notmuch_compact_status_cb_t status_cb,
+                            void *closure)
+{
+    void *local;
+    const char *xapian_path, *compact_xapian_path;
+    const char *path;
+    notmuch_status_t ret = NOTMUCH_STATUS_SUCCESS;
+    struct stat statbuf;
+    bool keep_backup;
+    char *message;
+
+    ret = _notmuch_database_ensure_writable (notmuch);
+    if (ret)
+       return ret;
+
+    path = notmuch_config_get (notmuch, NOTMUCH_CONFIG_DATABASE_PATH);
+    if (! path)
+       return NOTMUCH_STATUS_PATH_ERROR;
+
+    local = talloc_new (NULL);
+    if (! local)
+       return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+    ret = _notmuch_choose_xapian_path (local, path, &xapian_path, &message);
+    if (ret)
        goto DONE;
-    }
 
     if (! (compact_xapian_path = talloc_asprintf (local, "%s.compact", xapian_path))) {
        ret = NOTMUCH_STATUS_OUT_OF_MEMORY;
@@ -1165,8 +652,7 @@ notmuch_database_compact (const char *path,
            goto DONE;
        }
        keep_backup = false;
-    }
-    else {
+    } else {
        keep_backup = true;
     }
 
@@ -1177,7 +663,7 @@ notmuch_database_compact (const char *path,
     }
     if (errno != ENOENT) {
        _notmuch_database_log (notmuch, "Unknown error while stat()ing path: %s\n",
-                strerror (errno));
+                              strerror (errno));
        ret = NOTMUCH_STATUS_FILE_ERROR;
        goto DONE;
     }
@@ -1190,27 +676,24 @@ notmuch_database_compact (const char *path,
 
     try {
        NotmuchCompactor compactor (status_cb, closure);
-
-       compactor.set_renumber (false);
-       compactor.add_source (xapian_path);
-       compactor.set_destdir (compact_xapian_path);
-       compactor.compact ();
+       notmuch->xapian_db->compact (compact_xapian_path, Xapian::DBCOMPACT_NO_RENUMBER, 0,
+                                    compactor);
     } catch (const Xapian::Error &error) {
-       _notmuch_database_log (notmuch, "Error while compacting: %s\n", error.get_msg().c_str());
+       _notmuch_database_log (notmuch, "Error while compacting: %s\n", error.get_msg ().c_str ());
        ret = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
        goto DONE;
     }
 
     if (rename (xapian_path, backup_path)) {
        _notmuch_database_log (notmuch, "Error moving %s to %s: %s\n",
-                xapian_path, backup_path, strerror (errno));
+                              xapian_path, backup_path, strerror (errno));
        ret = NOTMUCH_STATUS_FILE_ERROR;
        goto DONE;
     }
 
     if (rename (compact_xapian_path, xapian_path)) {
        _notmuch_database_log (notmuch, "Error moving %s to %s: %s\n",
-                compact_xapian_path, xapian_path, strerror (errno));
+                              compact_xapian_path, xapian_path, strerror (errno));
        ret = NOTMUCH_STATUS_FILE_ERROR;
        goto DONE;
     }
@@ -1218,7 +701,7 @@ notmuch_database_compact (const char *path,
     if (! keep_backup) {
        if (rmtree (backup_path)) {
            _notmuch_database_log (notmuch, "Error removing old database %s: %s\n",
-                    backup_path, strerror (errno));
+                                  backup_path, strerror (errno));
            ret = NOTMUCH_STATUS_FILE_ERROR;
            goto DONE;
        }
@@ -1248,8 +731,33 @@ notmuch_status_t
 notmuch_database_destroy (notmuch_database_t *notmuch)
 {
     notmuch_status_t status;
+    const char *talloc_report;
+
+    talloc_report = getenv ("NOTMUCH_TALLOC_REPORT");
+    if (talloc_report && strcmp (talloc_report, "") != 0) {
+       FILE *report = fopen (talloc_report, "a");
+       if (report) {
+           talloc_report_full (notmuch, report);
+       }
+    }
 
     status = notmuch_database_close (notmuch);
+
+    delete notmuch->term_gen;
+    notmuch->term_gen = NULL;
+    delete notmuch->query_parser;
+    notmuch->query_parser = NULL;
+    delete notmuch->xapian_db;
+    notmuch->xapian_db = NULL;
+    delete notmuch->value_range_processor;
+    notmuch->value_range_processor = NULL;
+    delete notmuch->date_range_processor;
+    notmuch->date_range_processor = NULL;
+    delete notmuch->last_mod_range_processor;
+    notmuch->last_mod_range_processor = NULL;
+    delete notmuch->stemmer;
+    notmuch->stemmer = NULL;
+
     talloc_free (notmuch);
 
     return status;
@@ -1258,7 +766,7 @@ notmuch_database_destroy (notmuch_database_t *notmuch)
 const char *
 notmuch_database_get_path (notmuch_database_t *notmuch)
 {
-    return notmuch->path;
+    return notmuch_config_get (notmuch, NOTMUCH_CONFIG_DATABASE_PATH);
 }
 
 unsigned int
@@ -1269,7 +777,13 @@ notmuch_database_get_version (notmuch_database_t *notmuch)
     const char *str;
     char *end;
 
-    version_string = notmuch->xapian_db->get_metadata ("version");
+    try {
+       version_string = notmuch->xapian_db->get_metadata ("version");
+    } catch (const Xapian::Error &error) {
+       LOG_XAPIAN_EXCEPTION (notmuch, error);
+       return 0;
+    }
+
     if (version_string.empty ())
        return 0;
 
@@ -1287,9 +801,17 @@ notmuch_database_get_version (notmuch_database_t *notmuch)
 notmuch_bool_t
 notmuch_database_needs_upgrade (notmuch_database_t *notmuch)
 {
-    return notmuch->mode == NOTMUCH_DATABASE_MODE_READ_WRITE &&
-       ((NOTMUCH_FEATURES_CURRENT & ~notmuch->features) ||
-        (notmuch_database_get_version (notmuch) < NOTMUCH_DATABASE_VERSION));
+    unsigned int version;
+
+    if (_notmuch_database_mode (notmuch) != NOTMUCH_DATABASE_MODE_READ_WRITE)
+       return FALSE;
+
+    if (NOTMUCH_FEATURES_CURRENT & ~notmuch->features)
+       return TRUE;
+
+    version = notmuch_database_get_version (notmuch);
+
+    return (version > 0 && version < NOTMUCH_DATABASE_VERSION);
 }
 
 static volatile sig_atomic_t do_progress_notify = 0;
@@ -1314,8 +836,8 @@ handle_sigalrm (unused (int signal))
  */
 notmuch_status_t
 notmuch_database_upgrade (notmuch_database_t *notmuch,
-                         void (*progress_notify) (void *closure,
-                                                  double progress),
+                         void (*progress_notify)(void *closure,
+                                                 double progress),
                          void *closure)
 {
     void *local = talloc_new (NULL);
@@ -1334,7 +856,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
     if (status)
        return status;
 
-    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+    db = notmuch->writable_xapian_db;
 
     target_features = notmuch->features | NOTMUCH_FEATURES_CURRENT;
     new_features = NOTMUCH_FEATURES_CURRENT & ~notmuch->features;
@@ -1410,8 +932,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
            goto DONE;
        for (;
             notmuch_messages_valid (messages);
-            notmuch_messages_move_to_next (messages))
-       {
+            notmuch_messages_move_to_next (messages)) {
            if (do_progress_notify) {
                progress_notify (closure, (double) count / total);
                do_progress_notify = 0;
@@ -1468,8 +989,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
 
        for (t = notmuch->xapian_db->allterms_begin ("XTIMESTAMP");
             t != t_end;
-            t++)
-       {
+            t++) {
            Xapian::PostingIterator p, p_end;
            std::string term = *t;
 
@@ -1477,8 +997,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
 
            for (p = notmuch->xapian_db->postlist_begin (term);
                 p != p_end;
-                p++)
-           {
+                p++) {
                Xapian::Document document;
                time_t mtime;
                notmuch_directory_t *directory;
@@ -1492,8 +1011,8 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
                mtime = Xapian::sortable_unserialise (
                    document.get_value (NOTMUCH_VALUE_TIMESTAMP));
 
-               directory = _notmuch_directory_create (notmuch, term.c_str() + 10,
-                                                      NOTMUCH_FIND_CREATE, &status);
+               directory = _notmuch_directory_find_or_create (notmuch, term.c_str () + 10,
+                                                              NOTMUCH_FIND_CREATE, &status);
                notmuch_directory_set_mtime (directory, mtime);
                notmuch_directory_destroy (directory);
 
@@ -1541,8 +1060,9 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
 
            if (private_status) {
                _notmuch_database_log (notmuch,
-                        "Upgrade failed while creating ghost messages.\n");
-               status = COERCE_STATUS (private_status, "Unexpected status from _notmuch_message_initialize_ghost");
+                                      "Upgrade failed while creating ghost messages.\n");
+               status = COERCE_STATUS (private_status,
+                                       "Unexpected status from _notmuch_message_initialize_ghost");
                goto DONE;
            }
 
@@ -1554,10 +1074,10 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
     }
 
     status = NOTMUCH_STATUS_SUCCESS;
-    db->set_metadata ("features", _print_features (local, notmuch->features));
+    db->set_metadata ("features", _notmuch_database_print_features (local, notmuch->features));
     db->set_metadata ("version", STRINGIFY (NOTMUCH_DATABASE_VERSION));
 
- DONE:
 DONE:
     if (status == NOTMUCH_STATUS_SUCCESS)
        db->commit_transaction ();
     else
@@ -1586,7 +1106,7 @@ notmuch_database_upgrade (notmuch_database_t *notmuch,
 notmuch_status_t
 notmuch_database_begin_atomic (notmuch_database_t *notmuch)
 {
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY ||
+    if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY ||
        notmuch->atomic_nesting > 0)
        goto DONE;
 
@@ -1594,15 +1114,15 @@ notmuch_database_begin_atomic (notmuch_database_t *notmuch)
        return NOTMUCH_STATUS_UPGRADE_REQUIRED;
 
     try {
-       (static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))->begin_transaction (false);
+       notmuch->writable_xapian_db->begin_transaction (false);
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred beginning transaction: %s.\n",
-                error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
 
-DONE:
+  DONE:
     notmuch->atomic_nesting++;
     return NOTMUCH_STATUS_SUCCESS;
 }
@@ -1615,23 +1135,31 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch)
     if (notmuch->atomic_nesting == 0)
        return NOTMUCH_STATUS_UNBALANCED_ATOMIC;
 
-    if (notmuch->mode == NOTMUCH_DATABASE_MODE_READ_ONLY ||
+    if (_notmuch_database_mode (notmuch) == NOTMUCH_DATABASE_MODE_READ_ONLY ||
        notmuch->atomic_nesting > 1)
        goto DONE;
 
-    db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
+    db = notmuch->writable_xapian_db;
     try {
        db->commit_transaction ();
-
-       /* This is a hack for testing.  Xapian never flushes on a
-        * non-flushed commit, even if the flush threshold is 1.
-        * However, we rely on flushing to test atomicity. */
+       notmuch->transaction_count++;
+
+       /* Xapian never flushes on a non-flushed commit, even if the
+        * flush threshold is 1.  However, we rely on flushing to test
+        * atomicity. On the other hand, we can't straight replace
+        * XAPIAN_FLUSH_THRESHOLD with our autocommit counter, because
+        * the former also applies outside notmuch atomic
+        * commits. Hence the follow complicated  test */
        const char *thresh = getenv ("XAPIAN_FLUSH_THRESHOLD");
-       if (thresh && atoi (thresh) == 1)
+       if ((notmuch->transaction_threshold > 0 &&
+            notmuch->transaction_count >= notmuch->transaction_threshold) ||
+           (thresh && atoi (thresh) == 1)) {
            db->commit ();
+           notmuch->transaction_count = 0;
+       }
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred committing transaction: %s.\n",
-                error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        return NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
@@ -1641,14 +1169,14 @@ notmuch_database_end_atomic (notmuch_database_t *notmuch)
        notmuch->atomic_dirty = false;
     }
 
-DONE:
+  DONE:
     notmuch->atomic_nesting--;
     return NOTMUCH_STATUS_SUCCESS;
 }
 
 unsigned long
 notmuch_database_get_revision (notmuch_database_t *notmuch,
-                               const char **uuid)
+                              const char **uuid)
 {
     if (uuid)
        *uuid = notmuch->uuid;
@@ -1765,8 +1293,8 @@ _notmuch_database_find_directory_id (notmuch_database_t *notmuch,
        return NOTMUCH_STATUS_SUCCESS;
     }
 
-    directory = _notmuch_directory_create (notmuch, path, flags, &status);
-    if (status || !directory) {
+    directory = _notmuch_directory_find_or_create (notmuch, path, flags, &status);
+    if (status || ! directory) {
        *directory_id = -1;
        return status;
     }
@@ -1820,7 +1348,7 @@ _notmuch_database_filename_to_direntry (void *ctx,
 
     status = _notmuch_database_find_directory_id (notmuch, directory, flags,
                                                  &directory_id);
-    if (status || directory_id == (unsigned int)-1) {
+    if (status || directory_id == (unsigned int) -1) {
        *direntry = NULL;
        return status;
     }
@@ -1844,17 +1372,16 @@ _notmuch_database_relative_path (notmuch_database_t *notmuch,
     const char *db_path, *relative;
     unsigned int db_path_len;
 
-    db_path = notmuch_database_get_path (notmuch);
+    db_path = notmuch_config_get (notmuch, NOTMUCH_CONFIG_MAIL_ROOT);
     db_path_len = strlen (db_path);
 
     relative = path;
 
     if (*relative == '/') {
-       while (*relative == '/' && *(relative+1) == '/')
+       while (*relative == '/' && *(relative + 1) == '/')
            relative++;
 
-       if (strncmp (relative, db_path, db_path_len) == 0)
-       {
+       if (strncmp (relative, db_path, db_path_len) == 0) {
            relative += db_path_len;
            while (*relative == '/')
                relative++;
@@ -1876,11 +1403,11 @@ notmuch_database_get_directory (notmuch_database_t *notmuch,
     *directory = NULL;
 
     try {
-       *directory = _notmuch_directory_create (notmuch, path,
-                                               NOTMUCH_FIND_LOOKUP, &status);
+       *directory = _notmuch_directory_find_or_create (notmuch, path,
+                                                       NOTMUCH_FIND_LOOKUP, &status);
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (notmuch, "A Xapian exception occurred getting directory: %s.\n",
-                error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
@@ -1923,13 +1450,13 @@ notmuch_database_remove_message (notmuch_database_t *notmuch,
                                                        &message);
 
     if (status == NOTMUCH_STATUS_SUCCESS && message) {
-           status = _notmuch_message_remove_filename (message, filename);
-           if (status == NOTMUCH_STATUS_SUCCESS)
-               _notmuch_message_delete (message);
-           else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
-               _notmuch_message_sync (message);
+       status = _notmuch_message_remove_filename (message, filename);
+       if (status == NOTMUCH_STATUS_SUCCESS)
+           _notmuch_message_delete (message);
+       else if (status == NOTMUCH_STATUS_DUPLICATE_MESSAGE_ID)
+           _notmuch_message_sync (message);
 
-           notmuch_message_destroy (message);
+       notmuch_message_destroy (message);
     }
 
     return status;
@@ -1960,7 +1487,7 @@ notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,
     try {
        status = _notmuch_database_filename_to_direntry (
            local, notmuch, filename, NOTMUCH_FIND_LOOKUP, &direntry);
-       if (status || !direntry)
+       if (status || ! direntry)
            goto DONE;
 
        term = talloc_asprintf (local, "%s%s", prefix, direntry);
@@ -1976,8 +1503,9 @@ notmuch_database_find_message_by_filename (notmuch_database_t *notmuch,
                status = NOTMUCH_STATUS_OUT_OF_MEMORY;
        }
     } catch (const Xapian::Error &error) {
-       _notmuch_database_log (notmuch, "Error: A Xapian exception occurred finding message by filename: %s\n",
-                error.get_msg().c_str());
+       _notmuch_database_log (notmuch,
+                              "Error: A Xapian exception occurred finding message by filename: %s\n",
+                              error.get_msg ().c_str ());
        notmuch->exception_reported = true;
        status = NOTMUCH_STATUS_XAPIAN_EXCEPTION;
     }
@@ -2022,15 +1550,15 @@ notmuch_database_get_all_tags (notmuch_database_t *db)
     notmuch_string_list_t *tags;
 
     try {
-       i = db->xapian_db->allterms_begin();
-       end = db->xapian_db->allterms_end();
+       i = db->xapian_db->allterms_begin ();
+       end = db->xapian_db->allterms_end ();
        tags = _notmuch_database_get_terms_with_prefix (db, i, end,
                                                        _find_prefix ("tag"));
        _notmuch_string_list_sort (tags);
        return _notmuch_tags_create (db, tags);
     } catch (const Xapian::Error &error) {
        _notmuch_database_log (db, "A Xapian exception occurred getting tags: %s.\n",
-                error.get_msg().c_str());
+                              error.get_msg ().c_str ());
        db->exception_reported = true;
        return NULL;
     }