unofficial mirror of notmuch@notmuchmail.org
 help / color / mirror / code / Atom feed
* preliminaries for merged config
@ 2020-08-12 22:49 David Bremner
  2020-08-12 22:49 ` [PATCH 1/6] test: use keys with group 'test' in T590-libconfig David Bremner
                   ` (5 more replies)
  0 siblings, 6 replies; 8+ messages in thread
From: David Bremner @ 2020-08-12 22:49 UTC (permalink / raw)
  To: notmuch

This collects all of the patches from id:20200808141653.1124111-1-david@tethera.net that
are reasonable changes on their own, without wading into the new API design.

Two updates to tests

[PATCH 1/6] test: use keys with group 'test' in T590-libconfig
[PATCH 5/6] test: add regression test for searching with alternate

Three patches breaking up database.cc

[PATCH 2/6] lib: factor out feature name related code.
[PATCH 3/6] lib: factor out prefix related code to its own file
[PATCH 6/6] lib: factor out notmuch_database_open* related code to

One genuine (if a bit obscure) bug fix:

[PATCH 4/6] lib/config: delay setting talloc destructor

^ permalink raw reply	[flat|nested] 8+ messages in thread

* [PATCH 1/6] test: use keys with group 'test' in T590-libconfig
  2020-08-12 22:49 preliminaries for merged config David Bremner
@ 2020-08-12 22:49 ` David Bremner
  2020-08-12 22:49 ` [PATCH 2/6] lib: factor out feature name related code David Bremner
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: David Bremner @ 2020-08-12 22:49 UTC (permalink / raw)
  To: notmuch; +Cc: David Bremner

In a future commit we want to interoperate better with glib KeyFiles,
which need groups for all keys.
---
 test/T590-libconfig.sh | 36 ++++++++++++++++++------------------
 1 file changed, 18 insertions(+), 18 deletions(-)

diff --git a/test/T590-libconfig.sh b/test/T590-libconfig.sh
index 360e45b0..8c34acf9 100755
--- a/test/T590-libconfig.sh
+++ b/test/T590-libconfig.sh
@@ -28,18 +28,18 @@ EOF
 test_begin_subtest "notmuch_database_{set,get}_config"
 cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
 {
-   EXPECT0(notmuch_database_set_config (db, "testkey1", "testvalue1"));
-   EXPECT0(notmuch_database_set_config (db, "testkey2", "testvalue2"));
-   EXPECT0(notmuch_database_get_config (db, "testkey1", &val));
-   printf("testkey1 = %s\n", val);
-   EXPECT0(notmuch_database_get_config (db, "testkey2", &val));
-   printf("testkey2 = %s\n", val);
+   EXPECT0(notmuch_database_set_config (db, "test.key1", "testvalue1"));
+   EXPECT0(notmuch_database_set_config (db, "test.key2", "testvalue2"));
+   EXPECT0(notmuch_database_get_config (db, "test.key1", &val));
+   printf("test.key1 = %s\n", val);
+   EXPECT0(notmuch_database_get_config (db, "test.key2", &val));
+   printf("test.key2 = %s\n", val);
 }
 EOF
 cat <<'EOF' >EXPECTED
 == stdout ==
-testkey1 = testvalue1
-testkey2 = testvalue2
+test.key1 = testvalue1
+test.key2 = testvalue2
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT
@@ -93,8 +93,8 @@ EOF
 cat <<'EOF' >EXPECTED
 == stdout ==
 aaabefore beforeval
-testkey1 testvalue1
-testkey2 testvalue2
+test.key1 testvalue1
+test.key2 testvalue2
 zzzafter afterval
 == stderr ==
 EOF
@@ -115,8 +115,8 @@ EOF
 cat <<'EOF' >EXPECTED
 == stdout ==
 aaabefore 1
-testkey1 1
-testkey2 1
+test.key1 1
+test.key2 1
 zzzafter 1
 == stderr ==
 EOF
@@ -126,7 +126,7 @@ test_begin_subtest "notmuch_database_get_config_list: one prefix"
 cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
 {
    notmuch_config_list_t *list;
-   EXPECT0(notmuch_database_get_config_list (db, "testkey", &list));
+   EXPECT0(notmuch_database_get_config_list (db, "test.key", &list));
    for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
       printf("%s %s\n", notmuch_config_list_key (list), notmuch_config_list_value(list));
    }
@@ -135,8 +135,8 @@ cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
 EOF
 cat <<'EOF' >EXPECTED
 == stdout ==
