* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
- * along with this program. If not, see http://www.gnu.org/licenses/ .
+ * along with this program. If not, see https://www.gnu.org/licenses/ .
*
* Author: Carl Worth <cworth@cworth.org>
*/
#include "database-private.h"
#include "parse-time-vrp.h"
+#include "query-fp.h"
+#include "regexp-fields.h"
#include "string-util.h"
#include <iostream>
typedef struct {
const char *name;
const char *prefix;
+ notmuch_field_flag_t flags;
} prefix_t;
#define NOTMUCH_DATABASE_VERSION 3
#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
+
/* Here's the current schema for our database (for NOTMUCH_DATABASE_VERSION):
*
* We currently have three different types of documents (mail, ghost,
* STRING is the name of a file within that
* directory for this mail message.
*
+ * property: Has a property with key=value
+ * FIXME: if no = is present, should match on any value
+ *
* A mail document also has four values:
*
* TIMESTAMP: The time_t value corresponding to the message's
* generated is 1 and the value will be
* incremented for each thread ID.
*
+ * C* metadata keys starting with C indicate
+ * configuration data. It can be managed with the
+ * n_database_*config* API. There is a convention
+ * of hierarchical keys separated by '.' (e.g.
+ * query.notmuch stores the value for the named
+ * query 'notmuch'), but it is not enforced by the
+ * API.
+ *
* Obsolete metadata
* -----------------
*
/* With these prefix values we follow the conventions published here:
*
- * http://xapian.org/docs/omega/termprefixes.html
+ * 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'
* nearly universal to all mail messages).
*/
-static prefix_t BOOLEAN_PREFIX_INTERNAL[] = {
- { "type", "T" },
- { "reference", "XREFERENCE" },
- { "replyto", "XREPLYTO" },
- { "directory", "XDIRECTORY" },
- { "file-direntry", "XFDIRENTRY" },
- { "directory-direntry", "XDDIRENTRY" },
-};
-
-static prefix_t BOOLEAN_PREFIX_EXTERNAL[] = {
- { "thread", "G" },
- { "tag", "K" },
- { "is", "K" },
- { "id", "Q" },
- { "path", "P" },
+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 },
+ { "tag", "K", NOTMUCH_FIELD_EXTERNAL },
+ { "is", "K", NOTMUCH_FIELD_EXTERNAL },
+ { "id", "Q", NOTMUCH_FIELD_EXTERNAL },
+ { "mid", "Q", NOTMUCH_FIELD_EXTERNAL },
+ { "path", "P", NOTMUCH_FIELD_EXTERNAL },
+ { "property", "XPROPERTY", NOTMUCH_FIELD_EXTERNAL },
/*
- * Without the ":", since this is a multi-letter prefix, Xapian
- * will add a colon itself if the first letter of the path is
- * upper-case ASCII. Including the ":" forces there to always be a
- * colon, which keeps our own logic simpler.
+ * 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:" },
+ { "folder", "XFOLDER:", NOTMUCH_FIELD_EXTERNAL },
+#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 prefix_t PROBABILISTIC_PREFIX[]= {
- { "from", "XFROM" },
- { "to", "XTO" },
- { "attachment", "XATTACHMENT" },
- { "mimetype", "XMIMETYPE"},
- { "subject", "XSUBJECT"},
-};
+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);
+}
-const char *
-_find_prefix (const char *name)
+#if HAVE_XAPIAN_FIELD_PROCESSOR
+static void
+_setup_query_field (const prefix_t *prefix, notmuch_database_t *notmuch)
{
- unsigned int i;
+ if (prefix->flags & NOTMUCH_FIELD_PROCESSOR) {
+ Xapian::FieldProcessor *fp;
- for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_INTERNAL); i++) {
- if (strcmp (name, BOOLEAN_PREFIX_INTERNAL[i].name) == 0)
- return BOOLEAN_PREFIX_INTERNAL[i].prefix;
- }
+ 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
+ fp = (new RegexpFieldProcessor (prefix->name, *notmuch->query_parser, notmuch))->release ();
- for (i = 0; i < ARRAY_SIZE (BOOLEAN_PREFIX_EXTERNAL); i++) {
- if (strcmp (name, BOOLEAN_PREFIX_EXTERNAL[i].name) == 0)
- return BOOLEAN_PREFIX_EXTERNAL[i].prefix;
+ /* 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)
+{
+ unsigned int i;
- for (i = 0; i < ARRAY_SIZE (PROBABILISTIC_PREFIX); i++) {
- if (strcmp (name, PROBABILISTIC_PREFIX[i].name) == 0)
- return PROBABILISTIC_PREFIX[i].prefix;
+ 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);
talloc_free (notmuch->status_string);
notmuch->status_string = talloc_vasprintf (notmuch, format, va_args);
+ va_end (va_args);
+}
+
+void
+_notmuch_database_log_append (notmuch_database_t *notmuch,
+ const char *format,
+ ...)
+{
+ va_list va_args;
+
+ va_start (va_args, format);
+
+ if (notmuch->status_string)
+ notmuch->status_string = talloc_vasprintf_append (notmuch->status_string, format, va_args);
+ else
+ notmuch->status_string = talloc_vasprintf (notmuch, format, va_args);
va_end (va_args);
}
ref = _parse_message_id (ctx, refs, &refs);
if (ref && strcmp (ref, message_id)) {
- g_hash_table_insert (hash, ref, NULL);
+ g_hash_table_add (hash, ref);
last_ref = ref;
}
}
* reference to the database. We should avoid making a message
* its own parent, thus the above check.
*/
- return last_ref;
+ return talloc_strdup(ctx, last_ref);
}
notmuch_status_t
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,
- Xapian::DB_CREATE_OR_OPEN);
+ DB_ACTION);
} else {
notmuch->xapian_db = new Xapian::Database (xapian_path);
}
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 (BOOLEAN_PREFIX_EXTERNAL); i++) {
- prefix_t *prefix = &BOOLEAN_PREFIX_EXTERNAL[i];
- notmuch->query_parser->add_boolean_prefix (prefix->name,
- prefix->prefix);
- }
-
- for (i = 0; i < ARRAY_SIZE (PROBABILISTIC_PREFIX); i++) {
- prefix_t *prefix = &PROBABILISTIC_PREFIX[i];
- notmuch->query_parser->add_prefix (prefix->name, prefix->prefix);
+ 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",
return status;
}
-#if HAVE_XAPIAN_COMPACT
+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),
return ret;
}
-#else
-notmuch_status_t
-notmuch_database_compact (unused (const char *path),
- unused (const char *backup_path),
- unused (notmuch_compact_status_cb_t status_cb),
- unused (void *closure))
-{
- _notmuch_database_log (notmuch, "notmuch was compiled against a xapian version lacking compaction support.\n");
- return NOTMUCH_STATUS_UNSUPPORTED_OPERATION;
-}
-#endif
notmuch_status_t
notmuch_database_destroy (notmuch_database_t *notmuch)
notmuch->atomic_nesting > 0)
goto DONE;
- if (notmuch_database_needs_upgrade(notmuch))
- return NOTMUCH_STATUS_UPGRADE_REQUIRED;
+ if (notmuch_database_needs_upgrade (notmuch))
+ return NOTMUCH_STATUS_UPGRADE_REQUIRED;
try {
(static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db))->begin_transaction (false);
* However, we rely on flushing to test atomicity. */
const char *thresh = getenv ("XAPIAN_FLUSH_THRESHOLD");
if (thresh && atoi (thresh) == 1)
- db->flush ();
+ db->commit ();
} catch (const Xapian::Error &error) {
_notmuch_database_log (notmuch, "A Xapian exception occurred committing transaction: %s.\n",
error.get_msg().c_str());
slash = path + strlen (path) - 1;
/* First, skip trailing slashes. */
- while (slash != path) {
- if (*slash != '/')
- break;
-
+ while (slash != path && *slash == '/')
--slash;
- }
/* Then, find a slash. */
- while (slash != path) {
- if (*slash == '/')
- break;
-
+ while (slash != path && *slash != '/') {
if (basename)
*basename = slash;
}
/* Finally, skip multiple slashes. */
- while (slash != path) {
- if (*slash != '/')
- break;
-
+ while (slash != path && *(slash - 1) == '/')
--slash;
- }
if (slash == path) {
if (directory)
*basename = path;
} else {
if (directory)
- *directory = talloc_strndup (ctx, path, slash - path + 1);
+ *directory = talloc_strndup (ctx, path, slash - path);
}
return NOTMUCH_STATUS_SUCCESS;
* References header, if available. If not, fall back to the
* first message ID in the In-Reply-To header. */
if (last_ref_message_id) {
- _notmuch_message_add_term (message, "replyto",
- last_ref_message_id);
+ _notmuch_message_add_term (message, "replyto",
+ last_ref_message_id);
} else if (in_reply_to_message_id) {
_notmuch_message_add_term (message, "replyto",
in_reply_to_message_id);
if (stored_id.empty ()) {
return NULL;
} else {
- Xapian::WritableDatabase *db;
+ Xapian::WritableDatabase *db;
db = static_cast <Xapian::WritableDatabase *> (notmuch->xapian_db);
/* Clear the metadata for this message ID. We don't need it
* anymore. */
- db->set_metadata (metadata_key, "");
+ db->set_metadata (metadata_key, "");
- return talloc_strdup (ctx, stored_id.c_str ());
+ return talloc_strdup (ctx, stored_id.c_str ());
}
}