-testkey1 testvalue1
-testkey2 testvalue2
+test.key1 testvalue1
+test.key2 testvalue2
 == stderr ==
 EOF
 test_expect_equal_file EXPECTED OUTPUT
@@ -152,8 +152,8 @@ cat <<'EOF' >EXPECTED
 #notmuch-dump batch-tag:3 config
 #@ aaabefore beforeval
 #@ key%20with%20spaces value,%20with,%20spaces%21
-#@ testkey1 testvalue1
-#@ testkey2 testvalue2
+#@ test.key1 testvalue1
+#@ test.key2 testvalue2
 #@ zzzafter afterval
 EOF
 test_expect_equal_file EXPECTED OUTPUT
@@ -162,7 +162,7 @@ test_begin_subtest "restore config"
 notmuch dump --include=config >EXPECTED
 cat c_head - c_tail <<'EOF' | test_C ${MAIL_DIR}
 {
-    EXPECT0(notmuch_database_set_config (db, "testkey1", "mutatedvalue"));
+    EXPECT0(notmuch_database_set_config (db, "test.key1", "mutatedvalue"));
 }
 EOF
 notmuch restore --include=config <EXPECTED
-- 
2.28.0

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/6] lib: factor out feature name related code.
  2020-08-12 22:49 preliminaries for merged config David Bremner
  2020-08-12 22:49 ` [PATCH 1/6] test: use keys with group 'test' in T590-libconfig David Bremner
@ 2020-08-12 22:49 ` David Bremner
  2020-08-12 22:49 ` [PATCH 3/6] lib: factor out prefix related code to its own file David Bremner
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: David Bremner @ 2020-08-12 22:49 UTC (permalink / raw)
  To: notmuch; +Cc: David Bremner

database.cc is uncomfortably large, and some of the static data
structures do not need to be shared as much as they are.

This is a somewhat small piece to factor out, but it will turn out to
be helpful to further refactoring.
---
 lib/Makefile.local     |   3 +-
 lib/database-private.h |  14 +++++
 lib/database.cc        | 118 +----------------------------------------
 lib/features.cc        | 114 +++++++++++++++++++++++++++++++++++++++
 4 files changed, 132 insertions(+), 117 deletions(-)
 create mode 100644 lib/features.cc

diff --git a/lib/Makefile.local b/lib/Makefile.local
index a6400126..04418fa8 100644
--- a/lib/Makefile.local
+++ b/lib/Makefile.local
@@ -59,7 +59,8 @@ libnotmuch_cxx_srcs =		\
 	$(dir)/config.cc	\
 	$(dir)/regexp-fields.cc	\
 	$(dir)/thread.cc \
-	$(dir)/thread-fp.cc
+	$(dir)/thread-fp.cc     \
+	$(dir)/features.cc
 
 libnotmuch_modules := $(libnotmuch_c_srcs:.c=.o) $(libnotmuch_cxx_srcs:.cc=.o)
 
diff --git a/lib/database-private.h b/lib/database-private.h
index 041602cd..2d220811 100644
--- a/lib/database-private.h
+++ b/lib/database-private.h
@@ -32,6 +32,8 @@
 
 #include "notmuch-private.h"
 
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
 #ifdef SILENCE_XAPIAN_DEPRECATION_WARNINGS
 #define XAPIAN_DEPRECATED(D) D
 #endif
@@ -263,4 +265,16 @@ _notmuch_database_find_doc_ids (notmuch_database_t *notmuch,
 				const char *value,
 				Xapian::PostingIterator *begin,
 				Xapian::PostingIterator *end);
+
+#define NOTMUCH_DATABASE_VERSION 3
+
+/* features.cc */
+
+_notmuch_features
+_notmuch_database_parse_features (const void *ctx, const char *features, unsigned int version,
+				  char mode, char **incompat_out);
+
+char *
+_notmuch_database_print_features (const void *ctx, unsigned int features);
+
 #endif
diff --git a/lib/database.cc b/lib/database.cc
index 75189685..e08d43ca 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -39,8 +39,6 @@
 
 using namespace std;
 
-#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
-
 typedef struct {
     const char *name;
     const char *prefix;
@@ -458,41 +456,6 @@ _notmuch_database_prefix (notmuch_database_t *notmuch, const char *name)
     return NULL;
 }
 
-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" },
-    /* Existing databases will work fine for all queries not involving
-     * 'body:' */
-    { NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY,
-      "index body and headers separately", "w" },
-};
-
 const char *
 notmuch_status_to_string (notmuch_status_t status)
 {
@@ -817,83 +780,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,
@@ -1012,7 +898,7 @@ notmuch_database_open_verbose (const char *path,
 
 	/* Check features. */
 	incompat_features = NULL;
-	notmuch->features = _parse_features (
+	notmuch->features = _notmuch_database_parse_features (
 	    local, notmuch->xapian_db->get_metadata ("features").c_str (),
 	    version, mode == NOTMUCH_DATABASE_MODE_READ_WRITE ? 'w' : 'r',
 	    &incompat_features);
@@ -1674,7 +1560,7 @@ 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:
diff --git a/lib/features.cc b/lib/features.cc
new file mode 100644
index 00000000..8def2461
--- /dev/null
+++ b/lib/features.cc
@@ -0,0 +1,114 @@
+#include "database-private.h"
+
+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" },
+    /* Existing databases will work fine for all queries not involving
+     * 'body:' */
+    { NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY,
+      "index body and headers separately", "w" },
+};
+
+char *
+_notmuch_database_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;
+}
+
+
+/* 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.
+ */
+_notmuch_features
+_notmuch_database_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;
+}
-- 
2.28.0

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 3/6] lib: factor out prefix related code to its own file
  2020-08-12 22:49 preliminaries for merged config David Bremner
  2020-08-12 22:49 ` [PATCH 1/6] test: use keys with group 'test' in T590-libconfig David Bremner
  2020-08-12 22:49 ` [PATCH 2/6] lib: factor out feature name related code David Bremner
@ 2020-08-12 22:49 ` David Bremner
  2020-08-12 22:49 ` [PATCH 4/6] lib/config: delay setting talloc destructor David Bremner
                   ` (2 subsequent siblings)
  5 siblings, 0 replies; 8+ messages in thread
From: David Bremner @ 2020-08-12 22:49 UTC (permalink / raw)
  To: notmuch; +Cc: David Bremner

Reduce the size of database.cc, and limit the scope of prefix_table,
make sure it's accessed via a well-defined internal API.
---
 lib/Makefile.local     |   4 +-
 lib/database-private.h |   7 ++
 lib/database.cc        | 201 ++-------------------------------------
 lib/prefix.cc          | 210 +++++++++++++++++++++++++++++++++++++++++
 4 files changed, 228 insertions(+), 194 deletions(-)
 create mode 100644 lib/prefix.cc

diff --git a/lib/Makefile.local b/lib/Makefile.local
index 04418fa8..3aa9e80f 100644
--- a/lib/Makefile.local
+++ b/lib/Makefile.local
@@ -60,7 +60,9 @@ libnotmuch_cxx_srcs =		\
 	$(dir)/regexp-fields.cc	\
 	$(dir)/thread.cc \
 	$(dir)/thread-fp.cc     \
-	$(dir)/features.cc
+	$(dir)/features.cc	\
+	$(dir)/prefix.cc
+
 
 libnotmuch_modules := $(libnotmuch_c_srcs:.c=.o) $(libnotmuch_cxx_srcs:.cc=.o)
 
diff --git a/lib/database-private.h b/lib/database-private.h
index 2d220811..c9bc712b 100644
--- a/lib/database-private.h
+++ b/lib/database-private.h
@@ -277,4 +277,11 @@ _notmuch_database_parse_features (const void *ctx, const char *features, unsigne
 char *
 _notmuch_database_print_features (const void *ctx, unsigned int features);
 
+/* prefix.cc */
+notmuch_status_t
+_notmuch_database_setup_standard_query_fields (notmuch_database_t *notmuch);
+
+notmuch_status_t
+_notmuch_database_setup_user_query_fields (notmuch_database_t *notmuch);
+
 #endif
diff --git a/lib/database.cc b/lib/database.cc
index e08d43ca..defa3062 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -263,80 +263,6 @@ _notmuch_database_mode (notmuch_database_t *notmuch)
  *			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 },
-    { "body",                   "",             NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROBABILISTIC },
-    { "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 },
-    { "date",                   NULL,           NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROCESSOR },
-    { "query",                  NULL,           NOTMUCH_FIELD_EXTERNAL |
-      NOTMUCH_FIELD_PROCESSOR },
-    { "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->prefix)
-	notmuch->query_parser->add_prefix ("", prefix->prefix);
-    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);
-}
 
 notmuch_string_map_iterator_t *
 _notmuch_database_user_headers (notmuch_database_t *notmuch)
@@ -344,118 +270,6 @@ _notmuch_database_user_headers (notmuch_database_t *notmuch)
     return _notmuch_string_map_iterator_create (notmuch->user_header, "", false);
 }
 
-const char *
-_user_prefix (void *ctx, const char *name)
-{
-    return talloc_asprintf (ctx, "XU%s:", name);
-}
-
-static notmuch_status_t
-_setup_user_query_fields (notmuch_database_t *notmuch)
-{
-    notmuch_config_list_t *list;
-    notmuch_status_t status;
-
-    notmuch->user_prefix = _notmuch_string_map_create (notmuch);
-    if (notmuch->user_prefix == NULL)
-	return NOTMUCH_STATUS_OUT_OF_MEMORY;
-
-    notmuch->user_header = _notmuch_string_map_create (notmuch);
-    if (notmuch->user_header == NULL)
-	return NOTMUCH_STATUS_OUT_OF_MEMORY;
-
-    status = notmuch_database_get_config_list (notmuch, CONFIG_HEADER_PREFIX, &list);
-    if (status)
-	return status;
-
-    for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
-
-	prefix_t query_field;
-
-	const char *key = notmuch_config_list_key (list)
-			  + sizeof (CONFIG_HEADER_PREFIX) - 1;
-
-	_notmuch_string_map_append (notmuch->user_prefix,
-				    key,
-				    _user_prefix (notmuch, key));
-
-	_notmuch_string_map_append (notmuch->user_header,
-				    key,
-				    notmuch_config_list_value (list));
-
-	query_field.name = talloc_strdup (notmuch, key);
-	query_field.prefix = _user_prefix (notmuch, key);
-	query_field.flags = NOTMUCH_FIELD_PROBABILISTIC
-			    | NOTMUCH_FIELD_EXTERNAL;
-
-	_setup_query_field_default (&query_field, notmuch);
-    }
-
-    notmuch_config_list_destroy (list);
-
-    return NOTMUCH_STATUS_SUCCESS;
-}
-
-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(NOTMUCH_VALUE_TIMESTAMP))->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 */
-	if (prefix->prefix)
-	    notmuch->query_parser->add_prefix ("", prefix->prefix);
-	notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
-    } else {
-	_setup_query_field_default (prefix, notmuch);
-    }
-}
-
-const char *
-_find_prefix (const char *name)
-{
-    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 "";
-}
-
-/* Like find prefix, but include the possibility of user defined
- * prefixes specific to this database */
-
-const char *
-_notmuch_database_prefix (notmuch_database_t *notmuch, const char *name)
-{
-    unsigned int i;
-
-    /*XXX TODO: reduce code duplication */
-    for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
-	if (strcmp (name, prefix_table[i].name) == 0)
-	    return prefix_table[i].prefix;
-    }
-
-    if (notmuch->user_prefix)
-	return _notmuch_string_map_get (notmuch->user_prefix, name);
-
-    return NULL;
-}
-
 const char *
 notmuch_status_to_string (notmuch_status_t status)
 {
@@ -952,13 +766,14 @@ notmuch_database_open_verbose (const char *path,
 	notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
 	notmuch->query_parser->add_rangeprocessor (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);
-	    }
-	}
-	status = _setup_user_query_fields (notmuch);
+	status = _notmuch_database_setup_standard_query_fields (notmuch);
+	if (status)
+	    goto DONE;
+
+	status = _notmuch_database_setup_user_query_fields (notmuch);
+	if (status)
+	    goto DONE;
+
     } catch (const Xapian::Error &error) {
 	IGNORE_RESULT (asprintf (&message, "A Xapian exception occurred opening database: %s\n",
 				 error.get_msg ().c_str ()));
diff --git a/lib/prefix.cc b/lib/prefix.cc
new file mode 100644
index 00000000..dd7b193d
--- /dev/null
+++ b/lib/prefix.cc
@@ -0,0 +1,210 @@
+#include "database-private.h"
+#include "query-fp.h"
+#include "thread-fp.h"
+#include "regexp-fields.h"
+#include "parse-time-vrp.h"
+
+typedef struct {
+    const char *name;
+    const char *prefix;
+    notmuch_field_flag_t flags;
+} prefix_t;
+
+/* 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 },
+    { "body",                   "",             NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROBABILISTIC },
+    { "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 },
+    { "date",                   NULL,           NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "query",                  NULL,           NOTMUCH_FIELD_EXTERNAL |
+      NOTMUCH_FIELD_PROCESSOR },
+    { "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 const char *
+_user_prefix (void *ctx, const char *name)
+{
+    return talloc_asprintf (ctx, "XU%s:", name);
+}
+
+const char *
+_find_prefix (const char *name)
+{
+    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 "";
+}
+
+/* Like find prefix, but include the possibility of user defined
+ * prefixes specific to this database */
+
+const char *
+_notmuch_database_prefix (notmuch_database_t *notmuch, const char *name)
+{
+    unsigned int i;
+
+    /*XXX TODO: reduce code duplication */
+    for (i = 0; i < ARRAY_SIZE (prefix_table); i++) {
+	if (strcmp (name, prefix_table[i].name) == 0)
+	    return prefix_table[i].prefix;
+    }
+
+    if (notmuch->user_prefix)
+	return _notmuch_string_map_get (notmuch->user_prefix, name);
+
+    return NULL;
+}
+
+static void
+_setup_query_field_default (const prefix_t *prefix, notmuch_database_t *notmuch)
+{
+    if (prefix->prefix)
+	notmuch->query_parser->add_prefix ("", prefix->prefix);
+    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);
+}
+
+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(NOTMUCH_VALUE_TIMESTAMP))->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 */
+	if (prefix->prefix)
+	    notmuch->query_parser->add_prefix ("", prefix->prefix);
+	notmuch->query_parser->add_boolean_prefix (prefix->name, fp);
+    } else {
+	_setup_query_field_default (prefix, notmuch);
+    }
+}
+
+notmuch_status_t
+_notmuch_database_setup_standard_query_fields (notmuch_database_t *notmuch)
+{
+    for (unsigned int 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);
+	}
+    }
+    return NOTMUCH_STATUS_SUCCESS;
+}
+
+notmuch_status_t
+_notmuch_database_setup_user_query_fields (notmuch_database_t *notmuch)
+{
+    notmuch_config_list_t *list;
+    notmuch_status_t status;
+
+    notmuch->user_prefix = _notmuch_string_map_create (notmuch);
+    if (notmuch->user_prefix == NULL)
+	return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+    notmuch->user_header = _notmuch_string_map_create (notmuch);
+    if (notmuch->user_header == NULL)
+	return NOTMUCH_STATUS_OUT_OF_MEMORY;
+
+    status = notmuch_database_get_config_list (notmuch, CONFIG_HEADER_PREFIX, &list);
+    if (status)
+	return status;
+
+    for (; notmuch_config_list_valid (list); notmuch_config_list_move_to_next (list)) {
+
+	prefix_t query_field;
+
+	const char *key = notmuch_config_list_key (list)
+			  + sizeof (CONFIG_HEADER_PREFIX) - 1;
+
+	_notmuch_string_map_append (notmuch->user_prefix,
+				    key,
+				    _user_prefix (notmuch, key));
+
+	_notmuch_string_map_append (notmuch->user_header,
+				    key,
+				    notmuch_config_list_value (list));
+
+	query_field.name = talloc_strdup (notmuch, key);
+	query_field.prefix = _user_prefix (notmuch, key);
+	query_field.flags = NOTMUCH_FIELD_PROBABILISTIC
+			    | NOTMUCH_FIELD_EXTERNAL;
+
+	_setup_query_field_default (&query_field, notmuch);
+    }
+
+    notmuch_config_list_destroy (list);
+
+    return NOTMUCH_STATUS_SUCCESS;
+}
-- 
2.28.0

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 4/6] lib/config: delay setting talloc destructor
  2020-08-12 22:49 preliminaries for merged config David Bremner
                   ` (2 preceding siblings ...)
  2020-08-12 22:49 ` [PATCH 3/6] lib: factor out prefix related code to its own file David Bremner
@ 2020-08-12 22:49 ` David Bremner
  2020-08-12 22:49 ` [PATCH 5/6] test: add regression test for searching with alternate config David Bremner
  2020-08-12 22:49 ` [PATCH 6/6] lib: factor out notmuch_database_open* related code to own file David Bremner
  5 siblings, 0 replies; 8+ messages in thread
From: David Bremner @ 2020-08-12 22:49 UTC (permalink / raw)
  To: notmuch; +Cc: David Bremner

If Xapian has thrown an exception, it is not safe to invoke the
destuctor when freeing the list struct.
---
 lib/config.cc | 12 +++++++++---
 1 file changed, 9 insertions(+), 3 deletions(-)

diff --git a/lib/config.cc b/lib/config.cc
index dae0ff0e..d0497052 100644
--- a/lib/config.cc
+++ b/lib/config.cc
@@ -113,7 +113,6 @@ notmuch_database_get_config_list (notmuch_database_t *notmuch,
 	goto DONE;
     }
 
-    talloc_set_destructor (list, _notmuch_config_list_destroy);
     list->notmuch = notmuch;
     list->current_key = NULL;
     list->current_val = NULL;
@@ -133,8 +132,15 @@ notmuch_database_get_config_list (notmuch_database_t *notmuch,
     *out = list;
 
   DONE:
-    if (status && list)
-	talloc_free (list);
+    if (status) {
+	if (list) {
+	    talloc_free (list);
+	    if (status != NOTMUCH_STATUS_XAPIAN_EXCEPTION)
+		_notmuch_config_list_destroy (list);
+	}
+    }  else {
+	talloc_set_destructor (list, _notmuch_config_list_destroy);
+    }
 
     return status;
 }
-- 
2.28.0

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 5/6] test: add regression test for searching with alternate config
  2020-08-12 22:49 preliminaries for merged config David Bremner
                   ` (3 preceding siblings ...)
  2020-08-12 22:49 ` [PATCH 4/6] lib/config: delay setting talloc destructor David Bremner
@ 2020-08-12 22:49 ` David Bremner
  2020-08-12 22:49 ` [PATCH 6/6] lib: factor out notmuch_database_open* related code to own file David Bremner
  5 siblings, 0 replies; 8+ messages in thread
From: David Bremner @ 2020-08-12 22:49 UTC (permalink / raw)
  To: notmuch; +Cc: David Bremner

Make sure upcoming changes to config handling do not break command
line specification.
---
 test/T140-excludes.sh | 10 ++++++++++
 1 file changed, 10 insertions(+)

diff --git a/test/T140-excludes.sh b/test/T140-excludes.sh
index 0cf69975..cef07095 100755
--- a/test/T140-excludes.sh
+++ b/test/T140-excludes.sh
@@ -39,6 +39,16 @@ deleted_id=$gen_msg_id
 output=$(notmuch search subject:deleted | notmuch_search_sanitize)
 test_expect_equal "$output" "thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)"
 
+test_begin_subtest "Search, exclude \"deleted\" messages; alternate config file"
+cp ${NOTMUCH_CONFIG} alt-config
+notmuch config set search.exclude_tags
+notmuch --config=alt-config search subject:deleted | notmuch_search_sanitize > OUTPUT
+cp alt-config ${NOTMUCH_CONFIG}
+cat <<EOF > EXPECTED
+thread:XXX   2001-01-05 [1/1] Notmuch Test Suite; Not deleted (inbox unread)
+EOF
+test_expect_equal_file EXPECTED OUTPUT
+
 test_begin_subtest "Search, exclude \"deleted\" messages from message search"
 output=$(notmuch search --output=messages subject:deleted | notmuch_search_sanitize)
 test_expect_equal "$output" "id:$not_deleted_id"
-- 
2.28.0

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 6/6] lib: factor out notmuch_database_open* related code to own file
  2020-08-12 22:49 preliminaries for merged config David Bremner
                   ` (4 preceding siblings ...)
  2020-08-12 22:49 ` [PATCH 5/6] test: add regression test for searching with alternate config David Bremner
@ 2020-08-12 22:49 ` David Bremner
  5 siblings, 0 replies; 8+ messages in thread
From: David Bremner @ 2020-08-12 22:49 UTC (permalink / raw)
  To: notmuch; +Cc: David Bremner

Reduce the size of database.cc, and prepare for extending the database
opening API
---
 lib/Makefile.local |   3 +-
 lib/database.cc    | 219 ---------------------------------------------
 lib/open.cc        | 218 ++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 220 insertions(+), 220 deletions(-)
 create mode 100644 lib/open.cc

diff --git a/lib/Makefile.local b/lib/Makefile.local
index 3aa9e80f..ddd169dc 100644
--- a/lib/Makefile.local
+++ b/lib/Makefile.local
@@ -61,7 +61,8 @@ libnotmuch_cxx_srcs =		\
 	$(dir)/thread.cc \
 	$(dir)/thread-fp.cc     \
 	$(dir)/features.cc	\
-	$(dir)/prefix.cc
+	$(dir)/prefix.cc	\
+	$(dir)/open.cc
 
 
 libnotmuch_modules := $(libnotmuch_c_srcs:.c=.o) $(libnotmuch_cxx_srcs:.cc=.o)
diff --git a/lib/database.cc b/lib/database.cc
index defa3062..0f4e2ff9 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -19,10 +19,6 @@
  */
 
 #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>
@@ -50,12 +46,6 @@ typedef struct {
 #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
@@ -594,215 +584,6 @@ _notmuch_database_new_revision (notmuch_database_t *notmuch)
     return new_revision;
 }
 
-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 ();
-	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->writable_xapian_db = NULL;
-    notmuch->atomic_nesting = 0;
-    notmuch->view = 1;
-    try {
-	string last_thread_id;
-	string last_mod;
-
-	if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
-	    notmuch->writable_xapian_db = new Xapian::WritableDatabase (xapian_path,
-									DB_ACTION);
-	    notmuch->xapian_db = notmuch->writable_xapian_db;
-	} 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_database_destroy (notmuch);
-	    notmuch = NULL;
-	    status = NOTMUCH_STATUS_FILE_ERROR;
-	    goto DONE;
-	}
-
-	/* Check features. */
-	incompat_features = NULL;
-	notmuch->features = _notmuch_database_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_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::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
-	notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP, "date:");
-	notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (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_rangeprocessor (notmuch->value_range_processor);
-	notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
-	notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
-
-	status = _notmuch_database_setup_standard_query_fields (notmuch);
-	if (status)
-	    goto DONE;
-
-	status = _notmuch_database_setup_user_query_fields (notmuch);
-	if (status)
-	    goto DONE;
-
-    } 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);
-
-    if (notmuch)
-	notmuch->open = true;
-
-    return status;
-}
-
 notmuch_status_t
 notmuch_database_close (notmuch_database_t *notmuch)
 {
diff --git a/lib/open.cc b/lib/open.cc
new file mode 100644
index 00000000..1b17e63a
--- /dev/null
+++ b/lib/open.cc
@@ -0,0 +1,218 @@
+#include <unistd.h>
+#include "database-private.h"
+#include "parse-time-vrp.h"
+
+#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
+
+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 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 ();
+	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->writable_xapian_db = NULL;
+    notmuch->atomic_nesting = 0;
+    notmuch->view = 1;
+    try {
+	std::string last_thread_id;
+	std::string last_mod;
+
+	if (mode == NOTMUCH_DATABASE_MODE_READ_WRITE) {
+	    notmuch->writable_xapian_db = new Xapian::WritableDatabase (xapian_path,
+									DB_ACTION);
+	    notmuch->xapian_db = notmuch->writable_xapian_db;
+	} 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_database_destroy (notmuch);
+	    notmuch = NULL;
+	    status = NOTMUCH_STATUS_FILE_ERROR;
+	    goto DONE;
+	}
+
+	/* Check features. */
+	incompat_features = NULL;
+	notmuch->features = _notmuch_database_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_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::NumberRangeProcessor (NOTMUCH_VALUE_TIMESTAMP);
+	notmuch->date_range_processor = new ParseTimeRangeProcessor (NOTMUCH_VALUE_TIMESTAMP, "date:");
+	notmuch->last_mod_range_processor = new Xapian::NumberRangeProcessor (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_rangeprocessor (notmuch->value_range_processor);
+	notmuch->query_parser->add_rangeprocessor (notmuch->date_range_processor);
+	notmuch->query_parser->add_rangeprocessor (notmuch->last_mod_range_processor);
+
+	status = _notmuch_database_setup_standard_query_fields (notmuch);
+	if (status)
+	    goto DONE;
+
+	status = _notmuch_database_setup_user_query_fields (notmuch);
+	if (status)
+	    goto DONE;
+
+    } 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);
+
+    if (notmuch)
+	notmuch->open = true;
+
+    return status;
+}
-- 
2.28.0

^ permalink raw reply related	[flat|nested] 8+ messages in thread

* [PATCH 2/6] lib: factor out feature name related code.
  2020-12-20 12:10 v2 preliminaries for merged config David Bremner
@ 2020-12-20 12:10 ` David Bremner
  0 siblings, 0 replies; 8+ messages in thread
From: David Bremner @ 2020-12-20 12:10 UTC (permalink / raw)
  To: notmuch; +Cc: David Bremner

database.cc is uncomfortably large, and some of the static data
structures do not need to be shared as much as they are.

This is a somewhat small piece to factor out, but it will turn out to
be helpful to further refactoring.
---
 lib/Makefile.local     |   3 +-
 lib/database-private.h |  14 +++++
 lib/database.cc        | 116 +----------------------------------------
 lib/features.cc        | 114 ++++++++++++++++++++++++++++++++++++++++
 4 files changed, 131 insertions(+), 116 deletions(-)
 create mode 100644 lib/features.cc

diff --git a/lib/Makefile.local b/lib/Makefile.local
index a6400126..04418fa8 100644
--- a/lib/Makefile.local
+++ b/lib/Makefile.local
@@ -59,7 +59,8 @@ libnotmuch_cxx_srcs =		\
 	$(dir)/config.cc	\
 	$(dir)/regexp-fields.cc	\
 	$(dir)/thread.cc \
-	$(dir)/thread-fp.cc
+	$(dir)/thread-fp.cc     \
+	$(dir)/features.cc
 
 libnotmuch_modules := $(libnotmuch_c_srcs:.c=.o) $(libnotmuch_cxx_srcs:.cc=.o)
 
diff --git a/lib/database-private.h b/lib/database-private.h
index 041602cd..2d220811 100644
--- a/lib/database-private.h
+++ b/lib/database-private.h
@@ -32,6 +32,8 @@
 
 #include "notmuch-private.h"
 
+#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
+
 #ifdef SILENCE_XAPIAN_DEPRECATION_WARNINGS
 #define XAPIAN_DEPRECATED(D) D
 #endif
@@ -263,4 +265,16 @@ _notmuch_database_find_doc_ids (notmuch_database_t *notmuch,
 				const char *value,
 				Xapian::PostingIterator *begin,
 				Xapian::PostingIterator *end);
+
+#define NOTMUCH_DATABASE_VERSION 3
+
+/* features.cc */
+
+_notmuch_features
+_notmuch_database_parse_features (const void *ctx, const char *features, unsigned int version,
+				  char mode, char **incompat_out);
+
+char *
+_notmuch_database_print_features (const void *ctx, unsigned int features);
+
 #endif
diff --git a/lib/database.cc b/lib/database.cc
index 75189685..4a477bd7 100644
--- a/lib/database.cc
+++ b/lib/database.cc
@@ -39,8 +39,6 @@
 
 using namespace std;
 
-#define ARRAY_SIZE(arr) (sizeof (arr) / sizeof (arr[0]))
-
 typedef struct {
     const char *name;
     const char *prefix;
@@ -458,41 +456,6 @@ _notmuch_database_prefix (notmuch_database_t *notmuch, const char *name)
     return NULL;
 }
 
-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" },
-    /* Existing databases will work fine for all queries not involving
-     * 'body:' */
-    { NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY,
-      "index body and headers separately", "w" },
-};
-
 const char *
 notmuch_status_to_string (notmuch_status_t status)
 {
@@ -817,83 +780,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,
@@ -1674,7 +1560,7 @@ 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:
diff --git a/lib/features.cc b/lib/features.cc
new file mode 100644
index 00000000..8def2461
--- /dev/null
+++ b/lib/features.cc
@@ -0,0 +1,114 @@
+#include "database-private.h"
+
+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" },
+    /* Existing databases will work fine for all queries not involving
+     * 'body:' */
+    { NOTMUCH_FEATURE_UNPREFIX_BODY_ONLY,
+      "index body and headers separately", "w" },
+};
+
+char *
+_notmuch_database_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;
+}
+
+
+/* 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.
+ */
+_notmuch_features
+_notmuch_database_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;
+}
-- 
2.29.2

^ permalink raw reply related	[flat|nested] 8+ messages in thread

end of thread, other threads:[~2020-12-20 12:11 UTC | newest]

Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2020-08-12 22:49 preliminaries for merged config David Bremner
2020-08-12 22:49 ` [PATCH 1/6] test: use keys with group 'test' in T590-libconfig David Bremner
2020-08-12 22:49 ` [PATCH 2/6] lib: factor out feature name related code David Bremner
2020-08-12 22:49 ` [PATCH 3/6] lib: factor out prefix related code to its own file David Bremner
2020-08-12 22:49 ` [PATCH 4/6] lib/config: delay setting talloc destructor David Bremner
2020-08-12 22:49 ` [PATCH 5/6] test: add regression test for searching with alternate config David Bremner
2020-08-12 22:49 ` [PATCH 6/6] lib: factor out notmuch_database_open* related code to own file David Bremner
  -- strict thread matches above, loose matches on Subject: below --
2020-12-20 12:10 v2 preliminaries for merged config David Bremner
2020-12-20 12:10 ` [PATCH 2/6] lib: factor out feature name related code David Bremner

Code repositories for project(s) associated with this public inbox

	https://yhetil.org/notmuch.git/

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).